Ctrl K
ComunidadGuías…

e2e/approved-supporting-screens.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, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createApp } from "../apps/server/dist/app.js";
import { clickProjectAction, navigateWorkspace, openBuildTool, openProject, openProjectDetails, openProjectSection } from "./workspace-controls";

const projectName = "Synthetic enclosure workshop";
const assemblyFile = "synthetic-assembly.step";
const boardFile = "synthetic-board.kicad_pcb";

// Follow the isolated durable fixture used by the part-stock journeys. These
// records and artifact bytes never touch the shared demo or a user workspace.
const test = base.extend<{ supportingWorkspace: string }>({
  supportingWorkspace: async ({ page }, use) => {
    const directory = await mkdtemp(join(tmpdir(), "benchledger-supporting-screens-"));
    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 headers = async () => ({ "x-csrf-token": (await page.context().cookies()).find(cookie => cookie.name === "forge_csrf")!.value, "idempotency-key": randomUUID() });
      const write = async (path: string, data: object, method: "POST" | "PUT" = "POST") => {
        const response = await page.request.fetch(`${origin}/api/v1${path}`, { method, headers: await headers(), data });
        expect(response.ok(), await response.text()).toBe(true);
        return response.json();
      };
      const id = `supporting-${randomUUID()}`, revision = `${id}-r1`, itemId = `${id}-fasteners`;
      await write("/projects/with-initial-revision", { project: { id, name: projectName, status: "building", description: "Synthetic visual evidence fixture." }, revision: { id: revision, name: "Initial enclosure", status: "concept", fabricationRoute: "printed" } });
      await write("/inventory", { id: itemId, name: "Synthetic M3 mounting fasteners", kind: "fastener", quantity: 10, unit: "each", location: "Synthetic drawer A", tags: [], links: [], evidence: { state: "physically_counted", source: "Synthetic fixture only" } });
      const { data: line } = await write(`/project-revisions/${revision}/bom`, { name: "Enclosure mounting fasteners", itemId, requiredQuantity: 4, unit: "each", role: "consumed", optional: false, constraints: {}, alternatives: [] });
      await write(`/project-revisions/${revision}/reservations`, { lineId: line.id, itemId, quantity: 4 });
      const { data: work } = await write(`/projects/${id}/workstreams`, { name: "Enclosure fit and wiring", kind: "assembly" });
      await write(`/projects/${id}/workstreams/${work.item.id}/assignment`, { expectedVersion: 0, status: "in_progress", notes: "Review connector clearance and fastening order before physical assembly." }, "PUT");

      const upload = async (filename: string, role: string, mediaType: string) => {
        const bytes = await readFile(`packages/artifacts/testfiles/${filename}`);
        const sha256 = createHash("sha256").update(bytes).digest("hex");
        const { data: session } = await write("/artifacts/uploads", { projectId: id, projectRevisionId: revision, filename, role, mediaType, byteSize: bytes.length, sha256 });
        const response = await page.request.put(`${origin}/api/v1/artifacts/uploads/${session.id}`, { headers: { ...await headers(), "content-type": "application/octet-stream" }, data: bytes });
        expect(response.ok(), await response.text()).toBe(true);
        return (await write(`/artifacts/uploads/${session.id}/finalize`, {})).data;
      };
      const cad = await upload(assemblyFile, "step", "model/step");
      await upload(boardFile, "cad_source", "application/x-kicad-pcb");
      await write(`/projects/${id}/revisions/${revision}/build-plan`, {
        expectedVersion: 0, name: "Enclosure plate plan", parts: [{ id: "enclosure-base", name: "Enclosure base", quantity: 2, artifactId: cad.id }],
        plates: [{ id: "enclosure-plate", name: "Base plate", copies: 1, parts: [{ partId: "enclosure-base", quantity: 2 }], materials: [], minutes: 80 }],
        notes: "Check printer, material and supports in the slicer before printing.",
      }, "PUT");
      await page.reload();
      await use(id);
    } finally {
      const closing = app.close();
      app.server.closeAllConnections();
      await closing;
      await rm(directory, { recursive: true, force: true });
    }
  },
});

test.describe.configure({ timeout: 120_000 });

async function capture(page: Page, info: TestInfo, name: string, focus?: Locator) {
  if (focus) { await expect(focus).toBeVisible(); await focus.scrollIntoViewIfNeeded(); }
  await expect(page.getByText("Searching owned inventory…", { exact: true })).toHaveCount(0);
  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"]: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);
    expect(await modal.evaluate(element => element.scrollWidth <= element.clientWidth + 1)).toBe(true);
  }
  await page.screenshot({ path: info.outputPath(`${name}.png`), fullPage: false, animations: "disabled" });
}

