We use Playwright with Stencil's Playwright integration for testing our web components. Each component has a dedicated test file that covers functional behavior, visual regression, and accessibility.
For implementation examples, consult the existing test files.
-
Install Playwright browsers:
npm run test:e2e-install
-
Run tests:
npm run test:e2e
-
Run tests for a specific component (from
libraries/ui-library):cd libraries/ui-library npx playwright test six-button
-
Run tests with UI for debugging (from
libraries/ui-library):npx playwright test --ui
::: warning Run Tests Faster
Run npm start first. Tests run faster when the dev server is already running because it is reused
across all tests instead of a new server being started for each test file.
:::
Every component test file has three test.describe blocks with clear separation of concerns:
import { test } from '../../test-utils/fixtures';
import { expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
// 1. Functional tests - behavior, events, keyboard interaction
test.describe('six-{component}', () => {
test('should show and hide via open prop', async ({ page }) => {
/* ... */
});
test('should emit events (focus, change, blur)', async ({ page }) => {
/* ... */
});
test('should handle keyboard navigation', async ({ page }) => {
/* ... */
});
});
// 2. Screenshot tests - visual regression
test.describe('six-{component} screenshots', () => {
test('should match screenshot for default', async ({ page }) => {
/* ... */
});
test('should match screenshot for disabled', async ({ page }) => {
/* ... */
});
});
// 3. Accessibility tests - ARIA attributes and axe-core
test.describe('six-{component} accessibility', () => {
test('should have correct ARIA attributes', async ({ page }) => {
/* ... */
});
test('should have no a11y violations', async ({ page }) => {
/* ... */
});
});::: info Why separate blocks?
A test failure clearly indicates whether it's a visual bug, a behavioral bug, or an accessibility issue. This separation makes tests more maintainable and easier to debug.
:::
- Behavior: State transitions, show/hide logic, error handling
- Disabled state: Verify
click({ force: true })doesn't change state - Events: Test focus, change, blur in one realistic flow using
spyOnEvent() - Keyboard navigation: Follow ARIA APG patterns
- Programmatic changes: Setting values via JavaScript
See six-checkbox.e2e.ts for a complete example.
Capture visual states that functional tests cannot verify:
- Default and active states
- Disabled states
- All variants (types, sizes)
- Focus and hover states (for focusable components)
::: warning
Always visually verify screenshots after generating them. Open the PNG files and check that content is fully visible and not cropped.
:::
- ARIA attributes: role, aria-hidden, aria-checked, aria-label, etc.
- axe-core validation: Use
AxeBuilderto check WCAG compliance
Document any disabled axe rules with TODO comments explaining why.
- Use
page.locator('six-component')for interactions (clicks the host element) - Use
page.getByRole()for assertions (queries the accessibility tree)
Use spyOnEvent() to test custom events:
const changeSpy = await page.spyOnEvent('six-checkbox-change');
await page.locator('six-checkbox').click();
expect(changeSpy).toHaveReceivedEvent();Use evaluate() to set web component properties:
await page.locator('six-input').evaluate((el) => {
el.value = 'test';
});The test fixture disables animations by default. Use { disableAnimations: false } when testing
after-show or after-hide events.
Components should follow the ARIA Authoring Practices Guide (APG):
six-alert→ Alertsix-checkbox→ Checkboxsix-dialog→ Dialog (Modal)six-menu→ Menusix-radio→ Radio Groupsix-switch→ Switchsix-tab-group→ Tabs
From the repository root:
npm run test:e2e # run all tests
npm run test:e2e-install # install Playwright browsers
npm run test:e2e-update-screenshots # update all screenshotsFrom libraries/ui-library for more options:
npx playwright test six-button # run specific component
npx playwright test six-button --update-snapshots # update component screenshots
npx playwright test --ui # run with UI debugger
npx playwright test --headed # run with visible browser