Playwright
The @archibald/playwright package provides the Playwright integration for the archibald e2e command. See the Overview for the parts shared with Cypress.
Installation
From the root of your Archibald project run:
archibald add playwright
This installs @archibald/playwright and @playwright/test, scaffolds the config and support files, adds a CI environment, and sets cli.e2e.framework to "playwright" in archibald.json.
Layout
| Location | Contents |
|---|---|
playwright.config.ts | Project config (wraps Archibald defaults) |
playwright/support/fixtures.ts | The extended test/expect and the testUser fixture |
playwright/support/helpers/methods.ts | getTestUser(), selectors, features, helpers |
src/{platform}/e2e-playwright/specs | Test specifications (*.spec.ts) |
src/{platform}/e2e-playwright/pages | Page objects |
The spec/page folder is e2e-playwright when Cypress is the configured runner (the default). When Playwright is the configured runner (cli.e2e.framework: "playwright") it owns the base e2e folder instead — see Where tests live for the full convention.
Environment/selector/feature configuration is shared with Cypress and read from cypress/config (see the Overview).
Configuration
playwright.config.ts uses defineConfig from @archibald/playwright, which deep-merges your config over Archibald's defaults (the chrome/firefox/webkit projects, a CI-friendly reporter, tracing, etc.):
import { defineConfig } from '@archibald/playwright';
export default defineConfig({
fullyParallel: true,
use: {
baseURL: 'http://localhost:3100'
}
});
defineConfig also applies the merged environment on top of your config, so baseURL, viewport and userAgent follow the selected environment/tenant (e.g. a tenant baseUrl of http://localhost:3100/de). You do not need a webServer block — the dev server is started by archibald e2e (see the Overview).
Spec discovery and shadowing
defineConfig resolves testDir/testMatch from archibald.json rather than scanning src blindly — see Spec discovery and shadowing in the Overview for the rules, which are shared with Cypress.
Setting testDir or testMatch yourself in playwright.config.ts overrides the resolved set and opts out of shadowing.
Test user and fixtures
Specs import the extended test/expect from support/fixtures, which adds the testUser fixture on top of the package fixtures:
import { test, expect } from 'support/fixtures';
import LoginPage from 'pages/LoginPage';
test.describe('Cart', () => {
test.beforeEach(async ({ page, login, testUser }) => {
await testUser.reset(); // seed a clean account
await new LoginPage(page).visit();
await login(testUser.email, testUser.password);
});
test('...', async ({ page }) => {
/* ... */
});
});
Available fixtures:
| Fixture | From | Description |
|---|---|---|
testUser | support/fixtures (template) | Isolated account: email, password, reset, addCart, deleteCart, addOrder, deleteOrder |
login | @archibald/playwright/support | Logs in via the auth API and sets the session cookies |
fillForm | @archibald/playwright/support | Fills a form (input/select/textarea, incl. checkbox/radio) |
waitUntilSettled | @archibald/playwright/support | Waits until the DOM stops mutating |
waitForHydration | @archibald/playwright/support | Waits for the hydration island containing a selector to finish hydrating |
getTestUser() (from support/helpers/methods) returns only the identity ({ email, password }) and is what LoginPage.login() defaults to; the mutating operations live on the testUser fixture because they need the request context.
The package root (@archibald/playwright) re-exports everything from @playwright/test, plus defineConfig and getArchibaldConfig. The standalone waitUntilSettled(page) and waitForHydration(page, selector) helpers are exported from the @archibald/playwright/support subpath — the same subpath the Cypress integration uses (@archibald/cypress/support), so the shared hydration helper lives at a matching import path on both runners.
Waiting for hydration
Archibald hydrates CMS components (and the app root) as lazy islands marked with data-arc-hydrated, flipped to "true" once hydrated. Interacting before that — especially typing, which the event-replay feature does not replay — is silently lost. waitForHydration(page, selector) (Cypress: cy.waitForHydration(selector)) scrolls the target into view and waits for its closest island to hydrate, then returns the element. Use it before typing into a lazily-hydrated form (e.g. the review form) or after a full-page navigation such as the OIDC login round-trip (waitForHydration(page, '#app')).
Parallel isolation
Playwright runs specs in parallel. To keep the shared backend account from clashing across workers, getTestUser() derives a per-worker account by plus-tagging the email with the worker's parallel index (erika.musterfrau+0@…, +1@…, using withParallelTag from @archibald/e2e). Each worker reset()s and mutates only its own account, so fullyParallel: true is safe.
The password stays the shared base-account password (the mock clones each +N account from the canonical one), while the email is unique per worker.
Running
archibald e2e # interactive
archibald e2e -e local -b chrome -h # headless chrome, local env
The browser is selected with -b and mapped to the matching project (chrome, firefox, webkit). The Cypress-only electron value is mapped to chrome with a warning.
Without -h (headless), Playwright opens in --ui mode.
Gitlab CI
archibald add playwright scaffolds a CI environment. In CI the app is served externally (a Docker service reached via the ci environment's baseUrl), so archibald e2e does not start a local server. Enable the docker build in archibald.json and add the project's container registry, as for Cypress.
Further documentation
See the Playwright documentation.