chore: add github test actions
Some checks are pending
Tests / Unit (happy-dom) (push) Waiting to run
Tests / E2E (Chromium + Firefox + WebKit) (push) Waiting to run
Tests / Size limit (push) Waiting to run

This commit is contained in:
ayo 2026-07-05 16:10:35 +02:00
parent ef20daa140
commit 6af7598973
9 changed files with 271 additions and 88 deletions

67
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,67 @@
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
# Cancel superseded runs on the same ref (e.g. new pushes to a PR).
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
unit:
name: Unit (happy-dom)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
e2e:
name: E2E (Chromium + Firefox + WebKit)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
# Downloads the browser binaries and their OS libraries (Firefox/WebKit
# need system deps that `--with-deps` installs via apt on the runner).
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium firefox webkit
- run: pnpm test:e2e:all
size:
name: Size limit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: 11
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
# `pnpm size-limit` builds `dist/` (tsc + esbuild) and checks each entry
# against the byte budgets declared in package.json `size-limit`.
- run: pnpm size-limit

View file

@ -1,4 +1,5 @@
npm run lint --cache
npm run test:all
npm run build
npx size-limit
# Lean: lint + format staged files, then the fast unit suite.
# Heavier checks (browser e2e, size-limit) run on pre-push; the full
# cross-browser matrix runs in CI.
pnpm exec lint-staged
pnpm test

4
.husky/pre-push Executable file
View file

@ -0,0 +1,4 @@
# Heavier gate before code leaves the machine: browser e2e (Chromium only —
# Firefox/WebKit + their system deps are exercised in CI) and the size budget.
pnpm test:e2e
pnpm size-limit

View file

@ -17,7 +17,7 @@ This file provides guidance to AI coding agents when working with code in this r
- `pnpm size-limit` — enforces the byte budgets declared in `package.json` `size-limit` (e.g. `WebComponent.js` ≤ 1.2 KB). Keep additions tiny; this gate is a core project value.
- `pnpm docs` — run the Astro docs site (`docs/` workspace) locally.
- `pnpm demo` — run the Vite examples showcase (`demo/` workspace) locally; `pnpm demo:build` builds it.
- `pnpm test:e2e` — run the browser e2e specs (`test/e2e/`) in Chromium via Vitest browser mode; `pnpm test:all` runs unit + e2e.
- `pnpm test:e2e` — run the browser e2e specs (`test/e2e/`) via Vitest browser mode (Playwright). Defaults to Chromium; `pnpm test:e2e:firefox` / `:webkit` / `:all` target the other engines (the `E2E_BROWSERS` env var, comma-separated, selects instances). `pnpm test:all` runs unit + default e2e.
pnpm is mandatory (a `preinstall` `only-allow pnpm` guard enforces it). This is a pnpm workspace with two sub-packages: `docs/` (Astro docs site) and `demo/` (Vite examples showcase, which consumes the root `web-component-base` package as a `workspace:*` dependency). The runnable example sources live in `demo/examples/` and import the package by name (`web-component-base`), not via relative `src/` paths.

View file

@ -12,6 +12,12 @@ export default [
'no-unused-vars': 'warn',
},
},
// Config, build, and script files run in Node — give them Node globals
// (e.g. `process`) on top of the browser defaults.
{
files: ['**/*.config.{js,mjs,cjs}', 'scripts/**/*.{js,mjs}'],
languageOptions: { globals: globals.node },
},
{
ignores: ['site/*', '**/dist/*'],
},

View file

