Add Playwright database E2E inspections

This commit is contained in:
2026-08-05 12:45:03 +02:00
parent d7d5c0f912
commit 4c0c53d6e9
8 changed files with 145 additions and 2 deletions
+2
View File
@@ -9,6 +9,8 @@ tmp/
*.log *.log
*.sql *.sql
*.sql.gz *.sql.gz
test-results/
playwright-report/
# Keep source Excel files local unless we explicitly decide otherwise. # Keep source Excel files local unless we explicitly decide otherwise.
*.xlsm *.xlsm
+8
View File
@@ -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. - 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. - 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 ## Runtime
- Start the web app with `npm run dev`. - Start the web app with `npm run dev`.
+50
View File
@@ -12,6 +12,9 @@
"mariadb": "^3.5.3", "mariadb": "^3.5.3",
"selenium-webdriver": "^4.46.0" "selenium-webdriver": "^4.46.0"
}, },
"devDependencies": {
"playwright": "^1.62.1"
},
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
@@ -141,6 +144,21 @@
"node": ">=0.10" "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": { "node_modules/geckodriver": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.1.tgz", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-6.1.1.tgz",
@@ -308,6 +326,38 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)" "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": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+6 -1
View File
@@ -8,7 +8,9 @@
"start": "node src/cli.js", "start": "node src/cli.js",
"dev": "node src/server.js", "dev": "node src/server.js",
"catalog:dry-run": "node src/cli.js catalog-maker --dry-run", "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": { "engines": {
"node": ">=20" "node": ">=20"
@@ -17,5 +19,8 @@
"geckodriver": "^6.1.1", "geckodriver": "^6.1.1",
"mariadb": "^3.5.3", "mariadb": "^3.5.3",
"selenium-webdriver": "^4.46.0" "selenium-webdriver": "^4.46.0"
},
"devDependencies": {
"playwright": "^1.62.1"
} }
} }
+26
View File
@@ -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,
},
});
+1 -1
View File
@@ -131,7 +131,7 @@
</label> </label>
<label class="settings-field"> <label class="settings-field">
<span>Database</span> <span>Database</span>
<input id="localDbName" type="text" autocomplete="off" value="catalog_maker_test" /> <input id="localDbName" type="text" autocomplete="off" value="9bplus" />
</label> </label>
<label class="settings-field"> <label class="settings-field">
<span>User</span> <span>User</span>
+23
View File
@@ -0,0 +1,23 @@
import { test, expect } from "playwright/test";
test("catalog maker shell loads without page errors", async ({ page }) => {
const pageErrors = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("/");
await expect(page.locator("#settingsButton")).toBeVisible();
await expect(page.locator("#recordId")).toBeVisible();
await expect(page.locator("#variantHeader")).toBeVisible();
await page.screenshot({ path: "test-results/catalog-maker-shell.png", fullPage: true });
expect(pageErrors).toEqual([]);
});
test("settings modal opens and shows local database controls", async ({ page }) => {
await page.goto("/");
await page.locator("#settingsButton").click();
await expect(page.locator("#settingsModal")).toBeVisible();
await expect(page.locator("#localDbHost")).toBeVisible();
await expect(page.locator("#testLocalDbButton")).toBeVisible();
});
+29
View File
@@ -0,0 +1,29 @@
import { test, expect } from "playwright/test";
test.describe("database connection", () => {
test("tests the configured Local DB connection", async ({ page }) => {
await page.goto("/");
await page.locator("#settingsButton").click();
await expect(page.locator('input[name="databaseMode"][value="local"]')).toBeChecked();
await page.locator("#localDbHost").fill("127.0.0.1");
await page.locator("#localDbPort").fill("3306");
await page.locator("#localDbName").fill("9bplus");
await page.locator("#localDbUser").fill("root");
await page.locator("#localDbPassword").fill("root");
await page.locator("#testLocalDbButton").click();
await expect(page.locator("#localDbTestResult")).toContainText("Connected:");
});
test("loads manufacturers from Local DB without Live endpoint fallback", async ({ page }) => {
const manufacturersResponse = page.waitForResponse("**/api/manufacturers");
await page.goto("/");
const response = await manufacturersResponse;
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.items.length).toBeGreaterThan(0);
expect(data.items.some((item) => item.name === "La Sportiva")).toBeTruthy();
});
});