Test Your Components

Before you test components, make sure you’ve reviewed the best practices for portable components.

Important

To check if you can server-side render (SSR) your components, evaluate them with the SSR playground, the SSR test runner, and unit tests with Jest. You can also manually verify that SSR was successful for a component.

Debug with the SSR Playground 

The SSR playground lets you render, debug, and experiment with individual components and their children. You can use the playground in client-side rendering (CSR) mode or SSR mode.

To run the playground, navigate to a directory that contains one of these files and run the appropriate playground command.

  • lwc.config.json file
  • package.json file containing an lwc field
  • sfdx-project.json file that points to a project directory containing LWCs

SSR Playground Commands 

To open the playground in Chrome, run this command in your terminal:

1npx -p @lwc/wds-playground playground namespace/component

To open the playground in Chrome with DevTools, run this terminal command:

1npx -p @lwc/wds-playground playground namespace/component --open --devtools

Step-By-Step Testing with the SSR Playground 

Start at the “leaves” of your component tree, or components that don’t contain other components.

  1. Open the component in the SSR playground.
    1. Modify the component properties to test important use cases.
    2. Address errors that are reported during the three phases of hydration—SSR, DOM insertion, and rehydration.
    3. Look for and address any visual bugs in your component.
  2. Enable the CSR toggle to render your component with SSR and CSR side by side.
  3. Use the playground’s comparison tool to ensure the SSR and CSR component instances are visually identical.
  4. Enable the layout shift tool in the Misc section in config to observe any layout shifts during SSR hydration.

The SSR playground's comparison tool.

By default, the SSR playground takes your component through three stages of its SSR lifecycle:

  1. Render the component to HTML markup on the server.
  2. Insert the markup into the DOM on the client.
  3. Hydrate the DOM subtree and associate it with an instance of your component class.

We recommend that you write tests using the SSR test runner to ensure that your components don’t regress as you make changes.

Install the SSR Test Runner 

This tool works best with Node v18 or v20.

Note

You have to install the SSR Test Runner before you can use it on your project.

To add the test runner to your project, download the @lwc/test-runner package using a package management tool. We recommend using Node Package Manager (npm).

Installation command
1npm install --save-dev @lwc/test-runner

Write SSR tests 

By default, the SSR Test Runner executes SSR tests in headless Chrome. Unlike Jest tests, the SSR tests run in a full-featured web browser.

Note

To create your own SSR tests, you can use these functions from the @lwc/test-runner package.

renderToMarkup() 

renderToMarkup() is an asynchronous function that takes:

  • The path to your component
  • The properties that you use for rendering

It returns:

  • Promise<String> where the String is HTML markup

insertMarkupIntoDom() 

insertMarkupIntoDom() is an asynchronous function that takes:

  • SSR markup, like Promise<String> returned by renderToMarkup()

It returns:

  • Promise<HtmlElement>(), which is a handle to the root element of your SSR-rendered DOM subtree.

hydrateElement() 

hydrateElement() is an async function that takes:

  • A root element, like Promise<HtmlElement> returned from insertMarkupIntoDom()
  • Component properties, which should be the same as those passed into renderToMarkup()

It returns:

  • Promise<Boolean>, where the Boolean indicates whether hydration completed without validation errors. In most cases, you’ll want this Boolean value to be true. If hydration failed, review the errors in the console.

expect() 

You can use this function to chain assertions. To make it easier to test your components, it includes all the assertions from Chai.expect as well as the following additions.

  • throwErrorInConnectedCallback: Asserts any errors thrown in connectedCallback.
  • SSRCorrectly: Checks that all three stages of SSR lifecycle are performed correctly.
  • visuallyIdenticalInCSRandSSR: Performs a pixel match of SSR and CSR components.
  • noLayoutShifts: Ensures that the component didn’t have any layout shifts when it was hydrated in DOM.
  • notMakeDomMutationsDuringSSR: Ensures that the component didn’t make any DOM mutation.

Example SSR Test 

Here’s a sample SSR test that you can execute with the SSR Test Runner.

