Skip to main content

E2E Testing & Hydration Readiness

Because Coralite uses a Server-Side Rendering (SSR) approach with deferred client-side component definitions and scripts, there is a small window of time between when the browser initially paints the HTML and when the dynamic web components actually become interactive (Hydration).

End-to-end testing frameworks like Playwright or Cypress interact with the DOM incredibly fast. If you try to click a button or assert the state of a dynamic component immediately after navigating to a page, your tests will likely fail due to a race condition: the framework hasn't finished mounting or initializing the component's internal state.

Testing Selectors (data-testid) #

When Coralite is run in development mode, it automatically activates a built-in testing plugin. This plugin parses your component templates and duplicates the resolved ref attributes into data-testid attributes for both declarative and imperative components.

Because Coralite isolates component references, the generated data-testid follows a strict namespaced pattern: [componentId]__[refName]-[index].

For example, a component with the ID user-profile defining a button with ref="submit-btn" will render the following HTML for its first instance:

html
Code copied!
  <div class="profile-container" ref="user-profile__container-0" data-testid="user-profile__container-0">
    <h2 ref="user-profile__title-0" data-testid="user-profile__title-0">User Profile</h2>
    <button ref="user-profile__submit-btn-0" data-testid="user-profile__submit-btn-0">Save Changes</button>
  </div>

This allows you to write highly specific E2E tests using the standard data-testid pattern without polluting your production builds.

The window.__coralite_ready__ Promise #

To solve this, Coralite injects a global promise named window.__coralite_ready__ into the built HTML document. This promise acts as a reliable hook to signal the absolute completion of the client-side hydration phase.

Your testing frameworks MUST wait for this promise to resolve immediately after performing a page navigation before interacting with any Coralite components.

Playwright Example #

javascript
Code copied!
  import { test, expect } from '@playwright/test';
  
  test('component should increment counter', async ({ page }) => {
    // 1. Navigate to the page
    await page.goto('/my-page');
  
    // 2. Wait for Coralite hydration to complete
    await page.waitForFunction(() => window.__coralite_ready__);
  
    // 3. Now it is safe to interact with the DOM using the namespaced pattern
    const button = page.getByTestId('my-counter__increment-btn-0');
    await button.click();
  
    await expect(page.getByTestId('my-counter__count-display-0')).toHaveText('1');
  });

Cypress Example #

javascript
Code copied!
  describe('Counter Component', () => {
    it('should increment counter', () => {
      // 1. Navigate to the page
      cy.visit('/my-page');
  
      // 2. Wait for Coralite hydration to complete
      cy.window().then((win) => {
        return win.__coralite_ready__;
      });
  
      // 3. Now it is safe to interact with the DOM using the namespaced pattern
      cy.get('[data-testid="my-counter__increment-btn-0"]').click();
      cy.get('[data-testid="my-counter__count-display-0"]').should('have.text', '1');
    });
  });

What Does It Wait For? #

The window.__coralite_ready__ promise ensures two critical hydration pipelines have completed:

  1. Component Definition: The framework has dynamically imported all required module chunks for custom elements and registered them via customElements.define().
  2. Declarative Scripts: The framework has fully executed the async client.script blocks for all declarative component instances mapped on the page.

For a detailed technical breakdown of how these pipelines operate under the hood, refer to the Hydration Readiness Technical Reference.

Start Building with Coralite!

Use the scaffolding script to get jump started into your next project with Coralite

Copied commandline!