@ -30,8 +30,12 @@
"dev": "pnpm -F demo dev",
"test": "vitest --run",
"test:watch": "vitest",
"test:e2e": "vitest --run --config vitest.e2e.config.mjs",
"test:all": "pnpm test && pnpm test:e2e",
"test:e2e": "E2E_BROWSERS=chromium vitest --run --config vitest.e2e.config.mjs",
"test:e2e:chromium": "E2E_BROWSERS=chromium vitest --run --config vitest.e2e.config.mjs",
"test:e2e:firefox": "E2E_BROWSERS=firefox vitest --run --config vitest.e2e.config.mjs",
"test:e2e:webkit": "E2E_BROWSERS=webkit vitest --run --config vitest.e2e.config.mjs",
"test:e2e:all": "vitest --run --config vitest.e2e.config.mjs",
"test:all": "pnpm test && pnpm test:e2e:all",
"demo": "pnpm -F demo dev",
"demo:build": "pnpm -F demo build",
"docs": "pnpm -F docs start",
@ -44,6 +48,13 @@
"lint": "eslint . --config eslint.config.mjs",
"prepare": "husky"
},
"lint-staged": {
"*.{js,mjs,cjs}": [
"eslint --config eslint.config.mjs --fix",
"prettier --write"
],
"*.{json,css,html,md,yml,yaml}": "prettier --write"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ayo-run/web-component-base.git"
@ -73,6 +84,7 @@
"globals": "^17.1.0",
"happy-dom": "^20.3.7",
"husky": "^9.1.7",
"lint-staged": "^17.0.8",
"netlify-cli": "^23.13.5",
"playwright": "^1.61.1",
"prettier": "^3.8.1",

View file

@ -16,13 +16,13 @@ importers:
version: 12.0.0(size-limit@12.0.0(jiti@2.6.1))
'@vitest/browser':
specifier: 4.0.18
version: 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)
version: 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)
'@vitest/browser-playwright':
specifier: 4.0.18
version: 4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)
version: 4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)
'@vitest/coverage-v8':
specifier: 4.0.18
version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)
version: 4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18))(vitest@4.0.18)
bumpp:
specifier: ^11.1.0
version: 11.1.0
@ -44,6 +44,9 @@ importers:
husky:
specifier: ^9.1.7
version: 9.1.7
lint-staged:
specifier: ^17.0.8
version: 17.0.8
netlify-cli:
specifier: ^23.13.5
version: 23.13.5(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(picomatch@4.0.3)(rollup@4.60.3)(supports-color@10.2.2)
@ -70,7 +73,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^4.0.18
version: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
version: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
demo:
dependencies:
@ -89,10 +92,10 @@ importers:
dependencies:
'@astrojs/starlight':
specifier: ^0.39.1
version: 0.39.1(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2))(typescript@5.9.3)
version: 0.39.1(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0))(typescript@5.9.3)
astro:
specifier: ^6.3.1
version: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2)
version: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0)
sharp:
specifier: ^0.34.5
version: 0.34.5
@ -2680,6 +2683,10 @@ packages:
resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==}
engines: {node: '>=18.20'}
cli-truncate@5.2.0:
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
engines: {node: '>=20'}
cli-width@3.0.0:
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
engines: {node: '>= 10'}
@ -3690,6 +3697,10 @@ packages:
resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
engines: {node: '>=18'}
get-east-asian-width@1.6.0:
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -4432,10 +4443,19 @@ packages:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lint-staged@17.0.8:
resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==}
engines: {node: '>=22.22.1'}
hasBin: true
listhen@1.9.0:
resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==}
hasBin: true
listr2@10.2.2:
resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==}
engines: {node: '>=22.13.0'}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@ -6027,6 +6047,10 @@ packages:
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
engines: {node: '>=18'}
slice-ansi@8.0.0:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@ -6146,6 +6170,10 @@ packages:
streamx@2.22.0:
resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
string-width@2.1.1:
resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==}
engines: {node: '>=4'}
@ -6166,6 +6194,10 @@ packages:
resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==}
engines: {node: '>=20'}
string-width@8.2.1:
resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
engines: {node: '>=20'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@ -6318,6 +6350,10 @@ packages:
resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==}
engines: {node: '>=18'}
tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
@ -6937,6 +6973,10 @@ packages:
resolution: {integrity: sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==}
engines: {node: '>=0.4.0'}
wrap-ansi@10.0.0:
resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==}
engines: {node: '>=20'}
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@ -7015,11 +7055,6 @@ packages:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
yaml@2.8.2:
resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
engines: {node: '>= 14.6'}
hasBin: true
yaml@2.9.0:
resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
engines: {node: '>= 14.6'}
@ -7113,12 +7148,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@astrojs/mdx@5.0.4(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2))':
'@astrojs/mdx@5.0.4(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0))':
dependencies:
'@astrojs/markdown-remark': 7.1.1
'@mdx-js/mdx': 3.1.1
acorn: 8.16.0
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2)
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0)
es-module-lexer: 2.1.0
estree-util-visit: 2.0.0
hast-util-to-html: 9.0.5
@ -7142,17 +7177,17 @@ snapshots:
stream-replace-string: 2.0.0
zod: 4.4.3
'@astrojs/starlight@0.39.1(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2))(typescript@5.9.3)':
'@astrojs/starlight@0.39.1(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0))(typescript@5.9.3)':
dependencies:
'@astrojs/markdown-remark': 7.1.1
'@astrojs/mdx': 5.0.4(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2))
'@astrojs/mdx': 5.0.4(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0))
'@astrojs/sitemap': 3.7.2
'@pagefind/default-ui': 1.5.2
'@types/hast': 3.0.4
'@types/js-yaml': 4.0.9
'@types/mdast': 4.0.4
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2)
astro-expressive-code: 0.42.0(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2))
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0)
astro-expressive-code: 0.42.0(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0))
bcp-47: 2.1.0
hast-util-from-html: 2.0.3
hast-util-select: 6.0.4
@ -7934,7 +7969,7 @@ snapshots:
minimatch: 9.0.5
read-pkg: 9.0.1
semver: 7.7.2
yaml: 2.8.2
yaml: 2.9.0
yargs: 17.7.2
'@netlify/build@35.5.10(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(picomatch@4.0.3)(rollup@4.60.3)':
@ -7988,7 +8023,7 @@ snapshots:
ts-node: 10.9.2(@types/node@25.0.10)(typescript@5.9.3)
typescript: 5.9.3
uuid: 11.1.0
yaml: 2.8.2
yaml: 2.9.0
yargs: 17.7.2
zod: 3.25.76
transitivePeerDependencies:
@ -8033,7 +8068,7 @@ snapshots:
read-package-up: 11.0.0
tomlify-j0.4: 3.0.0
validate-npm-package-name: 5.0.1
yaml: 2.8.2
yaml: 2.9.0
yargs: 17.7.2
zod: 4.3.6
@ -8602,7 +8637,7 @@ snapshots:
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
picomatch: 4.0.4
optionalDependencies:
rollup: 4.60.3
@ -9034,29 +9069,29 @@ snapshots:
- rollup
- supports-color
'@vitest/browser-playwright@4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)':
'@vitest/browser-playwright@4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)':
dependencies:
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))
playwright: 1.61.1
tinyrainbow: 3.0.3
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
transitivePeerDependencies:
- bufferutil
- msw
- utf-8-validate
- vite
'@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)':
'@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)':
dependencies:
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))
'@vitest/utils': 4.0.18
magic-string: 0.30.21
pixelmatch: 7.1.0
pngjs: 7.0.0
sirv: 3.0.2
tinyrainbow: 3.0.3
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
ws: 8.19.0
transitivePeerDependencies:
- bufferutil
@ -9064,7 +9099,7 @@ snapshots:
- utf-8-validate
- vite
'@vitest/coverage-v8@4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18))(vitest@4.0.18)':
'@vitest/coverage-v8@4.0.18(@vitest/browser@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18))(vitest@4.0.18)':
dependencies:
'@bcoe/v8-coverage': 1.0.2
'@vitest/utils': 4.0.18
@ -9076,9 +9111,9 @@ snapshots:
obug: 2.1.1
std-env: 3.10.0
tinyrainbow: 3.0.3
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vitest: 4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
optionalDependencies:
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)
'@vitest/browser': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)
'@vitest/expect@4.0.18':
dependencies:
@ -9089,13 +9124,13 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.0.3
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))':
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))':
dependencies:
'@vitest/spy': 4.0.18
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
'@vitest/pretty-format@4.0.18':
dependencies:
@ -9421,12 +9456,12 @@ snapshots:
astring@1.9.0: {}
astro-expressive-code@0.42.0(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2)):
astro-expressive-code@0.42.0(astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0)):
dependencies:
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2)
astro: 6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0)
rehype-expressive-code: 0.42.0
astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.8.2):
astro@6.3.1(@netlify/blobs@10.5.0)(@types/node@25.0.10)(db0@0.3.1)(ioredis@5.6.0)(jiti@2.6.1)(rollup@4.60.3)(terser@5.39.0)(yaml@2.9.0):
dependencies:
'@astrojs/compiler': 4.0.0
'@astrojs/internal-helpers': 0.9.0
@ -9478,8 +9513,8 @@ snapshots:
unist-util-visit: 5.1.0
unstorage: 1.17.5(@netlify/blobs@10.5.0)(db0@0.3.1)(ioredis@5.6.0)
vfile: 6.0.3
vite: 7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vitefu: 1.1.3(vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))
vite: 7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
vitefu: 1.1.3(vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))
xxhash-wasm: 1.1.0
yargs-parser: 22.0.0
zod: 4.4.3
@ -9839,6 +9874,11 @@ snapshots:
cli-spinners@3.4.0: {}
cli-truncate@5.2.0:
dependencies:
slice-ansi: 8.0.0
string-width: 8.2.1
cli-width@3.0.0: {}
cli-width@4.1.0: {}
@ -10923,6 +10963,8 @@ snapshots:
get-east-asian-width@1.4.0: {}
get-east-asian-width@1.6.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -11836,6 +11878,15 @@ snapshots:
lilconfig@3.1.3: {}
lint-staged@17.0.8:
dependencies:
listr2: 10.2.2
picomatch: 4.0.4
string-argv: 0.3.2
tinyexec: 1.2.4
optionalDependencies:
yaml: 2.9.0
listhen@1.9.0:
dependencies:
'@parcel/watcher': 2.5.6
@ -11857,6 +11908,14 @@ snapshots:
untun: 0.1.3
uqr: 0.1.2
listr2@10.2.2:
dependencies:
cli-truncate: 5.2.0
eventemitter3: 5.0.4
log-update: 6.1.0
rfdc: 1.4.1
wrap-ansi: 10.0.0
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@ -14073,6 +14132,11 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
slice-ansi@8.0.0:
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
smart-buffer@4.2.0: {}
smol-toml@1.6.1: {}
@ -14181,6 +14245,8 @@ snapshots:
optionalDependencies:
bare-events: 2.5.4
string-argv@0.3.2: {}
string-width@2.1.1:
dependencies:
is-fullwidth-code-point: 2.0.0
@ -14209,6 +14275,11 @@ snapshots:
get-east-asian-width: 1.4.0
strip-ansi: 7.1.2
string-width@8.2.1:
dependencies:
get-east-asian-width: 1.6.0
strip-ansi: 7.1.2
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@ -14367,6 +14438,8 @@ snapshots:
tinyexec@1.1.2: {}
tinyexec@1.2.4: {}
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@ -14695,11 +14768,11 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2):
vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0):
dependencies:
esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.6
rollup: 4.56.0
tinyglobby: 0.2.15
@ -14708,22 +14781,7 @@ snapshots:
fsevents: 2.3.3
jiti: 2.6.1
terser: 5.39.0
yaml: 2.8.2
vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2):
dependencies:
esbuild: 0.27.7
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.14
rollup: 4.60.3
tinyglobby: 0.2.16
optionalDependencies:
'@types/node': 25.0.10
fsevents: 2.3.3
jiti: 2.6.1
terser: 5.39.0
yaml: 2.8.2
yaml: 2.9.0
vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0):
dependencies:
@ -14740,14 +14798,14 @@ snapshots:
terser: 5.39.0
yaml: 2.9.0
vitefu@1.1.3(vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)):
vitefu@1.1.3(vite@7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)):
optionalDependencies:
vite: 7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vite: 7.3.3(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
vitest@4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2):
vitest@4.0.18(@opentelemetry/api@1.8.0)(@types/node@25.0.10)(@vitest/browser-playwright@4.0.18)(happy-dom@20.3.7)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0):
dependencies:
'@vitest/expect': 4.0.18
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))
'@vitest/pretty-format': 4.0.18
'@vitest/runner': 4.0.18
'@vitest/snapshot': 4.0.18
@ -14764,12 +14822,12 @@ snapshots:
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2)
vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@opentelemetry/api': 1.8.0
'@types/node': 25.0.10
'@vitest/browser-playwright': 4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.8.2))(vitest@4.0.18)
'@vitest/browser-playwright': 4.0.18(playwright@1.61.1)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(terser@5.39.0)(yaml@2.9.0))(vitest@4.0.18)
happy-dom: 20.3.7
transitivePeerDependencies:
- jiti
@ -14862,6 +14920,12 @@ snapshots:
wordwrap@0.0.3: {}
wrap-ansi@10.0.0:
dependencies:
ansi-styles: 6.2.3
string-width: 8.2.1
strip-ansi: 7.1.2
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
@ -14918,8 +14982,6 @@ snapshots:
yallist@5.0.0: {}
yaml@2.8.2: {}
yaml@2.9.0: {}
yargs-parser@21.1.1: {}

