Shared Playwright end-to-end base tests for Abovo websites. One place to fix a smoke test that every site runs.
Playwright neither transpiles TypeScript nor discovers spec files inside
node_modules. A package therefore cannot ship tests that the runner picks up
on its own. Instead it exports functions that register tests, and each
project keeps a tiny spec file that calls the ones it wants.
In a project's tests/e2e/package.json:
{
"private": true,
"dependencies": {
"@abovomaxlead/playwright-am-e2e": "git+https://github.com/abovowebdevelopment/playwright-am-e2e#semver:^1.0.0"
},
"allowScripts": {
"github:abovowebdevelopment/playwright-am-e2e": true
}
}Keep @playwright/test pinned exactly to the version in the
am-dev-playwright image tag: bin/e2e compares the two and warns on drift,
and an exact pin is also what stops npm update from moving Playwright.
Tags must be three-part (v1.0.0). #semver: matches only valid semver tags —
a two-part tag such as 1.0 matches nothing and silently falls back to main.
tests/e2e/base.spec.ts:
import { testHomepageLoads } from '@abovomaxlead/playwright-am-e2e/global';
import { testThemeAssetsLoad } from '@abovomaxlead/playwright-am-e2e/wordpress/abovo-basis';
testHomepageLoads();
testThemeAssetsLoad();tests/e2e/playwright.config.ts:
import { defineE2EConfig } from '@abovomaxlead/playwright-am-e2e/config';
export default defineE2EConfig();Project-specific tests import the harness rather than @playwright/test, which
gets them the screenshot-path fixture:
import { expect, test } from '@abovomaxlead/playwright-am-e2e';| Specifier | Contents |
|---|---|
@abovomaxlead/playwright-am-e2e |
test, expect, and the Page, Response, Locator, BrowserContext types |
@abovomaxlead/playwright-am-e2e/config |
defineE2EConfig() |
@abovomaxlead/playwright-am-e2e/global |
testHomepageLoads() — platform-agnostic |
@abovomaxlead/playwright-am-e2e/wordpress/abovo-basis |
testThemeAssetsLoad(), ABOVO_BASIS_THEME_ASSETS |
@abovomaxlead/playwright-am-e2e/summary-reporter |
SummaryReporter — wired in by defineE2EConfig() automatically; only needed as a specifier because Playwright resolves reporters by module path |
Anything not listed is internal and may change in a patch release.
Every run gets its own directory: test-results/<runId>/, where runId is
YYYYMMDD-HHMMSS (UTC) — lexicographic sort equals chronological sort. Layout:
test-results/
20260817-104213/
artifacts/ <- Playwright's outputDir (traces, videos, per-test dirs)
html-report/ <- HTML reporter (a sibling of artifacts/, not nested in
it — Playwright refuses an HTML report folder inside
outputDir)
screenshots/ <- the screenshot fixture's relative-path target
results.json <- Playwright's JSON reporter output
summary.json <- compact machine-readable summary (see below)
20260817-102960/
...
bin/e2e (in dev-infra) generates the run id and passes it as E2E_RUN_ID.
For standalone use (npx playwright test without the wrapper), defineE2EConfig()
generates one itself and writes it into process.env at config load, in the
main process, before Playwright forks any workers — every worker inherits it,
so a single run never scatters across several directories. See
src/internal/run-id.ts for the reasoning and how it was verified against
2+ workers.
Retention. Old run directories are pruned automatically on every run,
keeping the newest E2E_KEEP_RUNS (default 3) by name — not by age. Only
direct children of test-results/ whose name matches the run-id pattern
exactly are ever considered, symlinks are never followed, and the current
run is never deleted. See src/internal/retention.ts.
summary.json contents: pass/fail/skip counts, total duration in
milliseconds, the base URL, the environment tag, the run's start timestamp,
and the Playwright version.
Screenshot sandbox. page.screenshot({ path }) (via this package's test
fixture) is sandboxed into the run directory:
- a relative path lands in
<run>/screenshots/<path> - a path with a leading slash is anchored at the run directory's root:
/sub/b.jpg-><run>/sub/b.jpg - a path that would escape the run directory (e.g. via
..) throws, rather than writing outside it
This is a behaviour change from 1.0.x, where an absolute path was left alone and written wherever it pointed — see the CHANGELOG.
Every registrar takes one optional options object, and every one accepts
tags (default ['@all']). Tags go through Playwright's tag option, so
bin/e2e's --grep "@<env>|@all" selects them without cluttering titles.
testHomepageLoads({ path: './nl/', tags: ['@dev', '@staging'] });
testThemeAssetsLoad({
assets: [
...ABOVO_BASIS_THEME_ASSETS,
{
label: 'critical css',
filename: 'critical.css',
domSelector: 'link[rel="stylesheet"][href]',
urlAttribute: 'href',
},
],
});Navigate with relative paths only — './', './contact', never '/contact'.
Playwright resolves goto() against baseURL with the URL() constructor, so
a leading slash escapes a path-prefixed base URL such as https://site/fr/.
Playwright attributes a test's location to the file where test() was called,
which for these suites is the package's compiled code — not your
base.spec.ts. So a filename filter does not match them:
e2e -- base.spec.ts # no tests found
e2e -- --grep "homepage" # works — filter by title or tagYour own spec files are unaffected: their tests are registered in your file, so
they filter by filename normally. bin/e2e's own --grep "@<env>|@all"
selection works on both, because tags and titles are matched, not paths.
bin/e2e runs npm update before every test run, so a new in-range tag is
picked up automatically with no action in the project. A project that must not
move pins an exact ref instead — #v1.2.3 — which npm update leaves alone.
The repo has no node service of its own; run everything in the shared Playwright container:
pw() { docker exec -i --user "$(id -u):$(id -g)" -e HOME="$HOME" \
-w /home/developer/projects/playwright-am-e2e am-dev-playwright "$@"; }
pw npm install
pw npm run typecheck
pw env E2E_BASE_URL=https://dentalclinics-nl.abovodevsites.nl/ npm testThe package's own tests import from dist/, so they exercise the artifact that
ships rather than the sources. npm test builds first.
SemVer is the contract every site depends on:
- patch — a base test fixed
- minor — a suite or option added
- major — a registrar renamed or removed, or its default behaviour changed
A bad tag reaches every site on its next e2e run, so:
pw env E2E_BASE_URL=<a real abovo-basis dev site> npm test— must pass.pw npm run test:registration— must list the base tests.pw npm run verify:exports— must pass. None of the other gates resolve a specifier the way a consuming project'snode_modulesinstall does, so this is the only thing standing between a rename insrc/and every site failing at collection withERR_PACKAGE_PATH_NOT_EXPORTED.pw npm run typecheckandpw npm run typecheck:tests— must both pass.pw npm pack --dry-run— must listdist/, must not listsrc/.- Bump
versioninpackage.json, add aCHANGELOG.mdentry, runpw npm install --package-lock-onlyso the lockfile's own version field stays in sync, commit. git tag vX.Y.Z && git push origin main --tags.