Ctrl K
ComunidadGuías…

e2e/approved-part-stock-flow.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { expect, test as base, type Locator, type Page, type TestInfo } from "@playwright/test";
import { createHash, randomUUID } from "node:crypto";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createApp } from "../apps/server/dist/app.js";
import { navigateWorkspace, openProject, openProjectSection } from "./workspace-controls";
import AxeBuilder from "@axe-core/playwright";

const originalName = "Synthetic control board";
const changedName = "Synthetic control board for front panel";
const itemName = "Synthetic bench controller";
const spareName = "Synthetic optional spare board";
const originalNote = "Verify voltage and pinout before using this board.";
const changedNote = "Front panel needs the keyed connector. Verify voltage and pinout before assembly.";
const compatibility = { compatible: "conditional", reason: "Verify voltage and pinout" };
const instructionsFile = "synthetic-control-board-notes.md";
const instructions = "# Synthetic control board notes\n\nCheck the keyed connector and voltage before assembly.\n\nA recorded count does not confirm compatibility.\n";

type BomRecord = { id: string; name: string; requiredQuantity: number; notes?: string; version: number; alternatives: Array<{ itemId: string; compatible: string; reason?: string }> };
type StockRecord = { quantity: number; version: number; evidence: { state: string } };
type GapRecord = { lineId: string; decision: string; status: string; suppliedQuantity: number; candidates: Array<{ itemId: string; compatibility: string }> };
type PartWorkspace = {
  projectName: string;
  itemId: string;
  lineId: string;
  countUrl: string;
  readStock(): Promise<StockRecord>;
  readBom(): Promise<BomRecord[]>;
  readGap(): Promise<GapRecord | undefined>;
};

// A temporary, empty workspace makes these real writes independent of the demo
// server and of any user inventory. Both widths exercise the same actual API.
const test = base.extend<{ partWorkspace: PartWorkspace }>({
  partWorkspace: async ({ page }, use) => {
    const directory = await mkdtemp(join(tmpdir(), "benchledger-part-stock-"));
    const app = await createApp({ demo: false, dataDir: directory, publicBaseUrl: "http://127.0.0.1", logger: false, auth: { sessionSecret: randomUUID().repeat(2) } });
    try {
      const origin = await app.listen({ host: "127.0.0.1", port: 0 });
      await page.goto(origin);
      await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible();
      const post = async <T,>(path: string, data: object): Promise<T> => {
        const csrf = (await page.context().cookies()).find(cookie => cookie.name === "forge_csrf")?.value;
        expect(csrf).toBeTruthy();
        const response = await page.request.post(`${origin}/api/v1${path}`, { headers: { "x-csrf-token": csrf!, "idempotency-key": randomUUID() }, data });
        expect(response.ok(), await response.text()).toBe(true);
        return response.json();
      };
      const read = async <T,>(path: string): Promise<T> => {
        const response = await page.request.get(`${origin}/api/v1${path}`);
        expect(response.ok(), await response.text()).toBe(true);
        return response.json();
      };
      const id = `part-stock-${randomUUID()}`, revisionId = `${id}-r1`, itemId = `${id}-item`;
      const projectName = "Synthetic panel controller";
      await post("/projects/with-initial-revision", { project: { id, name: projectName, status: "building" }, revision: { id: revisionId, name: "Initial", status: "concept", fabricationRoute: "none" } });
      await post("/inventory", { id: itemId, name: itemName, kind: "electronic", quantity: 8, unit: "each", location: "Synthetic drawer A", tags: [], links: [], evidence: { state: "delivered_uncounted", source: "Synthetic test fixture" } });
      const { data: line } = await post<{ data: BomRecord }>(`/project-revisions/${revisionId}/bom`, { name: originalName, requiredQuantity: 2, unit: "each", role: "consumed", itemId, notes: originalNote, optional: false, constraints: {}, alternatives: [{ itemId, ...compatibility }] });
      // A second linked part proves Needed by opens the requested requirement,
      // rather than whichever line happens to be first in the project.
      await post(`/project-revisions/${revisionId}/bom`, { name: spareName, requiredQuantity: 1, unit: "each", role: "consumed", itemId, optional: true, constraints: {}, alternatives: [{ itemId, ...compatibility }] });
      const bytes = Buffer.from(instructions);
      const sha256 = createHash("sha256").update(bytes).digest("hex");
      const { data: upload } = await post<{ data: { id: string } }>("/artifacts/uploads", { projectId: id, projectRevisionId: revisionId, filename: instructionsFile, role: "text", mediaType: "text/markdown", byteSize: bytes.length, sha256 });
      const csrf = (await page.context().cookies()).find(cookie => cookie.name === "forge_csrf")!.value;
      const uploaded = await page.request.put(`${origin}/api/v1/artifacts/uploads/${upload.id}`, { headers: { "x-csrf-token": csrf, "content-type": "application/octet-stream" }, data: bytes });
      expect(uploaded.ok(), await uploaded.text()).toBe(true);
      const { data: artifact } = await post<{ data: { sha256: string } }>(`/artifacts/uploads/${upload.id}/finalize`, {});
      expect(artifact.sha256).toBe(sha256);
      await page.reload();
      await use({
        projectName, itemId, lineId: line.id, countUrl: `${origin}/api/v1/inventory/${itemId}/count`,
        readStock: () => read(`/inventory/${itemId}`),
        readBom: () => read(`/project-revisions/${revisionId}/bom`),
        readGap: async () => (await read<{ lines: GapRecord[] }>(`/project-revisions/${revisionId}/gaps`)).lines.find(gap => gap.lineId === line.id),
      });
    } finally {
      const closed = app.close();
      app.server.closeAllConnections();
      await closed;
      await rm(directory, { recursive: true, force: true });
    }
  },
});

