132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
import AxeBuilder from "@axe-core/playwright";
|
|
import { expect, type Page, test } from "@playwright/test";
|
|
|
|
async function accessible(page: Page, sandboxPreview = false) {
|
|
const builder = new AxeBuilder({ page });
|
|
// A sandbox without allow-scripts cannot run axe; its accessible name is asserted separately.
|
|
if (sandboxPreview) builder.exclude("iframe[sandbox]");
|
|
const result = await builder
|
|
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
|
|
.analyze();
|
|
expect(
|
|
result.violations.map(({ id, impact, nodes }) => ({
|
|
id,
|
|
impact,
|
|
nodes: nodes.map((node) => ({
|
|
target: node.target,
|
|
summary: node.failureSummary,
|
|
})),
|
|
})),
|
|
).toEqual([]);
|
|
}
|
|
|
|
test.beforeEach(async ({ page, baseURL, request }) => {
|
|
await request.post("/fixture/reset");
|
|
await page.clock.setFixedTime(new Date("2030-01-01T12:00:00Z"));
|
|
await page.route("**/*", (route) =>
|
|
new URL(route.request().url()).origin === baseURL
|
|
? route.continue()
|
|
: route.abort(),
|
|
);
|
|
page.on("pageerror", (error) => {
|
|
throw error;
|
|
});
|
|
});
|
|
|
|
test("settings switches have names and remain keyboard operable", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/admin/settings");
|
|
const toggle = page.getByRole("switch", {
|
|
name: "Dark mode by default",
|
|
exact: true,
|
|
});
|
|
await expect(toggle).not.toBeChecked();
|
|
await toggle.focus();
|
|
await page.keyboard.press("Space");
|
|
await expect(toggle).toBeChecked();
|
|
await accessible(page);
|
|
const group = page.locator("section").filter({ has: toggle });
|
|
await expect(group).toHaveScreenshot("settings-switches.png");
|
|
const response = page.waitForResponse((response) =>
|
|
response.url().endsWith("/fixture/actions/settings"),
|
|
);
|
|
await page
|
|
.getByRole("button", { name: "Save settings", exact: true })
|
|
.click();
|
|
expect((await response).ok()).toBe(true);
|
|
await expect(
|
|
page.getByRole("button", { name: "Save settings", exact: true }),
|
|
).toBeDisabled();
|
|
});
|
|
|
|
test("article form and preview dialog pass accessibility with focus return", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/admin/articles/new");
|
|
await expect(page.locator("iframe.tox-edit-area__iframe")).toBeVisible();
|
|
await accessible(page);
|
|
await page.locator('input[name="title"]').fill("Accessible community update");
|
|
const preview = page.getByRole("button", { name: "Preview", exact: true });
|
|
await preview.click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible();
|
|
await expect(dialog).toHaveAccessibleName("Article preview");
|
|
await expect(dialog.locator("iframe")).toHaveAttribute("title", /.+/);
|
|
await accessible(page, true);
|
|
await expect(dialog).toHaveScreenshot("article-preview.png");
|
|
// Keyboard events inside the sandbox do not bubble to the parent dialog.
|
|
await page.keyboard.press("Tab");
|
|
await expect(
|
|
dialog.getByRole("button", { name: "Close", exact: true }),
|
|
).toBeFocused();
|
|
await page.keyboard.press("Escape");
|
|
await expect(dialog).not.toBeVisible();
|
|
await expect(preview).toBeFocused();
|
|
await expect(page.getByRole("main")).toHaveScreenshot("article-form.png");
|
|
});
|
|
|
|
test("support table filters and saved-view popover stay accessible", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/admin/tickets/desk");
|
|
await expect(
|
|
page.getByRole("link", { name: "Account assistance" }),
|
|
).toBeVisible();
|
|
await accessible(page);
|
|
await expect(page.getByRole("main")).toHaveScreenshot("ticket-table.png");
|
|
await page
|
|
.getByRole("button", { name: "Assigned to me", exact: true })
|
|
.click();
|
|
await expect(page).toHaveURL(/filter_assignee=mine/);
|
|
await page.getByRole("button", { name: "Saved views", exact: true }).click();
|
|
await expect(page.getByRole("textbox", { name: "View name" })).toBeVisible();
|
|
await accessible(page);
|
|
await expect(page.getByRole("main")).toHaveScreenshot(
|
|
"ticket-saved-views.png",
|
|
);
|
|
});
|
|
|
|
test("queued synchronization survives reload without duplicate submission", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/admin/import/official");
|
|
const requests: string[] = [];
|
|
page.on("request", (request) => {
|
|
if (request.method() === "POST" && request.url().endsWith("/sync-all"))
|
|
requests.push(request.postData() || "");
|
|
});
|
|
await page
|
|
.getByRole("button", { name: "Start synchronization", exact: true })
|
|
.dblclick();
|
|
await expect(
|
|
page.getByRole("status").filter({ hasText: "Synchronization saved" }),
|
|
).toBeVisible();
|
|
expect(requests).toHaveLength(1);
|
|
await page.reload();
|
|
await page.locator("details details > summary").first().click();
|
|
await expect(page.getByText("fixture_chair", { exact: true })).toBeVisible();
|
|
await accessible(page);
|
|
await expect(page.getByRole("main")).toHaveScreenshot("sync-history.png");
|
|
});
|