diff --git a/.gitignore b/.gitignore
index 90dd227..7f6c135 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@ tmp/
*.log
*.sql
*.sql.gz
+test-results/
+playwright-report/
# Keep source Excel files local unless we explicitly decide otherwise.
*.xlsm
diff --git a/agent.md b/agent.md
index 31cbaa1..18a577f 100644
--- a/agent.md
+++ b/agent.md
@@ -21,6 +21,14 @@ This project replaces the Excel/VBA workflow named `Catalog maker - 20` with a N
- Before the first push, initialize Git if needed, configure the project remote and create an initial baseline commit from safe files only.
- If a push fails, do not expose credentials in the terminal output or in chat; report the failure without printing secrets.
+## UI inspection workflow
+
+- Use Playwright for browser inspections and visual verification of the local application.
+- Run `npm run test:ui` after frontend or layout changes.
+- Check for page errors, visible controls, modal behavior and important responsive states.
+- Keep screenshots and traces in Playwright's ignored `test-results/` and `playwright-report/` directories; do not commit them.
+- A UI change is not complete until the relevant Playwright checks pass.
+
## Runtime
- Start the web app with `npm run dev`.
diff --git a/package-lock.json b/package-lock.json
index 18ee201..ed0dbc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,9 @@
"mariadb": "^3.5.3",
"selenium-webdriver": "^4.46.0"
},
+ "devDependencies": {
+ "playwright": "^1.62.1"
+ },
"engines": {
"node": ">=20"
}
@@ -141,6 +144,21 @@
"node": ">=0.10"
}
},
+ "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/geckodriver": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.1.tgz",
@@ -308,6 +326,38 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
+ "node_modules/playwright": {
+ "version": "1.62.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
+ "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.62.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.62.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
+ "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
diff --git a/package.json b/package.json
index a0cab58..e98a236 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
"start": "node src/cli.js",
"dev": "node src/server.js",
"catalog:dry-run": "node src/cli.js catalog-maker --dry-run",
- "test": "node --test"
+ "test": "node --test",
+ "test:ui": "playwright test",
+ "test:e2e": "playwright test tests/ui/database.e2e.spec.js"
},
"engines": {
"node": ">=20"
@@ -17,5 +19,8 @@
"geckodriver": "^6.1.1",
"mariadb": "^3.5.3",
"selenium-webdriver": "^4.46.0"
+ },
+ "devDependencies": {
+ "playwright": "^1.62.1"
}
}
diff --git a/playwright.config.js b/playwright.config.js
new file mode 100644
index 0000000..ee68ef3
--- /dev/null
+++ b/playwright.config.js
@@ -0,0 +1,26 @@
+import { defineConfig, devices } from "playwright/test";
+
+export default defineConfig({
+ testDir: "./tests/ui",
+ timeout: 30_000,
+ expect: {
+ timeout: 5_000,
+ },
+ fullyParallel: true,
+ forbidOnly: Boolean(process.env.CI),
+ retries: process.env.CI ? 2 : 0,
+ reporter: [["list"], ["html", { open: "never" }]],
+ use: {
+ baseURL: "http://127.0.0.1:3404",
+ trace: "retain-on-failure",
+ screenshot: "only-on-failure",
+ video: "retain-on-failure",
+ ...devices["Desktop Chrome"],
+ },
+ webServer: {
+ command: "node src/server.js",
+ url: "http://127.0.0.1:3404/api/status",
+ reuseExistingServer: true,
+ timeout: 30_000,
+ },
+});
diff --git a/public/index.html b/public/index.html
index e1fbbe7..4fa54af 100644
--- a/public/index.html
+++ b/public/index.html
@@ -131,7 +131,7 @@