async function editFromOverview(page: Page, workspace: PartWorkspace, changeDraft = true) {
  await openProject(page, workspace.projectName);
  await expect(page.getByRole("tab", { name: "Overview", exact: true })).toHaveAttribute("aria-selected", "true");
  const next = page.getByRole("region", { name: "Next project action", exact: true });
  await expect(next.getByRole("heading", { name: `Check ${originalName}`, exact: true })).toBeVisible();
  await next.getByRole("button", { name: "Review this part", exact: true }).click();
  const editor = page.getByRole("dialog", { name: "Part details", exact: true });
  await expect(editor.getByLabel("Requirement name", { exact: true })).toHaveValue(originalName);
  await expect(editor.getByRole("textbox", { name: "Specification and notes", exact: true })).toHaveValue(originalNote);
  if (!changeDraft) return editor;
  await editor.getByLabel("Requirement name", { exact: true }).fill(changedName);
  await editor.getByLabel("Required quantity", { exact: true }).fill("3");
  await editor.getByRole("textbox", { name: "Specification and notes", exact: true }).fill(changedNote);
  return editor;
}

async function expectPartDraft(editor: Locator) {
  await expect(editor.getByLabel("Requirement name", { exact: true })).toHaveValue(changedName);
  await expect(editor.getByLabel("Required quantity", { exact: true })).toHaveValue("3");
  await expect(editor.getByRole("textbox", { name: "Specification and notes", exact: true })).toHaveValue(changedNote);
}

async function expectConditionalGap(workspace: PartWorkspace) {
  const gap = await workspace.readGap();
  expect(gap).toMatchObject({ decision: "check", status: "inspect_first", suppliedQuantity: 0 });
  expect(gap?.candidates).toEqual(expect.arrayContaining([expect.objectContaining({ itemId: workspace.itemId, compatibility: "conditional" })]));
}

async function capture(page: Page, info: TestInfo, name: string) {
  await page.evaluate(() => document.fonts.ready);
  await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
  const viewport = page.viewportSize()!;
  for (const modal of await page.locator('[role="dialog"][aria-modal="true"]:visible, [role="alertdialog"]:visible').all()) {
    const bounds = (await modal.boundingBox())!;
    expect(bounds.x).toBeGreaterThanOrEqual(0); expect(bounds.y).toBeGreaterThanOrEqual(0);
    expect(bounds.x + bounds.width).toBeLessThanOrEqual(viewport.width + 1);
    expect(bounds.y + bounds.height).toBeLessThanOrEqual(viewport.height + 1);
  }
  await page.screenshot({ path: info.outputPath(`${name}.png`), fullPage: false, animations: "disabled" });
}