View file

@ -1,24 +1,45 @@
# End-to-end example tests
These specs load each `demo/examples/*` component into a **real browser** (Chromium
via Playwright) and drive it the way a user would. They exist because the v5
correctness work targets behaviors that happy-dom cannot reproduce — custom
element upgrade ordering, the constructor-attribute rule, empty-string/removed
attributes, and adopted stylesheets — so the unit suite alone can pass while a
real browser misbehaves.
These specs load each `demo/examples/*` component into a **real browser**
(Chromium, Firefox, and WebKit via Playwright) and drive it the way a user
would. They exist because the v5 correctness work targets behaviors that
happy-dom cannot reproduce — custom element upgrade ordering, the
constructor-attribute rule, empty-string/removed attributes, and adopted
stylesheets — so the unit suite alone can pass while a real browser misbehaves.
Running across engines also catches cross-browser differences.
## Running
```sh
pnpm test:e2e # browser specs only (this folder)
pnpm test:e2e # default: Chromium only (fast, universal)
pnpm test:e2e:chromium # Chromium
pnpm test:e2e:firefox # Firefox
pnpm test:e2e:webkit # WebKit (the engine behind Safari)
pnpm test:e2e:all # Chromium + Firefox + WebKit
pnpm test # fast unit suite (happy-dom), unchanged
pnpm test:all # both
pnpm test:all # unit + default e2e
```
`pnpm test:e2e` stays on Chromium so it works everywhere with minimal setup; use
`test:e2e:all` (or a CI matrix) for the full cross-browser run. Each script just
sets the `E2E_BROWSERS` env var (comma-separated), which the config reads.
Config lives in `vitest.e2e.config.mjs` (Vitest browser mode + the
`@vitest/browser-playwright` provider). It is kept separate from
`vitest.config.mjs` so `pnpm test` stays fast and needs no browser. The
Chromium binary is installed with `npx playwright install chromium`.
`@vitest/browser-playwright` provider), kept separate from `vitest.config.mjs`
so `pnpm test` stays fast and needs no browser.
### Browser setup
```sh
npx playwright install chromium firefox webkit # download the browsers
npx playwright install-deps # Linux only: system libraries
```
Firefox and especially WebKit need extra OS libraries on Linux (`libwoff1`,
etc.); `playwright install-deps` requires root. Without them those engines fail
to launch — which is why the default target is Chromium and CI installs the deps
before running `test:e2e:all`.
## Coverage

