import { expect } from "@playwright/test"; import { createJourneyScenario } from "../apps/backend/src/database/seeds"; import { loggedTest } from "./logged-test"; import { ensureTeamOwner, screenshot, waitForDiffListToSettle } from "./util"; loggedTest( "reviews a change against the journey it happened in", async ({ page, auth, team, project }) => { await ensureTeamOwner({ team: team.team, user: auth.user }); const { build } = await createJourneyScenario({ projectId: project.id }); await page.goto( `/${team.account.slug}/${project.name}/builds/${build.number}`, ); // The button is clickable before the diffs land, and then it has nothing // to open: wait for the list. await expect( page.getByRole("button", { name: "checkout/confirmation.png" }), ).toBeVisible(); await page .getByRole("button", { name: /^(Start review|Browse snapshots)/ }) .click(); // The review opens on the biggest change, which is the last step of the // purchase — so the journey is there to be opened, and its order is // already known not to be the score's. await expect( page.getByRole("heading", { name: "checkout/confirmation.png" }), ).toBeVisible(); const toggle = page.getByRole("button", { name: "Journey" }); await expect(toggle).toBeVisible(); await toggle.click(); // The whole journey, in the order the test walked it — which is neither // the alphabet (cart, confirmation, payment, shipping) nor the diff score // (confirmation, payment). Cart and shipping did not change and are in it // all the same: a change reads against the journey it happened in. const drawer = page.getByTestId("journey-drawer"); await expect(drawer.getByRole("button")).toHaveText([ "1 · cart", "2 · shipping", "3 · payment", "4 · confirmation", ]); // The label is cut to fit, so it hands the whole key over on hover; the // dot has no legend anywhere, so it explains itself. // Base UI portals its tooltip out of the trigger and gives the box // `role="presentation"`, so the portal is what there is to assert on. const tooltip = page.locator("[data-base-ui-portal]"); await expect(async () => { await page.mouse.move(0, 0); await drawer.getByText("2 · shipping").hover(); await expect(tooltip).toHaveText("checkout/shipping", { timeout: 2_000 }); }).toPass(); await expect(async () => { await page.mouse.move(0, 0); await drawer.getByTestId("journey-step-attention").first().hover(); await expect(tooltip).toHaveText("Needs review: Changed", { timeout: 2_000, }); }).toPass(); await page.mouse.move(0, 0); // Jumping to a step opens it, unchanged included. await drawer.getByRole("button", { name: "1 · cart" }).click(); await expect( page.getByRole("heading", { name: "checkout/cart.png" }), ).toBeVisible(); await expect(drawer.locator("[aria-current='step']")).toHaveText( "1 · cart", ); // Each jump scrolls the snapshot list to the step, and the next jump reads // that scroll to decide whether it has to move at all. Settle between them // or the walk below rests wherever the animation happened to be. await waitForDiffListToSettle(page); // ⇧→ walks to the next step, ⇧← back — across status sections. await page.keyboard.press("Shift+ArrowRight"); await expect( page.getByRole("heading", { name: "checkout/shipping.png" }), ).toBeVisible(); await waitForDiffListToSettle(page); await page.keyboard.press("Shift+ArrowLeft"); await expect( page.getByRole("heading", { name: "checkout/cart.png" }), ).toBeVisible(); await waitForDiffListToSettle(page); await screenshot(page, "build-journey-drawer", { replacements: { [team.account.slug]: "acme", }, }); // The click above left focus in the strip, and from there plain ← / → // walk it instead. Focus goes along, so the next arrow starts from the // step on screen. await page.keyboard.press("ArrowRight"); await expect( page.getByRole("heading", { name: "checkout/shipping.png" }), ).toBeVisible(); await waitForDiffListToSettle(page); await page.keyboard.press("ArrowRight"); await expect( page.getByRole("heading", { name: "checkout/payment.png" }), ).toBeVisible(); await waitForDiffListToSettle(page); await page.keyboard.press("ArrowRight"); await expect( page.getByRole("heading", { name: "checkout/confirmation.png" }), ).toBeVisible(); await waitForDiffListToSettle(page); await page.keyboard.press("ArrowLeft"); await expect( page.getByRole("heading", { name: "checkout/payment.png" }), ).toBeVisible(); const payment = drawer.getByRole("button", { name: "3 · payment" }); await expect(payment).toBeFocused(); await waitForDiffListToSettle(page); // Read once the list has settled, long after the build hotkeys would have // run had the ← reached them: they would have shown the baseline. const baseline = page.getByRole("button", { name: "Baseline", exact: true, }); await expect(baseline).toHaveAttribute("aria-pressed", "false"); // Out of the strip, ← / → switch the view mode again and the step stays. await payment.blur(); const changes = page.getByRole("button", { name: "Changes", exact: true }); await page.keyboard.press("ArrowLeft"); await expect(baseline).toHaveAttribute("aria-pressed", "true"); await page.keyboard.press("ArrowRight"); await expect(changes).toHaveAttribute("aria-pressed", "true"); await expect( page.getByRole("heading", { name: "checkout/payment.png" }), ).toBeVisible(); // A test that captures a single screen is regular visual testing, not a // journey: nothing to open, and nothing offering to. await page .locator("[role='button'][data-index]") .filter({ hasText: "account/settings.png" }) .click(); await expect( page.getByRole("heading", { name: "account/settings.png" }), ).toBeVisible(); await expect(page.getByTestId("journey-drawer")).toHaveCount(0); await expect(page.getByRole("button", { name: "Journey" })).toHaveCount(0); }, ); loggedTest( "walks the journey on screen when a step is marked", async ({ page, auth, team, project }) => { await ensureTeamOwner({ team: team.team, user: auth.user }); const { build } = await createJourneyScenario({ projectId: project.id }); await page.goto( `/${team.account.slug}/${project.name}/builds/${build.number}`, ); await expect( page.getByRole("button", { name: "checkout/confirmation.png" }), ).toBeVisible(); await page .getByRole("button", { name: /^(Start review|Browse snapshots)/ }) .click(); // The list goes confirmation, the account settings test, payment; the // journey puts payment before confirmation. const confirmationHeading = page.getByRole("heading", { name: "checkout/confirmation.png", }); const settingsHeading = page.getByRole("heading", { name: "account/settings.png", }); await expect(confirmationHeading).toBeVisible(); // With the journey closed, nothing on screen says the review follows it: // marking moves down the list. await page.keyboard.press("y"); await expect(settingsHeading).toBeVisible(); await page.keyboard.press("ControlOrMeta+z"); await expect(confirmationHeading).toBeVisible(); // With it open, marking moves on to the step still to review, from the // last step back to the ones before it. await page.getByRole("button", { name: "Journey" }).click(); await page.keyboard.press("y"); await expect( page.getByRole("heading", { name: "checkout/payment.png" }), ).toBeVisible(); await expect( page.getByTestId("journey-drawer").locator("[aria-current='step']"), ).toHaveText("3 · payment"); // Cart and shipping did not change, so the journey is reviewed: the review // goes back to the diff it jumped over, which the list now has above. await page.keyboard.press("y"); await expect(settingsHeading).toBeVisible(); }, );