test("resizing during a stock detour keeps the active task reachable and retains the part draft", async ({ page, partWorkspace: workspace }, info) => {
  await page.setViewportSize({ width: 1536, height: 1024 });
  const beforeStock = await workspace.readStock();
  const beforeBom = await workspace.readBom();
  const editor = await editFromOverview(page, workspace);
  await editor.getByRole("button", { name: "Check this stock", exact: true }).click();
  const stock = page.getByRole("dialog", { name: itemName, exact: true });
  const quantity = stock.getByLabel("Counted quantity", { exact: true });
  await quantity.fill("7");
  await page.setViewportSize({ width: 390, height: 844 });
  await expect(stock).toBeVisible();
  await expect(editor).toHaveCount(0);
  await expect(quantity).toBeFocused();
  await expect(quantity).toHaveValue("7");
  // A real pointer action catches a suspended parent becoming the top modal.
  await stock.getByRole("button", { name: "Review physical count", exact: true }).click();
  const review = page.getByRole("alertdialog", { name: "Review physical count", exact: true });
  await expect(review).toContainText("7 pieces");
  await review.getByRole("button", { name: "Confirm physical count", exact: true }).focus();
  await page.setViewportSize({ width: 1536, height: 1024 });
  await expect(review.getByRole("button", { name: "Confirm physical count", exact: true })).toBeFocused();
  await review.getByRole("button", { name: "Confirm physical count", exact: true }).click();
  await expect(stock.getByRole("status", { name: "Physical count saved", exact: true })).toContainText("7 pieces");
  await page.setViewportSize({ width: 390, height: 844 });
  await stock.getByRole("button", { name: `Return to ${originalName}`, exact: true }).click();
  await expectPartDraft(editor);
  await expect(editor).toHaveAttribute("aria-modal", "true");
  expect(await workspace.readStock()).toMatchObject({ quantity: 7, version: beforeStock.version + 1 });
  expect(await workspace.readBom()).toEqual(beforeBom);
  await expectConditionalGap(workspace);
  await capture(page, info, "part-draft-after-breakpoint-stock-detour");
});

