feat(ui): redesign grouped application shell
This commit is contained in:
@@ -30,6 +30,7 @@ test("public entry points meet automated WCAG A and AA checks", async ({ page })
|
||||
});
|
||||
|
||||
test("primary authenticated workspaces meet automated WCAG A and AA checks", async ({ page }) => {
|
||||
test.setTimeout(60_000); // Six complete axe scans exceed Playwright's default 30s on slower hosts.
|
||||
await login(page);
|
||||
|
||||
for (const route of ["/dashboard", "/jobs", "/kanban", "/career", "/settings", "/admin/system"]) {
|
||||
|
||||
@@ -5,11 +5,7 @@ const apiUrl = "http://localhost:5302/api";
|
||||
|
||||
test("CSV export has safe cells and headers, and browser download preserves its UTF-8 bytes", async ({ page }) => {
|
||||
await page.addInitScript(() => window.localStorage.setItem("uiLanguage", "en"));
|
||||
await page.goto("/login");
|
||||
await page.getByLabel("Username or email", { exact: true }).fill("e2e@example.test");
|
||||
await page.getByLabel("Current password", { exact: true }).fill("E2ePassword123!");
|
||||
await page.getByRole("button", { name: "Sign in", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard$/);
|
||||
expect((await page.request.post(`${apiUrl}/auth/login`, { data: { email: "e2e@example.test", password: "E2ePassword123!" } })).status()).toBe(200);
|
||||
const me = await (await page.request.get(`${apiUrl}/auth/me`)).json();
|
||||
const csrf = (await page.context().cookies()).find(cookie => cookie.name === "XSRF-TOKEN")!.value;
|
||||
const headers = { "X-CSRF-TOKEN": csrf, "X-Session-Context": me.sessionContext };
|
||||
|
||||
@@ -38,6 +38,7 @@ const test = base.extend<{ headers: Record<string, string> }>({
|
||||
});
|
||||
|
||||
test("[PRES-C01 PRES-C06 PRES-C07 PRES-C10] mobile CV override persists without changing the canonical profile or sibling CV", async ({ page, headers }) => {
|
||||
test.setTimeout(60_000); // Real API setup, mobile editing, preview and reload share this journey.
|
||||
const create = async (name: string) => {
|
||||
const response = await page.request.post(`${apiUrl}/cv/variants`, { headers, data: { name } });
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
@@ -309,6 +309,46 @@ for (const mode of ["light", "dark"] as const) for (const language of ["en", "nb
|
||||
}
|
||||
});
|
||||
|
||||
test(`[P5-01 PRES-E07 E08 A03] ${suffix} grouped shell, rail menu and route focus`, async ({ page }, info) => {
|
||||
test.setTimeout(90_000);
|
||||
const t = translations[language];
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.goto("/kanban");
|
||||
const navigation = page.getByRole("navigation", { name: t.navigation });
|
||||
await expect(navigation.getByText(t.jobSearch, { exact: true })).toBeVisible();
|
||||
await expect(navigation.getByText(t.career, { exact: true })).toBeVisible();
|
||||
await expect(navigation.getByRole("button", { name: t.jobApplications, exact: true })).toHaveAttribute("aria-current", "location");
|
||||
await expect(navigation.getByRole("button", { name: t.kanbanBoard, exact: true })).toHaveAttribute("aria-current", "page");
|
||||
await expect(navigation.getByRole("button", { name: t.connectedAccounts, exact: true })).toBeInViewport({ ratio: 1 });
|
||||
await expect(page.getByRole("link", { name: t.skipToMainContent, exact: true })).toHaveAttribute("href", "#app-main-content");
|
||||
await capture(page, info, `grouped-shell-${suffix}`);
|
||||
await scan(page, info, `grouped-shell-${suffix}`, "nav");
|
||||
|
||||
await page.getByRole("button", { name: t.collapseSidebar, exact: true }).click();
|
||||
const jobsRail = page.getByRole("button", { name: t.jobApplications, exact: true });
|
||||
await jobsRail.focus();
|
||||
await jobsRail.press("Enter");
|
||||
const railMenu = page.getByRole("menu");
|
||||
await expect(railMenu.getByRole("menuitem", { name: t.trash, exact: true })).toBeVisible();
|
||||
await capture(page, info, `grouped-rail-${suffix}`);
|
||||
await railMenu.getByRole("menuitem", { name: t.trash, exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/trash$/);
|
||||
await expect(page.getByRole("heading", { level: 1, name: t.trash, exact: true })).toBeFocused();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const trigger = page.getByRole("button", { name: t.openNavigation, exact: true });
|
||||
await trigger.click();
|
||||
const drawer = page.getByRole("dialog", { name: t.navigation });
|
||||
await expect(drawer.getByText(t.jobSearch, { exact: true })).toBeVisible();
|
||||
await expect(drawer.getByRole("button", { name: t.trash, exact: true })).toHaveAttribute("aria-current", "page");
|
||||
await capture(page, info, `grouped-mobile-${suffix}`);
|
||||
await scan(page, info, `grouped-mobile-${suffix}`, '[role="dialog"]');
|
||||
await drawer.getByRole("button", { name: t.kanbanBoard, exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/kanban$/);
|
||||
await expect(page.getByRole("heading", { level: 1, name: t.kanbanBoard, exact: true })).toBeFocused();
|
||||
await expect(drawer).not.toBeVisible();
|
||||
});
|
||||
|
||||
if ((mode === "light" && language === "en") || (mode === "dark" && language === "nb")) {
|
||||
test(`[P5-00 PRES-E07 E09 C06 S02 S03] ${suffix} dirty editor survives shell utilities and cancel`, async ({ page, context, owner }, info) => {
|
||||
test.setTimeout(90_000);
|
||||
|
||||
@@ -47,6 +47,10 @@ async function login(page: Page) {
|
||||
await loginAs(page, "e2e@example.test", "E2ePassword123!");
|
||||
}
|
||||
|
||||
async function loginForApiSetup(page: Page) {
|
||||
expect((await page.request.post(`${apiUrl}/auth/login`, { data: { email: "e2e@example.test", password: "E2ePassword123!" } })).status()).toBe(200);
|
||||
}
|
||||
|
||||
async function csrfHeader(page: Page) {
|
||||
const cookie = (await page.context().cookies()).find((value) => value.name === "XSRF-TOKEN");
|
||||
expect(cookie, "login should issue a CSRF cookie").toBeTruthy();
|
||||
@@ -126,7 +130,7 @@ test("a Free account keeps manual work available while AI actions stay honestly
|
||||
const email = `free-${suffix}@example.test`;
|
||||
const password = "FreeUserPassword123!";
|
||||
|
||||
await login(page);
|
||||
await loginForApiSetup(page);
|
||||
const headers = await csrfHeader(page);
|
||||
const createUser = await page.request.post(`${apiUrl}/users`, {
|
||||
headers,
|
||||
@@ -189,11 +193,12 @@ test("a saved job can be created through the reviewed UI flow", async ({ page })
|
||||
});
|
||||
|
||||
test("the dedicated application workspace survives deep links, long data and unsaved navigation", async ({ page }) => {
|
||||
test.setTimeout(60_000); // API setup and the complete loaded workspace journey exceed 30s on slower hosts.
|
||||
const suffix = Date.now().toString();
|
||||
const companyName = `Application Workspace Company With A Deliberately Long Name ${suffix}`;
|
||||
const title = `Principal Platform Reliability Engineer For Distributed Customer Systems ${suffix}`;
|
||||
|
||||
await login(page);
|
||||
await loginForApiSetup(page);
|
||||
const headers = await csrfHeader(page);
|
||||
let companyResponse = await page.request.post(`${apiUrl}/companies`, {
|
||||
headers,
|
||||
@@ -243,7 +248,7 @@ test("the dedicated application workspace survives deep links, long data and uns
|
||||
const job = await jobResponse.json();
|
||||
|
||||
await page.goto("/jobs");
|
||||
const applicationRow = page.getByRole("row", { name: new RegExp(`Open ${title}`, "i") });
|
||||
const applicationRow = page.locator(`tr[data-job-row-id="${job.id}"]`);
|
||||
await applicationRow.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}$`));
|
||||
@@ -294,7 +299,7 @@ test("the dedicated application workspace survives deep links, long data and uns
|
||||
|
||||
await page.getByRole("button", { name: "Back to applications" }).click();
|
||||
await expect(page).toHaveURL(/\/jobs$/);
|
||||
await expect(page.getByRole("row", { name: new RegExp(`Open ${title}`, "i") })).toBeFocused();
|
||||
await expect(applicationRow).toBeFocused();
|
||||
|
||||
await page.goto("/jobs/2147483647");
|
||||
const missingJobAlert = page.getByRole("alert").filter({ hasText: /Not Found|Could not open this application/i });
|
||||
@@ -387,7 +392,7 @@ test("Career Workspace loads from the authenticated application shell", async ({
|
||||
});
|
||||
|
||||
test("a public CV renders anonymously and downloads as PDF", async ({ page }, testInfo) => {
|
||||
await login(page);
|
||||
await loginForApiSetup(page);
|
||||
const headers = await csrfHeader(page);
|
||||
const create = await page.request.post(`${apiUrl}/cv/variants`, {
|
||||
headers,
|
||||
|
||||
Reference in New Issue
Block a user