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
|
||||||
mise.local.toml
|
mise.local.toml
|
||||||
|
|
||||||
|
# Project-local development caches and browser artifacts
|
||||||
|
/.cache/
|
||||||
|
/.artifacts/
|
||||||
|
|
||||||
# MyGO configuration
|
# MyGO configuration
|
||||||
# Tracking config.example.yaml in version control
|
# Tracking config.example.yaml in version control
|
||||||
config.yaml
|
config.yaml
|
||||||
|
|||||||
@@ -43,3 +43,29 @@ account, admin, or large-transfer designs.
|
|||||||
transfer policy in MyGO code.
|
transfer policy in MyGO code.
|
||||||
- Handwritten TypeScript wire types remain temporary until an OpenAPI contract
|
- Handwritten TypeScript wire types remain temporary until an OpenAPI contract
|
||||||
is available.
|
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
|
- Ant Design icons for application and file actions
|
||||||
- Oxlint from the Vite template
|
- Oxlint from the Vite template
|
||||||
- Vitest for framework-independent client and session tests
|
- 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.
|
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]
|
[tools]
|
||||||
go = "1.26.2"
|
go = "1.26.2"
|
||||||
node = "24"
|
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
|
serve the static build and `/api/v1` from the same origin, normally through a
|
||||||
reverse proxy.
|
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
|
## Checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mise exec -- npm run check
|
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
|
`docs/web/roadmap.md` at the repository root for architecture boundaries and
|
||||||
planned dependencies.
|
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"
|
"react-router": "^8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/mcp": "0.0.78",
|
||||||
|
"@playwright/test": "1.61.1",
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
"@types/node": "^24.13.2",
|
"@types/node": "^24.13.2",
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.2.17",
|
||||||
@@ -581,6 +583,86 @@
|
|||||||
"node": "^20.19.0 || >=22.12.0"
|
"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": {
|
"node_modules/@rc-component/async-validator": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-6.0.0.tgz",
|
"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"
|
"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": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.19",
|
"version": "8.5.19",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.19.tgz",
|
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.19.tgz",
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "oxlint",
|
"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": "vitest run",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
"typecheck": "tsc -b",
|
"typecheck": "tsc -b",
|
||||||
"check": "npm run lint && npm run test && npm run build",
|
"check": "npm run lint && npm run test && npm run build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
@@ -25,6 +29,8 @@
|
|||||||
"react-router": "^8.2.0"
|
"react-router": "^8.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/mcp": "0.0.78",
|
||||||
|
"@playwright/test": "1.61.1",
|
||||||
"@tailwindcss/vite": "^4.3.2",
|
"@tailwindcss/vite": "^4.3.2",
|
||||||
"@types/node": "^24.13.2",
|
"@types/node": "^24.13.2",
|
||||||
"@types/react": "^19.2.17",
|
"@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