Pages declare brief tours in code, keyed to stable data-tour anchors. The layout resolves each visitor's access signals (intelligence and logistics qualifications, admin-panel access, feature flags) and step filtering hides what a user cannot reach, mirroring the sidebar. A tour auto-starts the first time a browser visits its page; dismissals are remembered per browser in localStorage. The header gains a replay control for on-demand runs on toured routes, the tour card gains Skip all tours on this device to opt a browser out of auto-starts, and the Account page holds the re-enable toggle. The card and spotlight get a distinct overlay treatment (glow, accent strip, outlined target).
95 lines
4.2 KiB
TypeScript
95 lines
4.2 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
/**
|
|
* Guided tour smoke: auto-start on first visit, per-browser dismissal via
|
|
* localStorage, and the header replay control. Each test gets a fresh browser
|
|
* context, so localStorage starts empty: exactly the "first visit" state.
|
|
*/
|
|
|
|
/** Login and wait for the redirect to land (cold dev-server compiles are slow). */
|
|
async function login(page: Page): Promise<void> {
|
|
await page.goto("/login");
|
|
await page.getByLabel("Username").fill("dev");
|
|
await page.getByLabel("Password").fill("Test123");
|
|
await page.getByRole("button", { name: "Log in" }).click();
|
|
await page.waitForURL((url) => !url.pathname.startsWith("/login"), { timeout: 90_000 });
|
|
}
|
|
|
|
test.describe("Guided tours", () => {
|
|
test("auto-starts on first visit, dismisses, and never auto-starts again", async ({ page }) => {
|
|
await login(page);
|
|
|
|
// Landing on the dashboard starts its tour (first visit for this browser).
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
await expect(page.locator("[data-tour-card]")).toContainText("1 / 3");
|
|
|
|
// Walk the tour to the end: dismissal is remembered.
|
|
await page.getByRole("button", { name: "Next", exact: true }).click();
|
|
await expect(page.locator("[data-tour-card]")).toContainText("2 / 3");
|
|
await page.getByRole("button", { name: "Next", exact: true }).click();
|
|
await expect(page.locator("[data-tour-card]")).toContainText("3 / 3");
|
|
await page.getByRole("button", { name: "Done" }).click();
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
|
|
const dismissals = await page.evaluate(() =>
|
|
window.localStorage.getItem("ptf:tour:dismissals"),
|
|
);
|
|
expect(dismissals).not.toBeNull();
|
|
expect(JSON.parse(dismissals ?? "{}")).toHaveProperty("dashboard");
|
|
|
|
// Reload: the tour does NOT auto-start again.
|
|
await page.reload();
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
});
|
|
|
|
test("replay control re-runs the tour after dismissal", async ({ page }) => {
|
|
await login(page);
|
|
|
|
// Dismiss the auto-started tour immediately.
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
|
|
// The header replay control brings it back on demand.
|
|
await page.getByRole("button", { name: "Replay page tour" }).click();
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
await expect(page.locator("[data-tour-card]")).toContainText("Dashboard");
|
|
});
|
|
|
|
test("skip all opts the browser out; the account toggle re-enables", async ({ page }) => {
|
|
await login(page);
|
|
|
|
// Opt out from the tour card itself.
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
await page.getByRole("button", { name: "Skip all tours on this device" }).click();
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
expect(await page.evaluate(() => window.localStorage.getItem("ptf:tours:opted-out"))).toBe("1");
|
|
|
|
// Another toured page no longer auto-starts...
|
|
await page.goto("/wiki");
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
|
|
// ...but replay stays available as the explicit opt-in.
|
|
await page.getByRole("button", { name: "Replay page tour" }).click();
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
await page.getByRole("button", { name: "Skip", exact: true }).click();
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
|
|
// Re-enable from the account page (the switch reads off after the opt-out),
|
|
// then a never-visited toured page auto-starts again.
|
|
await page.goto("/account");
|
|
const toursSwitch = page.getByRole("switch", { name: "Auto-start page tours" });
|
|
await expect(toursSwitch).toBeVisible();
|
|
await toursSwitch.click();
|
|
await page.goto("/awards");
|
|
await expect(page.locator("[data-tour-card]")).toBeVisible();
|
|
});
|
|
|
|
test("pages without tours show no replay control and no overlay", async ({ page }) => {
|
|
await login(page);
|
|
|
|
await page.goto("/account");
|
|
await expect(page.locator("[data-tour-card]")).toHaveCount(0);
|
|
await expect(page.getByRole("button", { name: "Replay page tour" })).toHaveCount(0);
|
|
});
|
|
});
|