View file

@ -4,11 +4,21 @@ import { fileURLToPath } from 'node:url'
const src = (p) => fileURLToPath(new URL(`./src/${p}`, import.meta.url))
// End-to-end specs run against the real `src/` in a real browser (Chromium via
// Which engines to run the e2e suite against. Defaults to all three Playwright
// engines (Chromium, Firefox, and WebKit — the engine behind Safari). Override
// with e.g. `E2E_BROWSERS=chromium pnpm test:e2e` in environments that only
// have some browsers installed (Firefox/WebKit also need system libs — see
// `npx playwright install-deps`).
const BROWSERS = (process.env.E2E_BROWSERS || 'chromium,firefox,webkit')
.split(',')
.map((name) => name.trim())
.filter(Boolean)
// End-to-end specs run against the real `src/` in a real browser (via
// Playwright), so behaviors that happy-dom cannot reproduce — custom-element
// upgrade ordering, constructor attribute rules, adopted stylesheets — are
// actually exercised. Kept separate from the unit config so `pnpm test` stays
// fast and headless-browser-free.
// actually exercised across engines. Kept separate from the unit config so
// `pnpm test` stays fast and headless-browser-free.
export default defineConfig({
// The demo examples import the `web-component-base` package; resolve it to
// local source (mirrors demo/vite.config.js) so e2e tests exercise src.
@ -29,7 +39,7 @@ export default defineConfig({
provider: playwright(),
headless: true,
screenshotFailures: false,
instances: [{ browser: 'chromium' }],
instances: BROWSERS.map((browser) => ({ browser })),
},
},
})