for (const width of [1536, 390]) {
  test(`approved supporting screens load and fit at ${width}px`, async ({ page, supportingWorkspace }, info) => {
    await page.setViewportSize({ width, height: width === 390 ? 844 : 1024 });
    await page.emulateMedia({ reducedMotion: "reduce" });
    await openProject(page, projectName);
    expect(page.url()).toContain(supportingWorkspace);

    await clickProjectAction(page, "Edit project");
    const edit = page.getByRole("dialog", { name: "Edit project", exact: true });
    await expect(edit.getByLabel("Project name", { exact: true })).toHaveValue(projectName);
    await capture(page, info, "edit-project");
    await edit.getByRole("button", { name: "Cancel", exact: true }).click();
    await clickProjectAction(page, "New revision");
    const revision = page.getByRole("dialog", { name: `New revision for ${projectName}`, exact: true });
    await expect(revision.getByLabel("Revision name", { exact: true })).toBeEditable();
    await capture(page, info, "new-revision");
    await revision.getByRole("button", { name: "Cancel", exact: true }).click();
    await clickProjectAction(page, "Revision history");
    const history = page.getByRole("dialog", { name: "Revision history", exact: true });
    await history.getByRole("button", { name: "Read revision 1: Initial enclosure", exact: true }).click();
    await expect(history.getByRole("region", { name: "Read-only revision snapshot" })).toContainText("Enclosure mounting fasteners");
    await capture(page, info, "revision-history");
    await history.getByRole("button", { name: "Close dialog", exact: true }).click();

    await openProjectDetails(page);
    await page.locator(".build-approach-card").getByRole("button", { name: "Change build approach", exact: true }).click();
    const approach = page.getByRole("dialog", { name: "Edit build approach", exact: true });
    await expect(approach.getByRole("radio", { name: /^3D-print parts/u })).toBeChecked();
    await capture(page, info, "edit-build-approach");
    await approach.getByRole("button", { name: "Cancel", exact: true }).click();
    await openBuildTool(page, "print");
    await expect(page.locator(".build-planning")).toContainText("80 recorded minutes");
    await capture(page, info, "build-plate-plan", page.getByRole("heading", { name: "Enclosure plate plan", exact: true }));
    await openBuildTool(page, "tasks");
    await page.getByRole("button", { name: "Enclosure fit and wiring · In progress", exact: true }).click();
    await expect(page.getByRole("textbox", { name: "Task group notes", exact: true })).toHaveValue("Review connector clearance and fastening order before physical assembly.");
    await capture(page, info, "build-task-group", page.getByRole("heading", { name: "Task groups", exact: true }));

    await openProjectSection(page, "Record used stock");
    await expect(page.locator(".reconciliation-shell")).toContainText("Enclosure mounting fasteners");
    await page.locator(".content").evaluate(element => element.scrollTo(0, 0));
    await expect.poll(() => page.locator(".content").evaluate(element => element.scrollTop)).toBe(0);
    await expect(page.getByRole("list", { name: "Build sequence" }).getByRole("heading", { name: "Record actual use", exact: true })).toBeInViewport();
    await capture(page, info, "build-review-journey");
    await page.getByRole("button", { name: "Add result", exact: true }).click();
    await page.getByLabel("What happened", { exact: true }).selectOption("consumed");
    await page.getByLabel("Quantity for result 1", { exact: true }).fill("4");
    await page.getByLabel("How did you check for result 1", { exact: true }).selectOption("physically_counted");
    await capture(page, info, "record-used-stock", page.getByLabel("What happened", { exact: true }));
    await page.getByRole("button", { name: "Review changes", exact: true }).click();
    await expect(page.locator(".reconciliation-preview-details")).toContainText("stock record");
    await capture(page, info, "stock-change-review", page.getByRole("heading", { name: "Review stock changes", exact: true }));
    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);
      const journey = page.getByRole("list", { name: "Build sequence" });
      const names = ["Check parts", "Prepare files", "Assemble", "Verify the build", "Record actual use"];
      for (const name of names) {
        const heading = journey.getByRole("heading", { name, exact: true });
        await expect(heading).toBeInViewport();
      }
      await expect(journey.locator('[aria-current="step"]')).toContainText("Review stock changes");
      await expect(journey.getByText("Review stock changes", { exact: true })).toBeInViewport({ ratio: 1 });
      const journeyBox = (await journey.boundingBox())!;
      const reviewBox = (await page.locator(".reconciliation-shell").boundingBox())!;
      expect(journeyBox.x + journeyBox.width).toBeLessThan(reviewBox.x);
      await capture(page, info, "stock-change-review-document-top");
    }
    await page.getByRole("button", { name: "Apply stock changes", exact: true }).click();
    const confirmation = page.getByRole("dialog", { name: "Apply these changes?", exact: true });
    await expect(confirmation.getByRole("button", { name: "Apply stock changes", exact: true })).toBeEnabled();
    const finalEffect = confirmation.getByRole("region", { name: "Final stock effect" });
    await expect(finalEffect).toContainText("Used 4 each");
    await expect(finalEffect).toContainText("On hand6 eachAvailable6 each");
    await expect(confirmation.getByRole("button", { name: "Review 2 separate stock entries" })).toHaveAttribute("aria-expanded", "false");
    for (const fact of [finalEffect.getByText("Used 4 each", { exact: true }), ...await finalEffect.getByText("6 each", { exact: true }).all(), confirmation.getByRole("button", { name: "Apply stock changes", exact: true })]) {
      await expect(fact).toBeInViewport({ ratio: 1 });
    }
    await capture(page, info, "stock-change-confirmation");
    // Capture the final review without consuming the fixture stock.
    await confirmation.getByRole("button", { name: "Go back", exact: true }).click();

    await openProjectSection(page, "PCB");
    await page.getByRole("combobox", { name: "Board source", exact: true }).selectOption({ label: `${boardFile} · r01` });
    await page.getByRole("button", { name: "Open PCB", exact: true }).click();
    await expect(page.locator(".pcb-workspace canvas")).toBeVisible();
    await expect(page.locator(".assembly-dimensions")).toContainText("36.0 × 24.0");
    await capture(page, info, "pcb-top", page.locator(".pcb-workspace canvas"));
    await page.getByRole("button", { name: "3D", exact: true }).click();
    await page.getByLabel("Find a component or part").fill("R1");
    await page.getByRole("button", { name: "R1 · footprint outline", exact: true }).click();
    await expect(page.getByRole("heading", { name: "R1", exact: true })).toBeVisible();
    await capture(page, info, "pcb-component", page.getByRole("heading", { name: "R1", exact: true }));

    await openProjectSection(page, "Assembly");
    await page.getByLabel(assemblyFile, { exact: true }).check();
    await page.getByRole("button", { name: "Open assembly", exact: true }).click();
    await expect(page.locator(".assembly-canvas canvas")).toBeVisible();
    await expect(page.locator(".assembly-dimensions")).toContainText("40.0 × 30.0 × 17.0 mm");
    await page.getByRole("button", { name: "Exploded", exact: true }).click();
    await capture(page, info, "assembly-exploded", page.locator(".assembly-canvas canvas"));
    await page.getByRole("button", { name: "Lid", exact: true }).click();
    await page.getByRole("button", { name: "Edit assembly", exact: true }).click();
    await expect(page.getByLabel("Fixing notes")).toBeEditable();
    await capture(page, info, "assembly-part-details", page.getByLabel("Material", { exact: true }));
    await navigateWorkspace(page, "Settings");
    await page.getByRole("alertdialog", { name: "Leave without saving?", exact: true }).getByRole("button", { name: "Discard changes and leave", exact: true }).click();

    await page.getByRole("button", { name: /^Categories · /u }).click();
    await expect(page.locator(".category-tree-group").first()).toBeVisible();
    await capture(page, info, "settings-categories", page.getByRole("heading", { name: "Manage inventory categories", exact: true }));
    await page.getByRole("button", { name: /^Categories · /u }).click();
    await expect(page.getByRole("form", { name: "Enable workspace password", exact: true })).toBeVisible();
    await capture(page, info, "settings-access", page.getByRole("heading", { name: "Workspace access", exact: true }));
    await page.getByRole("button", { name: "Connection and agent access", exact: true }).click();
    await expect(page.locator(".settings-system-content .connection-badge")).toContainText("Connected");
    await capture(page, info, "settings-connection", page.getByRole("heading", { name: "Workspace connection", exact: true }));
    await page.getByRole("switch", { name: "Technical details", exact: true }).click();
    await expect(page.getByRole("heading", { name: "Private API", exact: true })).toBeVisible();
    await capture(page, info, "settings-system-details", page.getByRole("heading", { name: "Private API", exact: true }));
    await page.getByRole("button", { name: "For agents", exact: true }).click();
    await expect(page.getByRole("heading", { name: "Agent workspace context", exact: true })).toBeVisible();
    await expect(page.getByRole("heading", { name: "How agents should work", exact: true })).toBeVisible();
    await capture(page, info, "agent-capabilities", page.getByRole("heading", { name: "Agent workspace context", exact: true }));
  });
}