import { expect } from "@playwright/test"; import { test } from "../fixtures"; import { registerAndLogin } from "../helpers"; /** Navigates to the rewards cards page. */ async function gotoRewards(page: import("@playwright/test").Page) { await page.goto("/rewards"); await expect(page.locator("h1")).toContainText("Rewards cards"); } /** * Adds a rewards card and lands back on the /rewards page with it rendered. */ async function addCard( page: import("@playwright/test").Page, storeName: string, number: string, symbology?: string, ) { await page.fill("#store-name", storeName); await page.fill("#card-number", number); if (symbology) { await page.selectOption("#symbology", symbology); } await page.click('button:has-text("Add card")'); await expect(page).toHaveURL(/\/rewards$/); const card = page.locator(".rewards-card").filter({ hasText: storeName }); await expect(card).toBeVisible(); return card; } test("the rewards page shows an empty state before any cards are added", async ({ page, }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await expect(page.locator(".empty-state")).toContainText("No rewards cards yet"); await expect(page.locator(".rewards-card")).toHaveCount(0); }); test("the Rewards link is available in the site navigation", async ({ page }) => { await registerAndLogin(page, "alice@example.com"); const nav = page.locator(".site-nav"); await expect(nav.locator('a[href="/rewards"]')).toHaveText("Rewards"); }); test("a user can add a rewards card and see its barcode", async ({ page }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); const card = await addCard(page, "Kroger", "606171584511340224537"); // The card renders an inline SVG barcode and the store's card number. await expect(card.locator(".rewards-barcode svg")).toBeVisible(); await expect(card.locator(".rewards-number")).toHaveText("606171584511340224537"); // The barcode should be included via the embedded SVG (Code 128 set B→C mix), // not rendered client-side from scratch by an image. const svg = card.locator(".rewards-barcode svg"); await expect(svg).toHaveAttribute("viewBox", /\d+ \d+/); }); test("a user can add a card as Code 39 and see its barcode", async ({ page }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); const card = await addCard(page, "Safeway", "ABC123", "code39"); await expect(card.locator(".rewards-barcode svg")).toBeVisible(); }); test("multiple rewards cards are each rendered with their own barcode", async ({ page, }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await addCard(page, "Kroger", "606171584511340224537"); await addCard(page, "Safeway", "012345678901"); await expect(page.locator(".rewards-card")).toHaveCount(2); await expect(page.locator(".rewards-card").filter({ hasText: "Kroger" })).toBeVisible(); await expect(page.locator(".rewards-card").filter({ hasText: "Safeway" })).toBeVisible(); }); test("removing a rewards card prompts for confirmation and deletes on accept", async ({ page, }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await addCard(page, "Kroger", "606171584511340224537"); await expect(page).toHaveURL(/\/rewards$/); await expect(page.locator(".rewards-card")).toHaveCount(1); page.on("dialog", (dialog) => dialog.accept()); await page.click('button:has-text("Remove")'); // The card is deleted and the empty state returns. await expect(page.locator(".rewards-card")).toHaveCount(0); await expect(page.locator(".empty-state")).toContainText("No rewards cards yet"); }); test("cancelling the remove confirmation keeps the rewards card", async ({ page, }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await addCard(page, "Kroger", "606171584511340224537"); await expect(page.locator(".rewards-card")).toHaveCount(1); page.on("dialog", (dialog) => dialog.dismiss()); await page.click('button:has-text("Remove")'); // The card must remain after cancelling. await expect(page.locator(".rewards-card")).toHaveCount(1); await expect(page.locator(".rewards-card").filter({ hasText: "Kroger" })).toBeVisible(); }); test("a user can open a single-card scan view with only one barcode", async ({ page, }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await addCard(page, "Kroger", "606171584511340224537"); await addCard(page, "Safeway", "012345678901"); // Click the Kroger card's barcode to open its focused scan view. await page .locator(".rewards-card") .filter({ hasText: "Kroger" }) .locator(".rewards-card-link") .click(); await expect(page).toHaveURL(/\/rewards\/\d+/); // Only one barcode is rendered on the scan page. await expect(page.locator(".scan-barcode svg")).toHaveCount(1); await expect(page.locator(".scan-store")).toHaveText("Kroger"); await expect(page.locator(".scan-card .rewards-number")).toHaveText( "606171584511340224537", ); // The wake-lock script is loaded on the scan page. await expect(page.locator('script[src*="rewards.js"]')).toHaveCount(1); // Back link returns to the list. await page.click('.scan-back'); await expect(page).toHaveURL(/\/rewards$/); }); test("a scan view for another user's card is not accessible", async ({ page }) => { await registerAndLogin(page, "alice@example.com"); await gotoRewards(page); await addCard(page, "Kroger", "606171584511340224537"); // Grab the card id from the URL of the scan view. await page .locator(".rewards-card-link") .click(); const url = page.url(); const cardId = url.split("/").pop(); // Sign out and sign in as a different user. await page.click('button:has-text("Sign out")'); await registerAndLogin(page, "bob@example.com"); // Bob cannot view Alice's card. await page.goto(`/rewards/${cardId}`); await expect(page.locator(".scan-barcode svg")).toHaveCount(0); });