test: harden MVP QA gate
This commit is contained in:
parent
081d6085a5
commit
bdb02daa96
11 changed files with 479 additions and 44 deletions
|
|
@ -1,6 +1,26 @@
|
|||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const linkedServiceIds = [
|
||||
"vaultwarden",
|
||||
"forgejo",
|
||||
"wiki",
|
||||
"aws-start",
|
||||
"grafana",
|
||||
"uptime-kuma",
|
||||
"prometheus",
|
||||
"backrest",
|
||||
"n8n",
|
||||
"open-webui",
|
||||
"comfyui",
|
||||
"models",
|
||||
"adminer",
|
||||
"assistant",
|
||||
"suna",
|
||||
"cockpit-infra",
|
||||
"forgejo-ssh-relay",
|
||||
];
|
||||
|
||||
test.describe("dashboard page QA gate", () => {
|
||||
test("renders the model-driven dashboard on desktop", async ({
|
||||
page,
|
||||
|
|
@ -23,8 +43,9 @@ test.describe("dashboard page QA gate", () => {
|
|||
const bodyBox = await page.locator("body").boundingBox();
|
||||
expect(bodyBox?.width).toBeGreaterThan(1000);
|
||||
|
||||
const screenshot = await page.screenshot({ fullPage: true });
|
||||
expect(screenshot.byteLength).toBeGreaterThan(20_000);
|
||||
await expect(page).toHaveScreenshot("dashboard-desktop.png", {
|
||||
fullPage: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps the first screen usable on mobile", async ({ page }, testInfo) => {
|
||||
|
|
@ -37,8 +58,9 @@ test.describe("dashboard page QA gate", () => {
|
|||
).toBeVisible();
|
||||
await expect(page.getByLabel("Service groups")).toBeVisible();
|
||||
|
||||
const screenshot = await page.screenshot({ fullPage: true });
|
||||
expect(screenshot.byteLength).toBeGreaterThan(12_000);
|
||||
await expect(page).toHaveScreenshot("dashboard-mobile.png", {
|
||||
fullPage: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("exposes usable landmarks and a visible keyboard focus state", async ({
|
||||
|
|
@ -47,15 +69,17 @@ test.describe("dashboard page QA gate", () => {
|
|||
await page.goto("/");
|
||||
|
||||
await expect(page.getByRole("main")).toHaveCount(1);
|
||||
await page.keyboard.press("Tab");
|
||||
for (const serviceId of linkedServiceIds) {
|
||||
await page.keyboard.press("Tab");
|
||||
|
||||
const focused = page.locator(":focus");
|
||||
await expect(focused).toHaveAttribute("href", /vault\.dimensionlab\.net/);
|
||||
const focused = page.locator(":focus");
|
||||
await expect(focused).toHaveAttribute("data-model-id", serviceId);
|
||||
|
||||
const focusBoxShadow = await focused.evaluate((element) => {
|
||||
return window.getComputedStyle(element).boxShadow;
|
||||
});
|
||||
expect(focusBoxShadow).not.toBe("none");
|
||||
const focusBoxShadow = await focused.evaluate((element) => {
|
||||
return window.getComputedStyle(element).boxShadow;
|
||||
});
|
||||
expect(focusBoxShadow).not.toBe("none");
|
||||
}
|
||||
});
|
||||
|
||||
test("passes automated accessibility checks", async ({ page }) => {
|
||||
|
|
@ -65,34 +89,29 @@ test.describe("dashboard page QA gate", () => {
|
|||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("honors reduced-motion preferences", async ({ browser }) => {
|
||||
const context = await browser.newContext({ reducedMotion: "reduce" });
|
||||
const page = await context.newPage();
|
||||
test("honors reduced-motion preferences", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
|
||||
try {
|
||||
await page.goto("/");
|
||||
const durations = await page.evaluate(() => {
|
||||
const element = document.createElement("div");
|
||||
element.style.animation = "qa-motion-check 10s infinite";
|
||||
element.style.transition = "opacity 10s linear";
|
||||
document.body.append(element);
|
||||
await page.goto("/");
|
||||
const durations = await page.evaluate(() => {
|
||||
const element = document.createElement("div");
|
||||
element.style.animation = "qa-motion-check 10s infinite";
|
||||
element.style.transition = "opacity 10s linear";
|
||||
document.body.append(element);
|
||||
|
||||
const styles = window.getComputedStyle(element);
|
||||
return {
|
||||
animation: styles.animationDuration,
|
||||
transition: styles.transitionDuration,
|
||||
};
|
||||
});
|
||||
const styles = window.getComputedStyle(element);
|
||||
return {
|
||||
animation: styles.animationDuration,
|
||||
transition: styles.transitionDuration,
|
||||
};
|
||||
});
|
||||
|
||||
expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
|
||||
0.01,
|
||||
);
|
||||
expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
|
||||
0.01,
|
||||
);
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
expect(cssDurationToMilliseconds(durations.animation)).toBeLessThanOrEqual(
|
||||
0.01,
|
||||
);
|
||||
expect(cssDurationToMilliseconds(durations.transition)).toBeLessThanOrEqual(
|
||||
0.01,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 379 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 351 KiB |
Loading…
Add table
Add a link
Reference in a new issue