for (const width of [1536, 390]) {
  test.describe(`approved part stock flow at ${width}px`, () => {
    test.use({ viewport: { width, height: width === 390 ? 844 : 1024 } });

    test("count review returns to the exact edited part without confirming compatibility", async ({ page, partWorkspace: workspace }, info) => {
      test.setTimeout(60_000);
      await page.emulateMedia({ reducedMotion: "reduce" });
      const beforeStock = await workspace.readStock();
      const beforeBom = await workspace.readBom();
      await expectConditionalGap(workspace);
      const editor = await editFromOverview(page, workspace);
      if (width === 1536) {
        await page.locator(".bom-row").getByRole("button", { name: spareName, exact: true }).click();
        const leave = page.getByRole("alertdialog", { name: "Leave without saving?", exact: true });
        await expect(leave).toBeVisible();
        await leave.getByRole("button", { name: "Keep editing", exact: true }).click();
        await expectPartDraft(editor);
      }
      const countWrites: string[] = [];
      page.on("request", request => { if (request.url() === workspace.countUrl && request.method() === "POST") countWrites.push(request.postData() ?? ""); });

      await editor.getByRole("button", { name: "Check this stock", exact: true }).click();
      const stock = page.getByRole("dialog", { name: itemName, exact: true });
      await expect(stock.getByLabel("Counted quantity", { exact: true })).toHaveValue("");
      await stock.getByLabel("Counted quantity", { exact: true }).fill("7");
      await stock.getByRole("button", { name: "Review physical count", exact: true }).click();
      const review = page.getByRole("alertdialog", { name: "Review physical count", exact: true });
      await expect(review).toContainText("This count does not confirm that the item fits a project.");
      await expect(review).toContainText(itemName);
      expect(countWrites).toHaveLength(0);
      expect(await workspace.readStock()).toEqual(beforeStock);
      await capture(page, info, "count-review");
      await review.getByRole("button", { name: "Back to item", exact: true }).click();
      await expect(stock.getByLabel("Counted quantity", { exact: true })).toHaveValue("7");
      expect(countWrites).toHaveLength(0);
      await stock.getByRole("button", { name: "Review physical count", exact: true }).click();
      await review.getByRole("button", { name: "Confirm physical count", exact: true }).click();
      await expect(review).toHaveCount(0);
      await expect(stock.getByText("Physical count saved", { exact: true })).toBeVisible();
      await expect(stock).toContainText("Compatibility with a project is checked separately.");
      expect(countWrites).toHaveLength(1);
      expect(JSON.parse(countWrites[0]!)).toEqual({ quantity: 7 });
      expect(await workspace.readStock()).toMatchObject({ quantity: 7, evidence: { state: "physically_counted" }, version: beforeStock.version + 1 });
      expect(await workspace.readBom()).toEqual(beforeBom);
      await expectConditionalGap(workspace);

      const returnToPart = stock.getByRole("button", { name: `Return to ${originalName}`, exact: true });
      await expect(returnToPart).toBeVisible();
      await returnToPart.scrollIntoViewIfNeeded();
      await capture(page, info, "count-receipt-return-to-exact-part");
      await returnToPart.click();
      await expectPartDraft(editor);
      await expect(editor.getByRole("button", { name: "Check this stock", exact: true })).toBeEnabled();
      await editor.getByRole("button", { name: "Save requirement", exact: true }).click();
      await expect(editor).toHaveCount(0);
      const saved = (await workspace.readBom()).find(line => line.id === workspace.lineId);
      expect(saved).toMatchObject({ name: changedName, requiredQuantity: 3, notes: changedNote, alternatives: [{ itemId: workspace.itemId, ...compatibility }] });
      await expectConditionalGap(workspace);
      await openProjectSection(page, "Parts");
      const row = page.locator(".bom-row").filter({ has: page.getByRole("button", { name: changedName, exact: true }) });
      await expect(row.locator(".status-pill")).toHaveText(/Check/u);
      await row.getByRole("button", { name: changedName, exact: true }).click();
      await expectPartDraft(editor);
      await expect(editor.getByRole("button", { name: "Check this stock", exact: true })).toBeVisible();
      await expect(editor).toContainText(itemName);
      await expect(editor.getByText("Searching all inventory…", { exact: true })).toHaveCount(0);
      const savedNotice = page.getByRole("button", { name: "Dismiss notification", exact: true });
      if (await savedNotice.isVisible()) await savedNotice.click();
      if (width === 1536) {
        await page.locator(".content").evaluate(element => element.scrollTo(0, 0));
        await expect(editor).toHaveAttribute("aria-modal", "false");
        await expect(page.locator('[data-slot="dialog-overlay"]:visible')).toHaveCount(0);
        const bounds = (await editor.boundingBox())!;
        const list = (await page.locator(".project-parts-register").boundingBox())!;
        expect(list.x + list.width).toBeLessThanOrEqual(bounds.x);
        for (const cell of [".bom-quantity", ".bom-available", ".bom-status"]) await expect(row.locator(cell)).toBeInViewport({ ratio: 1 });
        await expect(editor.getByLabel("Required quantity", { exact: true })).toBeInViewport({ ratio: 1 });
        await expect(editor.getByRole("region", { name: "Selected stock candidate", exact: true })).toBeInViewport({ ratio: 1 });
        await expect(editor.getByRole("button", { name: "Check this stock", exact: true })).toBeInViewport({ ratio: 1 });
        await expect(editor.getByText("You’ll return to this part with your edits kept. A count alone does not confirm fit.", { exact: true })).toBeInViewport({ ratio: 1 });
        const check = editor.getByRole("button", { name: "Check this stock", exact: true });
        expect(await check.evaluate(element => { const box = element.getBoundingClientRect(); return element.contains(document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2)); })).toBe(true);
        const a11y = await new AxeBuilder({ page }).include(".project-parts-workspace").withTags(["wcag2a", "wcag2aa", "wcag21aa"]).analyze();
        expect(a11y.violations).toEqual([]);
      }
      await capture(page, info, "parts-selected-detail");
      await editor.getByRole("button", { name: "Cancel", exact: true }).click();
      await expect(editor).toHaveCount(0);

      await navigateWorkspace(page, "Inventory");
      await page.getByRole("textbox", { name: "Search inventory", exact: true }).fill(itemName);
      await page.locator(".inventory-table tbody tr").filter({ hasText: itemName }).locator(".row-open").click();
      const needs = stock.getByRole("region", { name: "Needed by projects", exact: true });
      await expect(needs.getByRole("button", { name: /^Open .* in /u })).toHaveCount(2);
      await needs.getByRole("button", { name: `Open ${spareName} in ${workspace.projectName}`, exact: true }).click();
      await expect(stock).toHaveCount(0);
      await expect(editor.getByLabel("Requirement name", { exact: true })).toHaveValue(spareName);
      await expect(editor.getByLabel("Required quantity", { exact: true })).toHaveValue("1");
      await expect(editor.getByRole("textbox", { name: "Specification and notes", exact: true })).toHaveValue("");
      await editor.getByRole("button", { name: "Cancel", exact: true }).click();
      await expect(editor).toHaveCount(0);

      await openProjectSection(page, "Files");
      for (const name of ["3D print", "Electronics", "CAD & firmware", "Instructions"]) await expect(page.getByRole("region", { name, exact: true })).toBeVisible();
      await page.locator(".content").evaluate(element => element.scrollTo(0, 0));
      await expect(page.getByRole("button", { name: `Details for ${instructionsFile}`, exact: true })).toBeInViewport();
      await page.getByRole("button", { name: `Details for ${instructionsFile}`, exact: true }).click();
      const details = page.getByRole("complementary", { name: `File details: ${instructionsFile}`, exact: true });
      await expect(details).toContainText("Candidate file");
      if (width === 1536) {
        await page.locator(".content").evaluate(element => element.scrollTo(0, 0));
        await expect.poll(() => page.locator(".content").evaluate(element => element.scrollTop)).toBe(0);
        for (const name of ["3D print", "Electronics", "CAD & firmware", "Instructions"]) await expect(page.getByRole("heading", { name, exact: true })).toBeInViewport();
        await expect(details.getByRole("button", { name: "Preview file", exact: true })).toBeInViewport();
        await expect(details.getByRole("button", { name: "Download file", exact: true })).toBeInViewport();
      } else {
        await details.scrollIntoViewIfNeeded();
      }
      await capture(page, info, "files-current-selected-detail");
      await details.getByRole("button", { name: "Preview file", exact: true }).click();
      const preview = page.getByRole("dialog", { name: instructionsFile, exact: true });
      await expect(preview.getByRole("heading", { name: "Synthetic control board notes", exact: true })).toBeVisible();
      await expect(preview).toContainText("A recorded count does not confirm compatibility.");
      await capture(page, info, "files-current-loaded-preview");
      await preview.getByRole("button", { name: "Close preview", exact: true }).click();

      await openProjectSection(page, "Parts");
      await page.getByRole("button", { name: "Add part", exact: true }).click();
      const add = page.getByRole("dialog", { name: "Add a part", exact: true });
      const draftName = "Synthetic panel mounting clips";
      const draftNote = "Check clip width against the panel before sourcing.";
      await add.getByLabel("Part name", { exact: true }).fill(draftName);
      await add.getByLabel("Amount needed", { exact: true }).fill("4");
      await add.getByRole("textbox", { name: "Specification (optional)", exact: true }).fill(draftNote);
      const rejectedCreate = "**/project-revisions/*/bom";
      await page.route(rejectedCreate, route => route.request().method() === "POST"
        ? route.fulfill({ status: 409, contentType: "application/json", json: { error: { code: "version_conflict", message: "The project changed. Your part has not been saved." } } })
        : route.continue());
      await add.getByRole("button", { name: "Add part", exact: true }).click();
      await expect(add.getByRole("alert")).toContainText("Your entries are kept.");
      await expect(add.getByLabel("Part name", { exact: true })).toHaveValue(draftName);
      await expect(add.getByLabel("Amount needed", { exact: true })).toHaveValue("4");
      await expect(add.getByRole("textbox", { name: "Specification (optional)", exact: true })).toHaveValue(draftNote);
      await expect(add.getByRole("button", { name: "Try saving again", exact: true })).toBeInViewport();
      await capture(page, info, "add-part-error-draft-preserved");
      await page.unroute(rejectedCreate);
      await add.getByRole("button", { name: "Cancel", exact: true }).click();
      await page.getByRole("alertdialog", { name: "Leave without saving?", exact: true }).getByRole("button", { name: "Discard changes and leave", exact: true }).click();
      await expect(add).toHaveCount(0);
    });

    test("discarding a nested stock draft preserves the unsaved part draft", async ({ page, partWorkspace: workspace }) => {
      const beforeStock = await workspace.readStock();
      const beforeBom = await workspace.readBom();
      const editor = await editFromOverview(page, workspace);
      let countWrites = 0;
      page.on("request", request => { if (request.url() === workspace.countUrl && request.method() === "POST") countWrites++; });
      await editor.getByRole("button", { name: "Check this stock", exact: true }).click();
      const stock = page.getByRole("dialog", { name: itemName, exact: true });
      await stock.getByLabel("Counted quantity", { exact: true }).fill("6");
      await stock.getByRole("button", { name: `Return to ${originalName}`, exact: true }).click();
      const discard = page.getByRole("alertdialog", { name: "Discard this item draft?", exact: true });
      await expect(discard).toContainText("Discard only the unsaved item changes to return to your requirement.");
      await discard.getByRole("button", { name: "Keep editing", exact: true }).click();
      await expect(stock.getByLabel("Counted quantity", { exact: true })).toHaveValue("6");
      await stock.getByRole("button", { name: `Return to ${originalName}`, exact: true }).click();
      await discard.getByRole("button", { name: "Discard item changes", exact: true }).click();
      await expect(stock).toHaveCount(0);
      await expect(page.getByRole("alertdialog")).toHaveCount(0);
      await expectPartDraft(editor);
      expect(countWrites).toBe(0);
      expect(await workspace.readStock()).toEqual(beforeStock);
      expect(await workspace.readBom()).toEqual(beforeBom);

      await editor.getByRole("button", { name: "Check this stock", exact: true }).click();
      await expect(stock.getByLabel("Counted quantity", { exact: true })).toHaveValue("");
      await stock.getByRole("button", { name: `Return to ${originalName}`, exact: true }).click();
      await expect(page.getByRole("alertdialog")).toHaveCount(0);
      await expectPartDraft(editor);
    });

    test("browser Back retains a nested count and cannot leave an unconfirmed save", async ({ page, partWorkspace: workspace }) => {
      const beforeStock = await workspace.readStock();
      const beforeBom = await workspace.readBom();
      // Keep the requirement unchanged: only the supporting stock task is dirty.
      const editor = await editFromOverview(page, workspace, false);
      const originalUrl = page.url();
      await editor.getByRole("button", { name: "Check this stock", exact: true }).click();
      const stock = page.getByRole("dialog", { name: itemName, exact: true });
      await stock.getByLabel("Counted quantity", { exact: true }).fill("7");
      await page.goBack();
      const leave = page.getByRole("alertdialog", { name: "Leave without saving?", exact: true });
      await expect(leave).toContainText("unsaved changes in stock observation.");
      await expect(page).toHaveURL(originalUrl);
      await leave.getByRole("button", { name: "Keep editing", exact: true }).click();
      await expect(stock.getByLabel("Counted quantity", { exact: true })).toHaveValue("7");
      await expect(page).toHaveURL(originalUrl);
      expect(await workspace.readStock()).toEqual(beforeStock);
      expect(await workspace.readBom()).toEqual(beforeBom);

      const countRequests: Array<{ key: string | undefined; body: string | null }> = [];
      await page.route(workspace.countUrl, async route => {
        countRequests.push({ key: route.request().headers()["idempotency-key"], body: route.request().postData() });
        if (countRequests.length === 1) {
          // The service records the observation, but its acknowledgement is lost.
          const response = await route.fetch();
          expect(response.ok()).toBe(true);
          await route.abort("failed");
        } else await route.continue();
      });
      await stock.getByRole("button", { name: "Review physical count", exact: true }).click();
      const review = page.getByRole("alertdialog", { name: "Review physical count", exact: true });
      await review.getByRole("button", { name: "Confirm physical count", exact: true }).click();
      await expect(review.getByRole("button", { name: "Retry unchanged observation", exact: true })).toBeVisible();
      await page.goBack();
      const pending = page.getByRole("alertdialog", { name: "Finish the pending save", exact: true });
      await expect(pending).toContainText("has not been confirmed");
      await expect(pending.getByRole("button", { name: /Discard/u })).toHaveCount(0);
      await expect(page).toHaveURL(originalUrl);
      await pending.getByRole("button", { name: "Keep editing", exact: true }).click();
      await expect(review).toContainText("7 pieces");
      await expect(review.getByRole("button", { name: "Back to item", exact: true })).toBeDisabled();
      await review.getByRole("button", { name: "Retry unchanged observation", exact: true }).click();
      await expect(stock.getByText("Physical count saved", { exact: true })).toBeVisible();
      expect(countRequests).toHaveLength(2);
      expect(countRequests[0]!.key).toBeTruthy();
      expect(countRequests[1]).toEqual(countRequests[0]);
      expect(await workspace.readStock()).toMatchObject({ quantity: 7, version: beforeStock.version + 1 });
      expect(await workspace.readBom()).toEqual(beforeBom);
      await expectConditionalGap(workspace);
      await stock.getByRole("button", { name: `Return to ${originalName}`, exact: true }).click();
      await expect(editor.getByLabel("Requirement name", { exact: true })).toHaveValue(originalName);
      await expect(editor.getByLabel("Required quantity", { exact: true })).toHaveValue("2");
      await editor.getByRole("button", { name: "Cancel", exact: true }).click();
      await expect(page.getByRole("alertdialog")).toHaveCount(0);
      // The resolved child entry must not leave a stale navigation blocker.
      await page.goBack();
      await expect(page).not.toHaveURL(originalUrl);
      await expect(page.getByRole("alertdialog")).toHaveCount(0);
    });
  });
}