1import {
2    // Importing `expect` from the toolkit is subject to change
3    expect,
4    querySelectorDeep,
5    // These three functions contribute heavily to your tests
6    renderToMarkup,
7    insertMarkupIntoDom,
8    hydrateElement,
9} from '@lwc/test-runner';
10
11// Instead of importing the component directly, use the path of
12// the component's JS file. This value is passed to `renderToMarkup`
13// and `hydrateElement` where the component is imported automatically.
14const componentPath = import.meta.resolve('./parent.js');
15
16describe('<x-parent>', () => {
17    it('is SSR-able and very snazzy', async () => {
18    // Errors thrown during SSR cause the test to fail
19    const markup = await renderToMarkup(componentPath, {});
20
21    // Once you have the raw HTML markup, you can make related assertions.
22    expect(markup).to.contain('</x-parent>');
23
24    // Insert that markup into the DOM. It doesn't get attached
25    // to an instance of your component class yet.
26    const el = await insertMarkupIntoDom(markup);
27
28    // Make assertions about pre-hydrated DOM
29    expect(el).to.haveShadowChild('p.child-content');
30
31    // Finally, hydrate the HTML that was generated on the server and
32    // inserted into the DOM in previous steps.
33    const hydratedWithSsrDOM = await hydrateElement(el, componentPath);
34
35    // Check that hydration occurred without validation errors.
36    expect(hydratedWithSsrDOM).to.be.true;
37
38    // Now that the SSR-generated markup has been inserted and hydrated,
39    // the component should behave as though it were originally rendered
40    // in the browser with CSR. Check the component to ensure expected behavior.
41    expect(querySelectorDeep('p.child-content', el)).to.have.text('Hmm! hello from parent');
42    });
43
44    // Unless you want to make assertions on output of each individual stage of SSR,
45    // you can use this one-liner to check that DOM was hydrated without any errors.
46    it('SSR correctly', async () => {
47        await expect(componentPath, {}).to.SSRCorrectly();
48    });
49
50    // Make sure no error is thrown when connected callback lifecycle is executed
51    it('doesnt throw in connected callback', async () => {
52        await expect(async () => {
53            await renderToMarkup(componentPath, {});
54        }).to.not.throwErrorInConnectedCallback();
55    });
56
57    // Pixel match rendered SSR and CSR components to check for visual correctness
58    it('checks that SSR and CSR are visually the same ', async () => {
59        await expect(componentPath, {}).to.be.visuallyIdenticalInCSRandSSR();
60    });
61
62    // No layout shift was observed during hydration stage
63    it('has no layout shifts', async () => {
64        await expect(componentPath, {}).to.have.noLayoutShifts();
65    });
66
67    // The component doesn't make any DOM mutation
68    it('makes no DOM mutations', async () => {
69        await expect(componentPath, {}).to.notMakeDomMutationsDuringSSR();
70    });
71});

Run the Test Runner 

Ready to invoke the test runner and run your own SSR tests? First, make sure you installed the @lwc/test-runner dependency for your project.

Then, run the test runner using npx or NPM scripts to invoke the @lwc/test-runner command. Tests run in parallel in separate headless Chrome tabs. The tests run fast, and as many as ten thousand tests can complete in under six seconds — depending on your hardware.

Here’s the npx command to start the test runner. To customize your test runner output, you can use supported command line flags. By default, the @lwc/test-runner command prints all the browser console output in your terminal.

1npx @lwc/test-runner SPEC_FILE_PATTERN

If you use ZSH, surround SPEC_FILE_PATTERN in single quotes.

Important

To distinguish your project’s SSR tests from your Jest tests, use unique file extensions. For example, if your Jest tests follow the format COMPONENT_NAME.spec.js, you can follow the format COMPONENT_NAME.spec-ssr.js for your SSR test files. If you follow this filename format, run your tests like this:

1npx @lwc/test-runner './src/**/*.spec-ssr.js'

You can also create a shortcut for this command in your project’s package.json that uses NPM. Here’s an example:

1"scripts": {
2    "test:ssr": "lwc-test-runner 'src/**/*.spec-ssr.js'"
3}

@lwc/test-runner Flags 

Add these flags to your @lwc/test-runner command to customize its command line output.

FlagDescription
--debugEnables debug mode to find issues with your tests.
--modulesDir <directory name>Invoke the test runner on a specific directory in your project.
--puppeteerExecute the test runner in the Puppeteer browser environment.
--quietPrevents browser console output from printing in your terminal.

Interpret Test Runner Output 

If the test runner returns failures for hydrateElement, noLayoutShifts, or notMakeDomMutationsDuringSSR, you likely have performance issues on the client-side.

Manually Verify SSR for a Component 

When a component is rendered on a page, you can verify that SSR was successful by looking at the DOM elements. To determine if an island or component tree implemented SSR successfully:

  1. In Chrome, right-click the page and select to View Page Source. If you use Firefox or another browser, the View Page Source tool might be named something else, like Inspect or View Selection Source.
  2. Search for some HTML or text from the component. If you find it, then SSR was successful.

CSR-only — The wrapper has no inner HTML.

CSR-only output
1<webruntime-island-container-x1787601173>
2</webruntime-island-container-x1787601173>

SSR with hydration — The wrapper has inner HTML and a data-lwr-props-id attribute.

SSR with hydration output
1<webruntime-island-container-x1783627592 data-lwr-props-id="lwcprops8de0">
2    <c-cmp>
3        <style type="text/css">...</style>
4        <textarea></textarea>
5    </c-cmp>
6</webruntime-island-container-x1783627592>

SSR-only (not hydrated) — There’s no wrapper, so the HTML exists in the page source.

SSR-only (no hydration) output
1<c-title>
2    <style type="text/css">...</style>
3    <h1>Page Title</h1>
4</c-title>

Write Jest Unit Tests 

