build(web): add project-local Playwright tooling
- build: pin Playwright Test and MCP with repository-local npm, browser, and artifact paths plus sandboxed headless Chromium configuration. - test: add a browser smoke test for the login page while keeping Playwright isolated from Vitest. - docs: document Debian dependency setup, dual Chromium revisions, E2E commands, and project-scoped Codex MCP usage.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
[mcp_servers.playwright]
|
||||
command = "mise"
|
||||
args = [
|
||||
"exec",
|
||||
"--",
|
||||
"node",
|
||||
"node_modules/@playwright/mcp/cli.js",
|
||||
"--headless",
|
||||
"--browser",
|
||||
"chromium",
|
||||
"--sandbox",
|
||||
"--isolated",
|
||||
"--output-dir",
|
||||
"../.artifacts/playwright-mcp",
|
||||
"--output-max-size",
|
||||
"104857600",
|
||||
]
|
||||
cwd = "web"
|
||||
enabled = true
|
||||
required = false
|
||||
startup_timeout_sec = 30
|
||||
tool_timeout_sec = 120
|
||||
@@ -34,6 +34,10 @@ go.work.sum
|
||||
# Mise
|
||||
mise.local.toml
|
||||
|
||||
# Project-local development caches and browser artifacts
|
||||
/.cache/
|
||||
/.artifacts/
|
||||
|
||||
# MyGO configuration
|
||||
# Tracking config.example.yaml in version control
|
||||
config.yaml
|
||||
|
||||
@@ -43,3 +43,29 @@ account, admin, or large-transfer designs.
|
||||
transfer policy in MyGO code.
|
||||
- Handwritten TypeScript wire types remain temporary until an OpenAPI contract
|
||||
is available.
|
||||
|
||||
## 2026-07-14: Project-local Headless Browser Tooling
|
||||
|
||||
**Context**: Browser behavior needs deterministic E2E coverage and interactive
|
||||
agent debugging inside a long-lived headless Debian Incus container. Tooling
|
||||
should remain reproducible without placing browser binaries or generated
|
||||
artifacts in a developer's home directory.
|
||||
|
||||
**Decisions**:
|
||||
|
||||
| Area | Choice | Guidance |
|
||||
|------|--------|----------|
|
||||
| Regression tests | Playwright Test with bundled Chromium | Keep browser E2E tests separate from `npm run check` because browser installation is an explicit environment setup step. |
|
||||
| Agent debugging | Official Playwright MCP over project-scoped STDIO | Codex starts the locked local package from `.codex/config.toml`; do not use a global install, an `@latest` npx invocation, or a listening MCP service. |
|
||||
| Browser state | Headless, isolated, and sandboxed | Use bundled Chromium, discard the MCP profile after each session, and retain the Chromium sandbox supported by the Incus environment. |
|
||||
| Local resources | Repository `.cache/` and `.artifacts/` directories | `mise.toml` defines the portable npm and browser cache paths. Generated resources remain ignored by Git. |
|
||||
| Browser versions | Install both locked Playwright revisions | The stable test runner and current MCP package require different Chromium revisions; do not force either package onto an unsupported executable. |
|
||||
|
||||
**Consequences**:
|
||||
- Debian browser libraries are installed once in the Incus container root
|
||||
filesystem; npm packages, browsers, traces, screenshots, and MCP output stay
|
||||
project-scoped.
|
||||
- A fresh environment runs `npm ci`, the system dependency installer, and both
|
||||
browser install scripts before browser tests or MCP debugging.
|
||||
- Browser failures can retain traces, screenshots, and video under
|
||||
`.artifacts/playwright/` without adding generated files to source control.
|
||||
|
||||
@@ -36,6 +36,8 @@ The project foundation contains:
|
||||
- Ant Design icons for application and file actions
|
||||
- Oxlint from the Vite template
|
||||
- Vitest for framework-independent client and session tests
|
||||
- Playwright Test with a headless Chromium smoke test
|
||||
- Project-scoped Playwright MCP for interactive browser debugging
|
||||
|
||||
Ant Design owns reusable UI components and theme tokens. Tailwind CSS is initially limited to application layout, spacing, and responsive utilities.
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[tools]
|
||||
go = "1.26.2"
|
||||
node = "24"
|
||||
|
||||
[env]
|
||||
NPM_CONFIG_CACHE = "{{config_root}}/.cache/npm"
|
||||
PLAYWRIGHT_BROWSERS_PATH = "{{config_root}}/.cache/ms-playwright"
|
||||
|
||||
+37
-1
@@ -36,12 +36,48 @@ Vite proxies `/api` to `http://127.0.0.1:10086`. Production deployments must
|
||||
serve the static build and `/api/v1` from the same origin, normally through a
|
||||
reverse proxy.
|
||||
|
||||
## Browser Testing and Debugging
|
||||
|
||||
The repository uses Playwright Test for repeatable browser tests and the
|
||||
official Playwright MCP server for interactive browser debugging. Mise keeps
|
||||
the npm cache and Playwright browser binaries under the repository-level
|
||||
`.cache/` directory.
|
||||
|
||||
Install the Debian browser dependencies once per development container:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:install:deps
|
||||
```
|
||||
|
||||
Install the Chromium revisions used by Playwright Test and Playwright MCP:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run playwright:install
|
||||
mise exec -- npm run playwright:install:mcp
|
||||
```
|
||||
|
||||
The two commands are intentionally separate because the locked stable test
|
||||
runner and MCP package currently depend on different Playwright revisions.
|
||||
Both revisions are stored in `.cache/ms-playwright/` and are ignored by Git.
|
||||
|
||||
Run the headless browser tests with Chromium sandboxing enabled:
|
||||
|
||||
```bash
|
||||
mise exec -- npm run test:e2e
|
||||
```
|
||||
|
||||
Codex loads the project-scoped Playwright MCP server from
|
||||
`.codex/config.toml` after the repository is trusted and Codex is restarted.
|
||||
The MCP browser uses bundled Chromium in headless, isolated, sandboxed mode;
|
||||
its generated files are kept under `.artifacts/playwright-mcp/`.
|
||||
|
||||
## Checks
|
||||
|
||||
```bash
|
||||
mise exec -- npm run check
|
||||
```
|
||||
|
||||
The production build is emitted to `dist/` as static assets. See
|
||||
The production build is emitted to `dist/` as static assets. Browser E2E tests
|
||||
are run separately because they require the browser setup above. See
|
||||
`docs/web/roadmap.md` at the repository root for architecture boundaries and
|
||||
planned dependencies.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
test('renders the login page in a real browser', async ({ page }) => {
|
||||
const response = await page.goto('/login')
|
||||
|
||||
expect(response?.ok()).toBe(true)
|
||||
await expect(page.getByRole('heading', { name: 'Sign in to MyGO' })).toBeVisible()
|
||||
await expect(page.getByLabel('Email')).toBeVisible()
|
||||
await expect(page.getByLabel('Password')).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible()
|
||||
})
|
||||
Generated
+129
@@ -16,6 +16,8 @@
|
||||
"react-router": "^8.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/mcp": "0.0.78",
|
||||
"@playwright/test": "1.61.1",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/node": "^24.13.2",
|
||||
"@types/react": "^19.2.17",
|
||||
@@ -581,6 +583,86 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/mcp": {
|
||||
"version": "0.0.78",
|
||||
"resolved": "https://registry.npmmirror.com/@playwright/mcp/-/mcp-0.0.78.tgz",
|
||||
"integrity": "sha512-XLTUeA6mEN9sQ+hJ4dfG8EIkDbxS0K3Trc2RBkUJuf02TgE2FQRNTMtq/aJfhyRMINsRl/Ybc4sxcWLtFn4/TQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.62.0-alpha-1783623505000",
|
||||
"playwright-core": "1.62.0-alpha-1783623505000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright-mcp": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmmirror.com/@playwright/test/-/test-1.61.1.tgz",
|
||||
"integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/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/@playwright/test/node_modules/playwright": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.1.tgz",
|
||||
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright-core": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@rc-component/async-validator": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-6.0.0.tgz",
|
||||
@@ -2677,6 +2759,53 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.62.0-alpha-1783623505000",
|
||||
"resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.62.0-alpha-1783623505000.tgz",
|
||||
"integrity": "sha512-6KV9h4PP3hqu4NaGdxxcijWfYh9LJcFI/R2sP4TTC4I5cFo3oRawN0ETlW5MkE3cQEgKhhoj0KUNz4sfpCT0Tg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.62.0-alpha-1783623505000"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.62.0-alpha-1783623505000",
|
||||
"resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.62.0-alpha-1783623505000.tgz",
|
||||
"integrity": "sha512-CPJZdsA/KGT2QQlekiV6Wt+QlQrZHVSZ6oiNtOI/bYYOIVLM8jfKGWTM4zQiyd4UN+40Cq4cA6lxmZHZbtPvJQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/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/postcss": {
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.19.tgz",
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "oxlint",
|
||||
"playwright:install": "playwright install chromium",
|
||||
"playwright:install:deps": "playwright install-deps chromium",
|
||||
"playwright:install:mcp": "node node_modules/playwright/cli.js install chromium",
|
||||
"test": "vitest run",
|
||||
"test:e2e": "playwright test",
|
||||
"typecheck": "tsc -b",
|
||||
"check": "npm run lint && npm run test && npm run build",
|
||||
"preview": "vite preview"
|
||||
@@ -25,6 +29,8 @@
|
||||
"react-router": "^8.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/mcp": "0.0.78",
|
||||
"@playwright/test": "1.61.1",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@types/node": "^24.13.2",
|
||||
"@types/react": "^19.2.17",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const repositoryRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
outputDir: `${repositoryRoot}/.artifacts/playwright/test-results`,
|
||||
fullyParallel: true,
|
||||
forbidOnly: Boolean(process.env.CI),
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: [
|
||||
['list'],
|
||||
[
|
||||
'html',
|
||||
{
|
||||
open: 'never',
|
||||
outputFolder: `${repositoryRoot}/.artifacts/playwright/report`,
|
||||
},
|
||||
],
|
||||
],
|
||||
use: {
|
||||
baseURL: 'http://127.0.0.1:5173',
|
||||
launchOptions: {
|
||||
chromiumSandbox: true,
|
||||
},
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'retain-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'npm run dev -- --host 127.0.0.1',
|
||||
url: 'http://127.0.0.1:5173/login',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user