test: add e2e specs for dashboard, sections, templates, sharing and settings workflows

- dashboard-lifecycle: rename, duplicate, delete via card context menu
- section-editing: add experience item, verify persistence across reloads
- template-switch: switch template in gallery, verify persisted selection
- sharing-password: password-protect public link, unlock as anonymous visitor
- lock-resume: lock blocks update/delete, unlock restores them
- settings-profile: profile name change persists
This commit is contained in:
Amruth Pillai
2026-07-03 20:04:13 +02:00
parent a4999c04af
commit 0ba44865c7
7 changed files with 182 additions and 0 deletions
+8
View File
@@ -27,3 +27,11 @@ export async function openSidebarSection(page: Page, title: string) {
await page.getByTitle(title, { exact: true }).click();
await expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible();
}
export async function openResumeCardMenu(page: Page, resumeName: string) {
await page.goto("/dashboard/resumes");
const resumeLink = page.getByRole("link", { name: new RegExp(resumeName) });
await expect(resumeLink).toBeVisible();
await resumeLink.click({ button: "right" });
await expect(page.getByRole("menuitem", { name: "Open" })).toBeVisible();
}
@@ -0,0 +1,30 @@
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) => {
const resumeName = await createSampleResumeFromDashboard(page, testInfo);
// Rename via the card context menu (resume names are capped at 64 chars, keep it short)
const renamedTo = `E2E Renamed ${Date.now().toString(36)}`;
await openResumeCardMenu(page, resumeName);
await page.getByRole("menuitem", { name: "Update" }).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();
// Duplicate — the copy defaults to "<name> (Copy)"
await openResumeCardMenu(page, renamedTo);
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();
// Delete the copy and verify it disappears while the original stays
await openResumeCardMenu(page, `${renamedTo} \\(Copy\\)`);
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();
});
+34
View File
@@ -0,0 +1,34 @@
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) => {
const resumeName = await createSampleResumeFromDashboard(page, testInfo);
await openResumeCardMenu(page, resumeName);
const lockPromise = page.waitForResponse((response) => {
if (!response.url().includes("/api/rpc")) return false;
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();
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: "Update" })).toBeDisabled();
await expect(page.getByRole("menuitem", { name: "Delete" })).toBeDisabled();
// Unlock restores the actions — wait for the mutation to land before re-reading the menu
const unlockPromise = page.waitForResponse((response) => {
if (!response.url().includes("/api/rpc")) return false;
if (!response.ok()) return false;
return (response.request().postData() ?? "").includes('"isLocked":false');
});
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();
});
+34
View File
@@ -0,0 +1,34 @@
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
test("adds an experience item and persists it across reloads", async ({ authPage: page }, testInfo) => {
await createSampleResumeFromDashboard(page, testInfo);
const company = `E2E Corp ${Date.now()}`;
const position = "Principal Tester";
await openSidebarSection(page, "Experience");
await page.getByRole("button", { name: "Add a new experience" }).click();
const dialog = page.getByRole("dialog", { name: "Create a new experience" });
await dialog.getByLabel("Company").fill(company);
await dialog.getByLabel("Position").fill(position);
const savePromise = page.waitForResponse((response) => {
if (!response.url().includes("/api/rpc")) return false;
if (response.request().method() !== "POST") return false;
if (!response.ok()) return false;
return (response.request().postData() ?? "").includes(company);
});
await dialog.getByRole("button", { name: "Create" }).click();
await savePromise;
// The new item shows up in the section list with the company as its title
await expect(page.getByText(company)).toBeVisible();
// And it survives a full reload
await page.reload();
await openSidebarSection(page, "Experience");
await expect(page.getByText(company)).toBeVisible();
await expect(page.getByText(position)).toBeVisible();
});
+15
View File
@@ -0,0 +1,15 @@
import { expect, test } from "../fixtures/test";
test("updates the profile name from settings and persists it", async ({ authPage: page }) => {
await page.goto("/dashboard/settings/profile");
const updatedName = `E2E Renamed ${Date.now()}`;
const nameField = page.getByLabel("Name", { exact: true });
await expect(nameField).toBeVisible();
await nameField.fill(updatedName);
await page.getByRole("button", { name: "Save Changes" }).click();
// The save round-trips through the API; a reload must show the new value
await page.reload();
await expect(page.getByLabel("Name", { exact: true })).toHaveValue(updatedName);
});
+34
View File
@@ -0,0 +1,34 @@
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
test("password-protects a public resume and unlocks it as a visitor", async ({ browser, authPage: page }, testInfo) => {
await createSampleResumeFromDashboard(page, testInfo);
await openSidebarSection(page, "Sharing");
await page.getByRole("switch", { name: /Allow Public Access/ }).click();
const sharingUrl = page.locator("#sharing-url");
await expect(sharingUrl).toHaveValue(/\/e2e_/);
const publicUrl = await sharingUrl.inputValue();
const password = "e2e-secret-42";
await page.getByRole("button", { name: "Set Password" }).click();
const prompt = page.getByRole("alertdialog");
await prompt.locator('input[type="password"]').fill(password);
await prompt.getByRole("button", { name: "Set Password" }).click();
await expect(page.getByRole("button", { name: "Remove Password" })).toBeVisible();
const anonymous = await browser.newPage();
try {
// Anonymous visitors hit the password gate first
await anonymous.goto(publicUrl);
await anonymous.waitForURL(/\/auth\/resume-password/);
await anonymous.getByLabel("Password", { exact: true }).fill(password);
await anonymous.getByRole("button", { name: "Unlock" }).click();
// The correct password reveals the actual resume
await expect(anonymous.getByRole("button", { name: "Download PDF" })).toBeVisible();
} finally {
await anonymous.close();
}
});
+27
View File
@@ -0,0 +1,27 @@
import { createSampleResumeFromDashboard, openSidebarSection } from "../fixtures/resume";
import { expect, test } from "../fixtures/test";
test("switches the resume template and persists the choice", async ({ authPage: page }, testInfo) => {
await createSampleResumeFromDashboard(page, testInfo);
await openSidebarSection(page, "Template");
// Sample resumes start on Azurill; its preview button opens the gallery
await page.getByRole("button", { name: "Azurill", exact: true }).click();
const gallery = page.getByRole("dialog", { name: "Template Gallery" });
await expect(gallery).toBeVisible();
const savePromise = page.waitForResponse((response) => {
if (!response.url().includes("/api/rpc")) return false;
if (response.request().method() !== "POST") return false;
if (!response.ok()) return false;
return (response.request().postData() ?? "").includes("bronzor");
});
await gallery.getByRole("img", { name: "Bronzor", exact: true }).click();
await savePromise;
await page.keyboard.press("Escape");
// After a reload the Template section previews the newly selected template
await page.reload();
await openSidebarSection(page, "Template");
await expect(page.getByRole("img", { name: "Bronzor", exact: true })).toBeVisible();
});