Jest is the primary tool used for testing in LWR projects. The lwc-test NPM package lets you write Jest unit tests that you can run in SSR and CSR environments. We recommend using two separate test suites to ensure that SSR and CSR are independently and thoroughly validated.

To start working with SSR or CSR unit tests, add the latest version of the following dependencies to your project:

  • @lwc/jest-ssr-snapshot-utils
  • @lwc/jest-jsdom-test-env
  • @lwc/jest-preset

SSR Unit Tests 

Server-side tests focus on rendering components to static HTML on the server side. To make sure that server-side logic works as expected without client-side DOM interactions, these tests are executed in a Node environment.

Here’s a sample Jest configuration file for server-side testing:

jest.ssr-server.config.js
1module.exports = {
2    displayName: 'Server-side rendering',
3    preset: '@lwc/jest-preset/ssr-server',
4    testMatch: ['**/*.ssr-server.(spec|test).(js|ts)'],
5    collectCoverageFrom: ['**/*.ssr-server.(spec|test).(js|ts)'],
6};

To run a server-side test, add a script to your package.json. You can alternatively use jest --selectProjects <project_name> to target specific test suites.

package.json
1"scripts": {
2    "test:ssr:server": "jest --no-cache --projects=./example-ssr/jest.ssr-server.config.js"
3}

Running server-side tests generates static HTML markup as snapshots. Every part of a snapshot associated with a table-driven test case is linked to a unique hash that’s generated from the component’s tag name, properties, and state. Use snapshots to verify the consistency of server-rendered output, like this:

  1. Run server-side tests to generate baseline snapshots.
  2. Whenever your components change, rerun the server-side tests to identify any related markup changes. The tests fail if any discrepancies between component versions arise.
  3. After you confirm that your changes are safe and valid, update your HTML markup snapshots.

CSR Unit Tests 

Client-side tests validate how components behave post-hydration in a browser-like environment. To simulate browser behavior, these tests are executed using JSDOM.

Here’s a sample Jest configuration file for client-side testing:

jest.ssr-client.config.js
1module.exports = {
2    displayName: 'SSR with hydration',
3    preset: '@lwc/jest-preset/ssr-for-hydration',
4    setupFilesAfterEnv: ['./jest.ssr-client.setupAfterEnv.js'],
5    testMatch: ['**/*.ssr-client.(spec|test).(js|ts)'],
6    transformIgnorePatterns: ['node_modules/(?!(@webcomponents/.+)/)'],
7};

To track hydration errors, monitor the console.warn event.

jest.ssr-client.setupAfterEnv.js
1let hydrationMismatchOccurred = false;
2let hydrationMismatchMessage = '';
3
4beforeEach(() => {
5    // Reset the flag and message before each test
6    hydrationMismatchOccurred = false;
7    hydrationMismatchMessage = '';
8
9    // Spy on console.warn and intercept warnings
10    jest.spyOn(console, 'warn').mockImplementation((message) => {
11        if (message.includes('Hydration mismatch')) {
12            // Set the flag to indicate a hydration mismatch occurred
13            hydrationMismatchOccurred = true;
14            // Store the hydration mismatch message
15            hydrationMismatchMessage = message;
16        } else {
17            // If it's not a hydration mismatch, call the original console.warn
18            console.warn(message);
19        }
20    });
21});
22
23afterEach(() => {
24    // Restore original console.warn after each test
25    jest.restoreAllMocks();
26
27    // Check if a hydration mismatch occurred and fail the test if so
28    if (hydrationMismatchOccurred) {
29        throw new Error(`Test failed due to hydration mismatch: ${hydrationMismatchMessage}`);
30    }
31});

To run a client-side test, add a script to your package.json. You can alternatively use jest --selectProjects <project_name> to target specific test suites.

package.json
1"scripts": {
2    "test:ssr:client": "jest --no-cache --projects=./example-ssr/jest.ssr-client.config.js"
3}

Client-side tests use server-side snapshots to validate your components’ post-hydration behavior, like this:

  1. Read server-side-generated snapshots.
  2. Insert the pre-rendered markup into the DOM.
  3. Hydrate the component and validate its behavior in the client environment.

By comparing pre- and post-hydration markup, SSR client-rendering testing helps identify potential performance issues early on. It helps you address inconsistencies, layout shifts and inefficiencies before they affect your overall user experience.

Next Steps 

Now that you’ve…

  • Configured your components for SSR
  • Aligned them with the SSR best practices
  • Evaluated them in SSR testing environments

…you can enable SSR for a page of your LWR site by following the instructions in Enable SSR for LWR Apps. If you have an Experience Cloud site, review Hydration Capabilities for Islands (Experience Cloud) instead.

Enabling SSR for an entire page is a prerequisite for configuring a single component island for SSR.

Developer Preview Feature

Feature is available as a developer preview. Feature is not generally available unless or until Salesforce announces its general availability in documentation or in press releases or public statements. All commands, parameters, and other features are subject to change or deprecation at any time, with or without notice. Do not implement functionality developed with these commands or tools.