diff --git a/CLAUDE.md b/CLAUDE.md index aa9be8ec..f86effd0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,14 +15,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `npm run preview` - Preview production build **Testing:** -- `npm run test:unit` - Run unit tests with Vitest -- `npm run test:e2e` - Run end-to-end tests with Playwright -- `npm run test` - Run both unit and e2e tests - -**Testing Guidelines:** -- DO NOT run tests automatically (test:unit, test:e2e, test, etc.) -- The user prefers to run all tests manually -- Focus on implementing code changes and let the user handle testing +- `npm run test:unit` - Run unit tests with Vitest (preview provider, opens in user's real browser) +- `npm run test:headless` - Run unit tests with Vitest (playwright provider, headless background browser) +- `npm run test` - Run unit tests once (preview provider) + +First time setup for `test:headless`: run `npx playwright install chromium` once after `npm install` to download the playwright chromium binary (~90 MB). + +**Testing guidelines for AI agents:** +- ALWAYS use `npm run test:headless` when running tests. It runs in a background browser — no tabs in the user's real browser, no focus stealing, safe to invoke repeatedly. +- Do NOT use `npm test` or `npm run test:unit`. Both use the preview provider, which opens a new tab in the user's real browser on every invocation and does not close it. Multiple runs accumulate tabs and steal focus. +- Do not run tests in tight loops to "characterize flakiness." If a test fails once and passes when re-run alone, that's the test harness, not the code. +- The user generally prefers to run tests themselves. Default to making code changes and letting them verify. **Implementation Guidelines:** - Do exactly what the user asks for - one step at a time diff --git a/package-lock.json b/package-lock.json index 57233ac7..8e026450 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,12 +19,14 @@ "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "@vitest/browser": "^4.1.0", + "@vitest/browser-playwright": "4.1.5", "@vitest/browser-preview": "^4.1.0", "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.15.1", "globals": "^17.4.0", "nanoid": "^5.1.6", + "playwright": "^1.60.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.5.1", "publint": "^0.3.18", @@ -32,7 +34,7 @@ "svelte-check": "^4.4.5", "typescript": "^5.9.3", "vite": "^8.0.0", - "vitest": "^4.1.0", + "vitest": "4.1.5", "vitest-browser-svelte": "^2.1.0" }, "peerDependencies": { @@ -81,29 +83,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -713,6 +692,7 @@ "integrity": "sha512-kT9GCN8yJTkCK1W+Gi/bvGooWAM7y7WXP+yd+rf6QOIjyoK1ERPrMwSufXJUNu2pMWIqruhFvmz+LbOqsEmKmA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -778,6 +758,7 @@ "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", @@ -798,6 +779,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -840,9 +822,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -909,6 +891,7 @@ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -955,6 +938,7 @@ "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", @@ -1176,12 +1160,38 @@ "vitest": "4.1.5" } }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.5.tgz", + "integrity": "sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/browser": "4.1.5", + "@vitest/mocker": "4.1.5", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.5" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, "node_modules/@vitest/browser-preview": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@vitest/browser-preview/-/browser-preview-4.1.5.tgz", "integrity": "sha512-UnUeV6/ykOOmrHjtQkOj01p+DZbJD18pNopACcEwt6NY1naL6L+caR+phpzTB6Mmgr5NL+2LDyrITGeTEmM9fQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@testing-library/dom": "^10.4.1", "@testing-library/user-event": "^14.6.1", @@ -1313,6 +1323,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1606,6 +1617,7 @@ "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2647,6 +2659,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2654,6 +2667,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pngjs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", @@ -2684,6 +2744,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2836,6 +2897,7 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3074,6 +3136,7 @@ "integrity": "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3348,6 +3411,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3386,6 +3450,7 @@ "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -3484,6 +3549,7 @@ "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.5", "@vitest/mocker": "4.1.5", diff --git a/package.json b/package.json index e6d208fd..9d8e97e9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "format": "prettier --write .", "lint": "prettier --check . && eslint .", "test:unit": "vitest", - "test": "npm run test:unit -- --run" + "test": "npm run test:unit -- --run", + "test:headless": "vitest --config vitest.headless.config.js --run" }, "files": [ "dist", @@ -49,12 +50,14 @@ "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "@vitest/browser": "^4.1.0", + "@vitest/browser-playwright": "4.1.5", "@vitest/browser-preview": "^4.1.0", "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.15.1", "globals": "^17.4.0", "nanoid": "^5.1.6", + "playwright": "^1.60.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.5.1", "publint": "^0.3.18", @@ -62,7 +65,7 @@ "svelte-check": "^4.4.5", "typescript": "^5.9.3", "vite": "^8.0.0", - "vitest": "^4.1.0", + "vitest": "4.1.5", "vitest-browser-svelte": "^2.1.0" } } diff --git a/vitest.headless.config.js b/vitest.headless.config.js new file mode 100644 index 00000000..d5464992 --- /dev/null +++ b/vitest.headless.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; +import { sveltekit } from '@sveltejs/kit/vite'; + +// Headless playwright config. Used by `npm run test:headless`, which is +// what AI agents should call when running the test suite — runs in a +// background browser, no tabs in the user's real browser, no focus +// stealing. Humans keep using `npm test` (preview provider in their +// real browser). +export default defineConfig({ + plugins: [sveltekit()], + test: { + browser: { + provider: playwright(), + enabled: true, + headless: true, + instances: [{ browser: 'chromium' }] + } + }, + clearMocks: true, + include: ['src/**/*.svelte.{test,spec}.{js,ts}'], + exclude: ['src/lib/server/**'] +});