Skip to main content
Version: Next

End-to-End Testing

When it comes to writing end-to-end tests using the Stencil Playwright adapter, the best advice we can give is to leverage Playwright documentation. The adapter is set up in a way so that developers will use the same public APIs with as little Stencil nuances as possible.

Writing Tests

As far as writing the actual tests goes, the most important thing to be aware of for tests to run correctly is to import the test function from @stencil/playwright, not directly from @playwright/test:

// THIS IS CORRECT
import { test } from '@stencil/playwright';

// THIS IS NOT!!
// import { test } from '@playwright/test';

The adapter package extends Playwright's stock test function to provide additional fixtures and handle nuances related to web component hydration. More information is available in the API documentation.

Testing Patterns

page.goto()

The goto() method allows tests to load a pre-defined HTML template. This pattern is great if a test file has many tests to execute that all use the same HTML code or if additional script or style tags need to be included in the HTML. However, with this pattern, developers are responsible for defining the necessary script tags pointing to the Stencil entry code (so all web components are correctly loaded and registered).

For the following snippets, we'll imagine this is the simplified file structure for a Stencil project:

stencil-project-root
└── src
└── components
├── my.component.tsx
└── test <-- directory containing test files
├── my-component.e2e.ts
└── my-component.e2e.html
stencil.config.ts
export const config: Config = {
namespace: 'test-app',
outputTargets: [
{
type: 'www',
serviceWorker: null,
copy: [{ src: '**/test/*.html' }, { src: '**/test/*.css' }],
},
],
};
my-component.e2e.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf8" />

<!-- Replace with the path to your entrypoint -->
<script src="../../../build/test-app.esm.js" type="module"></script>
<script src="../../../build/test-app.js" nomodule></script>
</head>
<body>
<my-component first="Stencil"></my-component>
</body>
</html>

In the above snippet, where it says "replace with the path to your entrypoint", the src attributes for the script tags should be the relative path from the www output target's output directory (dir option) to the namespaced entry file. The entry file will have the name <namespace>.esm.js for ESM output and <namespace>.js for CJS output. The "namespace" value is the kebab-case value of the namespace from your Stencil config.

my-component.e2e.ts
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';

test.describe('my-component', () => {
test('should render the correct name', async ({ page }) => {
// The path here is the path to the www output relative to the dev server root directory
await page.goto('/components/my-component/test/my-component.e2e.html');

// Rest of test
const component = await page.locator('my-component');
await expect(component).toHaveText(`Hello World! I’m Stencil`);
});
});

In the above snippet, the path string passed to page.goto() should be the absolute path to the HTML file from the dev server's served directory.

page.setContent()

The setContent() method allows tests to define their own HTML code on a test-by-test basis. This pattern is helpful if the HTML for a test is small or to avoid affecting other tests using the page.goto() pattern and modifying a shared HTML template file. With this pattern, the script tags pointing to Stencil entry code will be automatically injected into the generated HTML.

my-component.e2e.ts
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';

test.describe('my-component', () => {
test('should render the correct name', async ({ page }) => {
await page.setContent('<my-component first="Stencil"></my-component>');

// Rest of test
});
});

page.waitForChanges()

The waitForChanges() method is a utility for waiting for Stencil components to rehydrate after an operation that results in a re-render:

my-component.e2e.ts
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';

test.describe('my-component', () => {
test('should render the correct name', async ({ page }) => {
// Assume we have a template setup with the `my-component` component and a `button`
await page.goto('/my-component/my-component.e2e.html');

const button = page.locator('button');
// Assume clicking the button changes the `@Prop()` values on `my-component`
button.click();
await page.waitForChanges();
});
});

Running Tests

To run tests, either run npx playwright test from the root of the Stencil project, or update the project's test script in the package.json file to run the Playwright command.

By default, the adapter will execute all tests in a project with a .e2e.ts file suffix. This can be modified by passing the testDir and/or testMatch configuration options as overrides to createConfig().

Debugging

Playwright offers several strategies for debugging e2e tests. See their debugging documentation for more information of the tools available.

Tests can also be launched in a headed mode. This is disabled by default, but can be enabled in the Playwright config or with the --headed CLI flag. Running tests in a headed mode will open a browser tab for each test.

Screenshot Testing

Playwright also has support for screenshot (or visual) testing. This functionality is available out of the box. Read the Playwright docs on screenshots and performing visual comparisons for more information on using these APIs.