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:
<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 #
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 #
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:
- Component Definition: The framework has dynamically imported all required module chunks for custom elements and registered them via
customElements.define(). - Declarative Scripts: The framework has fully executed the async
client.scriptblocks 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.