From 443897dcb070568b5c5170e11c74b83065f73f7a Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Mon, 28 Sep 2026 20:39:04 +0200 Subject: [PATCH] test(e2e): drive documents, trash and the new-document dialog The shared fixture creates its sample resume through the API and opens it; documents-new covers Start blank (named after the headline) and Copy for a job (linked and searchable by the application). dashboard-lifecycle renames inline, duplicates, trashes with undo, restores and deletes now; the letter, lock, view, import, auth and direction specs follow the new library and menus. --- tests/e2e/fixtures/auth.ts | 6 ++- tests/e2e/fixtures/resume.ts | 29 +++++----- tests/e2e/specs/auth.spec.ts | 4 +- tests/e2e/specs/cover-letter-library.spec.ts | 52 +++++++++++------- tests/e2e/specs/dashboard-lifecycle.spec.ts | 57 +++++++++++++------- tests/e2e/specs/documents-new.spec.ts | 55 +++++++++++++++++++ tests/e2e/specs/documents-views.spec.ts | 39 ++++++++++++++ tests/e2e/specs/json-export-import.spec.ts | 12 ++--- tests/e2e/specs/lock-resume.spec.ts | 14 ++--- tests/e2e/specs/preview-direction.spec.ts | 16 +++--- tests/e2e/specs/resume-views.spec.ts | 46 ---------------- tests/e2e/specs/section-recovery.spec.ts | 3 +- 12 files changed, 209 insertions(+), 124 deletions(-) create mode 100644 tests/e2e/specs/documents-new.spec.ts create mode 100644 tests/e2e/specs/documents-views.spec.ts delete mode 100644 tests/e2e/specs/resume-views.spec.ts diff --git a/tests/e2e/fixtures/auth.ts b/tests/e2e/fixtures/auth.ts index fa8a8439b..0889e7ed1 100644 --- a/tests/e2e/fixtures/auth.ts +++ b/tests/e2e/fixtures/auth.ts @@ -27,7 +27,11 @@ export async function loginViaUi(page: Page, account: E2EAccount) { } export async function logoutViaUi(page: Page, account: E2EAccount) { - await page.getByText(account.email).click(); + // The avatar row at the foot of the sidebar opens the account menu. + await page + .getByRole("complementary") + .getByRole("button", { name: new RegExp(account.name) }) + .click(); await page.getByRole("menuitem", { name: "Sign out" }).click(); await page.goto("/auth/login"); } diff --git a/tests/e2e/fixtures/resume.ts b/tests/e2e/fixtures/resume.ts index 1393dd943..b3fea7100 100644 --- a/tests/e2e/fixtures/resume.ts +++ b/tests/e2e/fixtures/resume.ts @@ -2,20 +2,20 @@ import type { Page, TestInfo } from "@playwright/test"; import { expect } from "@playwright/test"; import { createResumeName } from "./data"; +/** + * Creates a sample resume named after the test and opens it in the editor. It goes through the API: the New + * dialog has its own spec (documents), and every other spec just needs a resume. + */ export async function createSampleResumeFromDashboard(page: Page, testInfo: TestInfo) { const resumeName = createResumeName(testInfo); - await page.goto("/dashboard/resumes"); - await page.getByText("Create a new resume").click(); + const response = await page.request.post("/api/openapi/resumes", { + data: { name: resumeName, tags: [], withSampleData: true }, + }); + expect(response.ok()).toBe(true); + const resumeId = (await response.json()) as string; - const dialog = page.getByRole("dialog", { name: "Create a new resume" }); - await dialog.getByLabel("Name").fill(resumeName); - - const createGroup = dialog.getByRole("group", { name: "Create resume with options" }); - await createGroup.getByRole("button").last().click(); - await page.getByRole("menuitem", { name: "Create a Sample Resume" }).click(); - - // Creating a resume now navigates straight into the builder. + await page.goto(`/builder/${resumeId}`); await page.waitForURL(/\/builder\/.+/); return resumeName; @@ -87,10 +87,11 @@ export async function getPublicUrl(page: Page) { return (await link.getAttribute("href")) as string; } +/** Opens a document's card menu in Documents (right-click, the same menu as ⋯). */ export async function openResumeCardMenu(page: Page, resumeName: string, { reload = true } = {}) { - if (reload) await page.goto("/dashboard/resumes"); - const resumeLink = page.getByRole("link", { name: new RegExp(resumeName) }); - await expect(resumeLink).toBeVisible(); - await resumeLink.click({ button: "right" }); + if (reload) await page.goto("/dashboard"); + const card = page.getByRole("link", { name: resumeName, exact: true }); + await expect(card).toBeVisible(); + await card.click({ button: "right" }); await expect(page.getByRole("menuitem", { name: "Open" })).toBeVisible(); } diff --git a/tests/e2e/specs/auth.spec.ts b/tests/e2e/specs/auth.spec.ts index 65ea08953..a6f16a656 100644 --- a/tests/e2e/specs/auth.spec.ts +++ b/tests/e2e/specs/auth.spec.ts @@ -3,11 +3,11 @@ import { expect, test } from "../fixtures/test"; test("registers and logs in with email credentials", async ({ page, account }) => { await registerViaUi(page, account); - await expect(page.getByRole("heading", { name: "Resumes" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Documents", level: 1 })).toBeVisible(); await logoutViaUi(page, account); await expect(page.getByRole("heading", { name: "Sign in to your account" })).toBeVisible(); await loginViaUi(page, account); - await expect(page.getByRole("heading", { name: "Resumes" })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Documents", level: 1 })).toBeVisible(); }); diff --git a/tests/e2e/specs/cover-letter-library.spec.ts b/tests/e2e/specs/cover-letter-library.spec.ts index 4e964252d..996e602d3 100644 --- a/tests/e2e/specs/cover-letter-library.spec.ts +++ b/tests/e2e/specs/cover-letter-library.spec.ts @@ -1,18 +1,26 @@ +import type { Page } from "@playwright/test"; import { randomUUID } from "node:crypto"; import { readFile } from "node:fs/promises"; import { Pool } from "pg"; import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; +/** New → "New cover letter instead" opens the letter editor on an untitled letter; this names it. */ +async function createLetterFromDocuments(page: Page, name: string) { + await page.goto("/dashboard"); + await page.getByRole("button", { name: "New", exact: true }).click(); + await page.getByRole("button", { name: "New cover letter instead" }).click(); + const editor = page.getByRole("dialog", { name: "Edit cover letter", exact: true }); + await expect(editor).toBeVisible(); + await editor.getByLabel("Name", { exact: true }).fill(name); + return editor; +} + test("imports a library letter into the builder as an independent copy", async ({ authPage: page }, testInfo) => { test.setTimeout(90_000); await createSampleResumeFromDashboard(page, testInfo); const builderUrl = page.url(); - await page.goto("/dashboard/cover-letters"); - await page.getByRole("button", { name: "Create", exact: true }).click(); - await page.getByLabel("Name", { exact: true }).fill("Platform engineer letter"); - await page.getByRole("button", { name: "Create cover letter", exact: true }).click(); - const editor = page.getByRole("dialog", { name: "Edit cover letter", exact: true }); + const editor = await createLetterFromDocuments(page, "Platform engineer letter"); await editor.getByLabel("Recipient", { exact: true }).fill("Dear hiring team,"); await editor.getByLabel("Content", { exact: true }).fill("I build reliable platforms for growing teams."); await editor.getByRole("button", { name: "Save Changes", exact: true }).click(); @@ -59,20 +67,25 @@ test("imports a library letter into the builder as an independent copy", async ( await resumeSaved; await expect(page.getByRole("button", { name: /^Updated independent resume copy\./ })).toBeVisible(); - await page.goto("/dashboard/cover-letters"); - await page.getByRole("button", { name: "Edit Platform engineer letter", exact: true }).click(); + await page.goto("/dashboard?type=letter"); + await page.getByRole("button", { name: "Platform engineer letter", exact: true }).click(); await expect(editor.getByLabel("Content", { exact: true })).toContainText("reliable platforms"); await expect(editor.getByLabel("Content", { exact: true })).not.toContainText("Updated independent resume copy"); await editor.getByRole("button", { name: "Close", exact: true }).click(); - await page.getByLabel("Import cover letter JSON", { exact: true }).setInputFiles(jsonPath); + // New → Import reads a saved letter's JSON too, and opens the copy. + await page.getByRole("button", { name: "New", exact: true }).click(); + await page + .getByRole("dialog", { name: "New document" }) + .getByLabel("Choose a file to import") + .setInputFiles(jsonPath); await expect(editor.getByLabel("Content", { exact: true })).toContainText("reliable platforms"); await editor.getByLabel("Name", { exact: true }).fill("Imported independent copy"); await editor.getByRole("button", { name: "Save Changes", exact: true }).click(); await expect(editor.getByRole("button", { name: "Save Changes", exact: true })).toBeDisabled(); await editor.getByRole("button", { name: "Close", exact: true }).click(); - // The renamed copy shows once the list has refetched; until then both rows carry the original name. - await expect(page.getByRole("button", { name: "Edit Imported independent copy", exact: true })).toBeVisible(); - await expect(page.getByRole("button", { name: "Edit Platform engineer letter", exact: true })).toBeVisible(); + // The renamed copy shows once the list has refetched; until then both cards carry the original name. + await expect(page.getByRole("button", { name: "Imported independent copy", exact: true })).toBeVisible(); + await expect(page.getByRole("button", { name: "Platform engineer letter", exact: true })).toBeVisible(); }); test("keeps the application PDF snapshot after the library letter is deleted", async ({ authPage: page, account }) => { @@ -84,11 +97,7 @@ test("keeps the application PDF snapshot after the library letter is deleted", a 'insert into application (id, user_id, company, role) select $1, id, $2, $3 from "user" where email = $4', [applicationId, "Snapshot Company", "Platform Engineer", account.email], ); - await page.goto("/dashboard/cover-letters"); - await page.getByRole("button", { name: "Create", exact: true }).click(); - await page.getByLabel("Name", { exact: true }).fill("Snapshot letter"); - await page.getByRole("button", { name: "Create cover letter", exact: true }).click(); - const editor = page.getByRole("dialog", { name: "Edit cover letter", exact: true }); + const editor = await createLetterFromDocuments(page, "Snapshot letter"); await editor.getByLabel("Content", { exact: true }).fill("My application snapshot remains available."); await editor.getByRole("button", { name: "Save Changes", exact: true }).click(); await expect(editor.getByRole("button", { name: "Save Changes", exact: true })).toBeDisabled(); @@ -106,10 +115,15 @@ test("keeps the application PDF snapshot after the library letter is deleted", a expect(before.ok()).toBe(true); const bytes = await before.body(); expect(bytes.subarray(0, 5).toString()).toBe("%PDF-"); - await editor.getByRole("button", { name: "Delete", exact: true }).click(); - await page.getByRole("alertdialog").getByRole("button", { name: "Delete", exact: true }).click(); + await editor.getByRole("button", { name: "Move to Trash", exact: true }).click(); await expect(editor).not.toBeVisible(); - await expect(page.getByRole("button", { name: "Edit Snapshot letter", exact: true })).not.toBeVisible(); + await expect(page.getByRole("button", { name: "Snapshot letter", exact: true })).not.toBeVisible(); + // Deleting it for good from Trash leaves the application's snapshot alone too. + await page.goto("/dashboard/trash"); + await page.getByRole("button", { name: "Options for Snapshot letter" }).click(); + await page.getByRole("menuitem", { name: "Delete now…" }).click(); + await page.getByRole("alertdialog").getByRole("button", { name: "Delete now" }).click(); + await expect(page.getByText("Trash is empty")).toBeVisible(); const after = await page.request.get(url); expect(after.ok()).toBe(true); expect(await after.body()).toEqual(bytes); diff --git a/tests/e2e/specs/dashboard-lifecycle.spec.ts b/tests/e2e/specs/dashboard-lifecycle.spec.ts index 10a5c9068..97c06ac25 100644 --- a/tests/e2e/specs/dashboard-lifecycle.spec.ts +++ b/tests/e2e/specs/dashboard-lifecycle.spec.ts @@ -1,30 +1,51 @@ import { createSampleResumeFromDashboard, openResumeCardMenu } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; -test("renames, duplicates and deletes a resume from the dashboard", async ({ authPage: page }, testInfo) => { +test("renames, duplicates, moves to Trash, restores and deletes a resume", async ({ authPage: page }, testInfo) => { const resumeName = await createSampleResumeFromDashboard(page, testInfo); - // Rename via the card context menu (resume names are capped at 64 chars, keep it short) + // Rename inline from the card menu: Enter commits. const renamedTo = `E2E Renamed ${Date.now().toString(36)}`; await openResumeCardMenu(page, resumeName); - await page.getByRole("menuitem", { name: "Edit details" }).click(); - const updateDialog = page.getByRole("dialog", { name: "Update Resume" }); - await updateDialog.getByLabel("Name").fill(renamedTo); - await updateDialog.getByRole("button", { name: "Save Changes" }).click(); - await expect(page.getByRole("link", { name: new RegExp(renamedTo) })).toBeVisible(); + await page.getByRole("menuitem", { name: "Rename" }).click(); + const nameField = page.getByRole("textbox", { name: "Name", exact: true }); + await nameField.fill(renamedTo); + await nameField.press("Enter"); + await expect(page.getByRole("link", { name: renamedTo, exact: true })).toBeVisible(); - // Duplicate — the copy defaults to " (Copy)" + // Duplicate at once: " (copy)". await openResumeCardMenu(page, renamedTo, { reload: false }); await page.getByRole("menuitem", { name: "Duplicate" }).click(); - const duplicateDialog = page.getByRole("dialog", { name: "Duplicate Resume" }); - await duplicateDialog.getByRole("button", { name: "Duplicate" }).click(); - const copyLink = page.getByRole("link", { name: new RegExp(`${renamedTo} \\(Copy\\)`) }); - await expect(copyLink).toBeVisible(); + const copyName = `${renamedTo} (copy)`; + await expect(page.getByRole("link", { name: copyName, exact: true })).toBeVisible(); - // Delete the copy and verify it disappears while the original stays - await openResumeCardMenu(page, `${renamedTo} \\(Copy\\)`, { reload: false }); - await page.getByRole("menuitem", { name: "Delete" }).click(); - await page.getByRole("alertdialog").getByRole("button", { name: "Confirm" }).click(); - await expect(copyLink).toBeHidden(); - await expect(page.getByRole("link", { name: new RegExp(renamedTo) })).toBeVisible(); + // Move the copy to Trash, undo, then move it again. + await openResumeCardMenu(page, copyName, { reload: false }); + await page.getByRole("menuitem", { name: "Move to Trash" }).click(); + await expect(page.getByRole("link", { name: copyName, exact: true })).toBeHidden(); + await page.getByRole("button", { name: "Undo", exact: true }).click(); + await expect(page.getByRole("link", { name: copyName, exact: true })).toBeVisible(); + await openResumeCardMenu(page, copyName, { reload: false }); + await page.getByRole("menuitem", { name: "Move to Trash" }).click(); + await expect(page.getByRole("link", { name: copyName, exact: true })).toBeHidden(); + + // Trash: restore it, trash it again, then delete it now. + await page.getByRole("link", { name: /^Trash/ }).click(); + await expect(page.getByRole("heading", { name: "Trash", level: 1 })).toBeVisible(); + await expect(page.getByText("30 days left")).toBeVisible(); + await page.getByRole("button", { name: `Options for ${copyName}` }).click(); + await page.getByRole("menuitem", { name: "Restore" }).click(); + await expect(page.getByText("Trash is empty")).toBeVisible(); + + await openResumeCardMenu(page, copyName); + await page.getByRole("menuitem", { name: "Move to Trash" }).click(); + await page.goto("/dashboard/trash"); + await page.getByRole("button", { name: `Options for ${copyName}` }).click(); + await page.getByRole("menuitem", { name: "Delete now…" }).click(); + await page.getByRole("alertdialog").getByRole("button", { name: "Delete now" }).click(); + await expect(page.getByText("Trash is empty")).toBeVisible(); + + await page.goto("/dashboard"); + await expect(page.getByRole("link", { name: renamedTo, exact: true })).toBeVisible(); + await expect(page.getByRole("link", { name: copyName, exact: true })).toBeHidden(); }); diff --git a/tests/e2e/specs/documents-new.spec.ts b/tests/e2e/specs/documents-new.spec.ts new file mode 100644 index 000000000..e7bce1d73 --- /dev/null +++ b/tests/e2e/specs/documents-new.spec.ts @@ -0,0 +1,55 @@ +import { randomUUID } from "node:crypto"; +import { Pool } from "pg"; +import { createSampleResumeFromDashboard } from "../fixtures/resume"; +import { expect, test } from "../fixtures/test"; + +test("starts a blank resume on the name field and names it after the headline", async ({ authPage: page }) => { + await page.goto("/dashboard"); + await page.getByRole("button", { name: "New", exact: true }).click(); + await page.getByRole("button", { name: /^Start blank/ }).click(); + await page.waitForURL(/\/builder\/.+/); + + const fullName = page.getByRole("textbox", { name: "Full name", exact: true }); + await expect(fullName).toBeFocused(); + await fullName.fill("Jordan Reyes"); + await page.getByRole("textbox", { name: "Headline", exact: true }).fill("Product Designer"); + // Until someone renames it, the document is called by its headline. + await expect(page.getByRole("banner").getByText("Product Designer", { exact: true })).toBeVisible(); + + await page.goto("/dashboard"); + await expect(page.getByRole("link", { name: "Product Designer", exact: true })).toBeVisible(); +}); + +test("copies a resume for a job and links the copy to the application", async ({ + authPage: page, + account, +}, testInfo) => { + const pool = new Pool({ connectionString: process.env.DATABASE_URL }); + try { + const resumeName = await createSampleResumeFromDashboard(page, testInfo); + await pool.query( + 'insert into application (id, user_id, company, role) select $1, id, $2, $3 from "user" where email = $4', + [randomUUID(), "Orbital", "Product Designer", account.email], + ); + + await page.goto("/dashboard"); + await page.getByRole("button", { name: "New", exact: true }).click(); + await page.getByRole("button", { name: /^Copy a resume for a job/ }).click(); + const dialog = page.getByRole("dialog", { name: "Copy a resume for a job" }); + await dialog.getByText(resumeName, { exact: true }).click(); + await dialog.getByRole("button", { name: "Orbital", exact: true }).click(); + await expect(dialog.getByRole("textbox", { name: "Name", exact: true })).toHaveValue(`${resumeName} — Orbital`); + await dialog.getByRole("button", { name: "Create and open" }).click(); + await page.waitForURL(/\/builder\/.+/); + + await page.goto("/dashboard"); + const copy = page.getByRole("article").filter({ has: page.getByRole("link", { name: `${resumeName} — Orbital` }) }); + await expect(copy.getByText("Orbital", { exact: true })).toBeVisible(); + // Search covers linked applications. + await page.getByRole("searchbox", { name: "Search documents" }).fill("orbital"); + await expect(page.getByRole("link", { name: resumeName, exact: true })).toBeHidden(); + await expect(page.getByRole("link", { name: `${resumeName} — Orbital`, exact: true })).toBeVisible(); + } finally { + await pool.end(); + } +}); diff --git a/tests/e2e/specs/documents-views.spec.ts b/tests/e2e/specs/documents-views.spec.ts new file mode 100644 index 000000000..b12d6fc1b --- /dev/null +++ b/tests/e2e/specs/documents-views.spec.ts @@ -0,0 +1,39 @@ +import { createSampleResumeFromDashboard } from "../fixtures/resume"; +import { expect, test } from "../fixtures/test"; + +test("switches between grid and list, remembers the choice, and honours the URL", async ({ + authPage: page, +}, testInfo) => { + test.setTimeout(60_000); + await page.setViewportSize({ width: 1440, height: 1000 }); + const name = await createSampleResumeFromDashboard(page, testInfo); + await page.goto("/dashboard"); + const card = page.getByRole("link", { name, exact: true }); + await expect(card).toBeVisible(); + // The card shows the resume's real first page once it renders. + await expect(card.locator('[style*="background-image: url("]')).toBeVisible({ timeout: 30_000 }); + + const list = page.getByRole("radio", { name: "List" }); + const grid = page.getByRole("radio", { name: "Grid" }); + await list.click(); + await expect(list).toHaveAttribute("aria-checked", "true"); + await expect(page.getByRole("columnheader", { name: "Application" })).toBeVisible(); + + // The last view picked is remembered on this device. + await card.click(); + await page.waitForURL(/\/builder\/.+/); + await page.goto("/dashboard"); + await expect(list).toHaveAttribute("aria-checked", "true"); + + // The URL wins, and an unknown view falls back. + await page.goto("/dashboard?view=grid"); + await expect(grid).toHaveAttribute("aria-checked", "true"); + await page.goto("/dashboard?view=invalid"); + await expect(list).toHaveAttribute("aria-checked", "true"); + + await page.setViewportSize({ width: 390, height: 844 }); + await grid.click(); + await expect(card).toBeVisible(); + await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); + await page.screenshot({ path: testInfo.outputPath("documents-mobile.png"), animations: "disabled" }); +}); diff --git a/tests/e2e/specs/json-export-import.spec.ts b/tests/e2e/specs/json-export-import.spec.ts index f514869b4..7f84921c1 100644 --- a/tests/e2e/specs/json-export-import.spec.ts +++ b/tests/e2e/specs/json-export-import.spec.ts @@ -16,12 +16,12 @@ test("exports and imports a resume JSON backup", async ({ authPage: page }, test await download.saveAs(downloadPath); const exportedData = JSON.parse(await readFile(downloadPath, "utf-8")) as { basics: { name: string } }; - await page.goto("/dashboard/resumes"); - await page.getByRole("button", { name: "Import", exact: true }).click(); - const dialog = page.getByRole("dialog", { name: "Import an existing resume" }); - // Import is now file-first: selecting the file auto-detects the Reactive Resume JSON format. - await dialog.locator('input[type="file"]').setInputFiles(downloadPath); - await dialog.getByRole("button", { name: "Import", exact: true }).click(); + // New → Import a resume: picking the file detects the format and imports it in three steps. + await page.goto("/dashboard"); + await page.getByRole("button", { name: "New", exact: true }).click(); + const dialog = page.getByRole("dialog", { name: "New document" }); + await dialog.getByLabel("Choose a file to import").setInputFiles(downloadPath); + await page.getByRole("button", { name: "Open in editor" }).click(); await page.waitForURL(/\/builder\/.+/); await openSidebarSection(page, "Basics"); diff --git a/tests/e2e/specs/lock-resume.spec.ts b/tests/e2e/specs/lock-resume.spec.ts index ca69b4bea..b35d6c961 100644 --- a/tests/e2e/specs/lock-resume.spec.ts +++ b/tests/e2e/specs/lock-resume.spec.ts @@ -1,7 +1,7 @@ import { createSampleResumeFromDashboard, openResumeCardMenu } from "../fixtures/resume"; import { expect, test } from "../fixtures/test"; -test("locks a resume, blocking updates and deletion until unlocked", async ({ authPage: page }, testInfo) => { +test("locks a resume, blocking renames and Trash until unlocked", async ({ authPage: page }, testInfo) => { const resumeName = await createSampleResumeFromDashboard(page, testInfo); await openResumeCardMenu(page, resumeName); @@ -10,15 +10,15 @@ test("locks a resume, blocking updates and deletion until unlocked", async ({ au if (!response.ok()) return false; return (response.request().postData() ?? "").includes('"isLocked":true'); }); - await page.getByRole("menuitem", { name: "Lock" }).click(); - await page.getByRole("alertdialog").getByRole("button", { name: "Confirm" }).click(); + // Locking is reversible, so it doesn't ask first. + await page.getByRole("menuitem", { name: "Lock editing" }).click(); await lockPromise; // Locked: the menu now offers Unlock, and destructive/edit actions are disabled await openResumeCardMenu(page, resumeName); await expect(page.getByRole("menuitem", { name: "Unlock" })).toBeVisible(); - await expect(page.getByRole("menuitem", { name: "Edit details" })).toBeDisabled(); - await expect(page.getByRole("menuitem", { name: "Delete" })).toBeDisabled(); + await expect(page.getByRole("menuitem", { name: "Rename" })).toBeDisabled(); + await expect(page.getByRole("menuitem", { name: "Move to Trash" })).toBeDisabled(); // Unlock restores the actions — wait for the mutation to land before re-reading the menu const unlockPromise = page.waitForResponse((response) => { @@ -29,6 +29,6 @@ test("locks a resume, blocking updates and deletion until unlocked", async ({ au await page.getByRole("menuitem", { name: "Unlock" }).click(); await unlockPromise; await openResumeCardMenu(page, resumeName); - await expect(page.getByRole("menuitem", { name: "Lock" })).toBeVisible(); - await expect(page.getByRole("menuitem", { name: "Delete" })).toBeEnabled(); + await expect(page.getByRole("menuitem", { name: "Lock editing" })).toBeVisible(); + await expect(page.getByRole("menuitem", { name: "Move to Trash" })).toBeEnabled(); }); diff --git a/tests/e2e/specs/preview-direction.spec.ts b/tests/e2e/specs/preview-direction.spec.ts index 7e670b35d..d847a5169 100644 --- a/tests/e2e/specs/preview-direction.spec.ts +++ b/tests/e2e/specs/preview-direction.spec.ts @@ -26,15 +26,13 @@ for (const uiLanguage of ["English", "Arabic"]) { test(`centers preview with ${uiLanguage} UI and ${resumeLocale} resume`, async ({ authPage: page }, info) => { test.setTimeout(60_000); await page.setViewportSize({ width: 1920, height: 950 }); - await page.goto("/dashboard/resumes"); - await page.getByText("Create a new resume", { exact: true }).click(); - const dialog = page.getByRole("dialog", { name: "Create a new resume" }); - await dialog.getByLabel("Name", { exact: true }).fill("Preview direction fixture"); - await dialog.getByRole("button", { name: "Create", exact: true }).click(); - await page.waitForURL(/\/builder\/.+/); - const builderUrl = page.url(); - const resumeId = builderUrl.split("/").at(-1); - await page.goto("/dashboard/resumes"); + const created = await page.request.post("/api/openapi/resumes", { + data: { name: "Preview direction fixture", tags: [] }, + }); + expect(created.ok()).toBe(true); + const resumeId = (await created.json()) as string; + await page.goto("/dashboard"); + const builderUrl = new URL(`/builder/${resumeId}`, page.url()).toString(); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); try { await pool.query( diff --git a/tests/e2e/specs/resume-views.spec.ts b/tests/e2e/specs/resume-views.spec.ts deleted file mode 100644 index 5405f08c4..000000000 --- a/tests/e2e/specs/resume-views.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createSampleResumeFromDashboard } from "../fixtures/resume"; -import { expect, test } from "../fixtures/test"; - -test("keeps compact and list preferences through navigation and reload, with explicit URL overrides", async ({ - authPage: page, -}, testInfo) => { - test.setTimeout(60_000); - await page.setViewportSize({ width: 1440, height: 1000 }); - const name = await createSampleResumeFromDashboard(page, testInfo); - await page.goto("/dashboard/resumes"); - const card = page.getByRole("link").filter({ hasText: name }); - await expect(card).toBeVisible(); - const gridWidth = (await card.boundingBox())?.width; - if (!gridWidth) throw new Error("Resume card has no width."); - await page.getByRole("tab", { name: "Compact", exact: true }).click(); - await expect(page.getByRole("tab", { name: "Compact", exact: true })).toHaveAttribute("aria-selected", "true"); - await expect.poll(async () => (await card.boundingBox())?.width ?? gridWidth).toBeLessThan(gridWidth); - await card.click(); - await page.waitForURL(/\/builder\/.+/); - await page.goto("/dashboard/resumes"); - await expect(page.getByRole("tab", { name: "Compact", exact: true })).toHaveAttribute("aria-selected", "true"); - await page.reload(); - await expect(page.getByRole("tab", { name: "Compact", exact: true })).toHaveAttribute("aria-selected", "true"); - await expect(card.locator('[style*="background-image: url("]')).toBeVisible({ timeout: 30_000 }); - await page.screenshot({ path: testInfo.outputPath("compact-resumes.png"), animations: "disabled" }); - - await page.getByRole("tab", { name: "Grid", exact: true }).click(); - await expect(page).toHaveURL(/view=grid/); - await expect(page.getByRole("tab", { name: "Grid", exact: true })).toHaveAttribute("aria-selected", "true"); - await expect.poll(async () => (await card.boundingBox())?.width).toBe(gridWidth); - await page.getByRole("tab", { name: "List", exact: true }).click(); - await expect(page.getByRole("tab", { name: "List", exact: true })).toHaveAttribute("aria-selected", "true"); - await page.goto("/dashboard/resumes"); - await expect(page.getByRole("tab", { name: "List", exact: true })).toHaveAttribute("aria-selected", "true"); - await page.goto("/dashboard/resumes?view=grid"); - await expect(page.getByRole("tab", { name: "Grid", exact: true })).toHaveAttribute("aria-selected", "true"); - await page.goto("/dashboard/resumes?view=invalid"); - await expect(page.getByRole("tab", { name: "Grid", exact: true })).toHaveAttribute("aria-selected", "true"); - - await page.setViewportSize({ width: 390, height: 844 }); - await page.getByRole("tab", { name: "Compact", exact: true }).click(); - await expect(page.getByRole("tab", { name: "Compact", exact: true })).toHaveAttribute("aria-selected", "true"); - await expect(card).toBeVisible(); - await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); - await page.screenshot({ path: testInfo.outputPath("compact-resumes-mobile.png"), animations: "disabled" }); -}); diff --git a/tests/e2e/specs/section-recovery.spec.ts b/tests/e2e/specs/section-recovery.spec.ts index 73f0debdb..9fa15ebde 100644 --- a/tests/e2e/specs/section-recovery.spec.ts +++ b/tests/e2e/specs/section-recovery.spec.ts @@ -178,8 +178,7 @@ test("recovers hidden printable sections without changing authored placement", a .toBe(true); await openResumeCardMenu(page, resumeName); const locked = page.waitForResponse((response) => (response.request().postData() ?? "").includes('"isLocked":true')); - await page.getByRole("menuitem", { name: "Lock" }).click(); - await page.getByRole("alertdialog").getByRole("button", { name: "Confirm" }).click(); + await page.getByRole("menuitem", { name: "Lock editing" }).click(); await locked; await page.goto(`/builder/${resumeId}`);