import { expect, test } from "@playwright/test"; import { mediaBackend, PROJECT_ID } from "./fixtures/media-editor-data"; import { captureScreenshot } from "./helpers"; const reviewUrl = (renderId: string) => `/e2e/fixtures/media-editor.html?view=review&projectId=${PROJECT_ID}&renderId=${renderId}`; test("review lists gate blockers that jump the player and shows coverage and auto-fix", async ({ page, }, testInfo) => { const backend = mediaBackend(); await backend.install(page); await page.goto(reviewUrl("render-paid-draft")); const review = page.getByTestId("media-review"); await expect(review.getByTestId("media-gate-status")).toHaveAttribute("data-status", "blocked"); await expect(review.getByTestId("media-autofix")).toContainText("Auto-fix 1/3"); await expect(review.getByTestId("media-autofix")).toContainText("widened the face mask"); // Issues in timeline order, untimed last. const issues = review.getByTestId("media-gate-issue"); await expect(issues).toHaveCount(5); await expect(issues.nth(0)).toContainText("0:02.0"); await expect(issues.nth(0)).toContainText("Explicit nudity above the profile limit."); await expect(issues.nth(1)).toContainText("0:05.0"); await expect(issues.nth(4)).toContainText("Fewer hashtags would read better."); await expect(issues.nth(4).getByRole("button")).toBeDisabled(); // A blocked render has no output; the plan plays from the sources. const player = review.getByTestId("media-source-player"); await expect(review.getByRole("button", { name: "After" })).toHaveCount(0); await issues.nth(1).getByRole("button").click(); await expect(player).toHaveAttribute("data-position-ms", "5000"); await issues.nth(2).getByRole("button").click(); await expect(player).toHaveAttribute("data-position-ms", "9000"); // Coverage per person and mark; a segment seeks too. const coverage = review.getByTestId("media-coverage"); await expect(coverage.getByText("Alex")).toBeVisible(); await expect(coverage.getByText("Sam")).toBeVisible(); await expect(coverage.getByText("Tattoo")).toBeVisible(); await coverage.getByRole("button", { name: /0:10\.0/ }).click(); await expect(player).toHaveAttribute("data-position-ms", "10000"); await captureScreenshot(page, testInfo, "media-review"); }); test("review edits people, marks, and masks, then re-renders the draft", async ({ page, }, testInfo) => { const backend = mediaBackend(); await backend.install(page); await page.goto(reviewUrl("render-paid-draft")); const review = page.getByTestId("media-review"); await expect(review.getByTestId("media-gate-issue")).toHaveCount(5); // Per person: anonymization mode; consent only where the gate asks for it. const people = review.getByTestId("media-review-person"); await expect(people).toHaveCount(2); await review.getByLabel("Anonymization for Alex").selectOption("pixelate"); await expect .poll(() => backend.calls.find((call) => call.procedure === "people/update")?.input) .toEqual({ personId: "person-a", anonymizeByDefault: "pixelate" }); await expect(people.nth(0).getByRole("button", { name: "Record consent" })).toHaveCount(0); await expect(people.nth(1).getByRole("button", { name: "Record consent" })).toBeVisible(); // Per mark: a low quality score offers the suggested fallback. const mark = review.getByTestId("media-review-mark"); await expect(mark).toContainText("42%"); await mark.getByRole("button", { name: "Use Cover" }).click(); await expect(mark.getByLabel("Treatment")).toHaveValue("cover"); await expect(mark.getByRole("button", { name: "Use Cover" })).toHaveCount(0); // Mark a spot on the frame. await review.getByRole("button", { name: "Mark", exact: true }).click(); await review.getByRole("button", { name: "Mark this spot" }).click({ position: { x: 20, y: 20 } }); await expect .poll(() => backend.calls.find((call) => call.procedure === "marks/create")?.input) .toMatchObject({ kind: "custom", assetId: "asset-1", treatment: "blur" }); // Draw a mask and drag it. await review.getByRole("button", { name: "Mask", exact: true }).click(); const mask = review.getByTestId("media-mask"); await expect(mask).toBeVisible(); const box = await mask.boundingBox(); if (!box) throw new Error("mask not laid out"); await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); await page.mouse.down(); await page.mouse.move(box.x + box.width / 2 + 40, box.y + box.height / 2 + 10, { steps: 4 }); await page.mouse.up(); await captureScreenshot(page, testInfo, "media-review-mask"); await review.getByRole("button", { name: "Save" }).click(); await expect(review.getByRole("button", { name: "Draft" })).toBeVisible(); const submitted = backend.calls.find((call) => call.procedure === "plans/submit")?.input as { edl: { redactions: { regions: Array<{ clipId: string; keyframes: Array<{ box: { x: number } }> }>; }; }; }; const region = submitted.edl.redactions.regions[0]; expect(region?.clipId).toBe("clip-1"); expect(region?.keyframes[0]?.box.x).toBeGreaterThan(0.4); // Re-rendering opens the new render, which polls while it runs. backend.holdRenders(1); await review.getByRole("button", { name: "Draft" }).click(); await expect(review.getByText(/Rendering \d+%/)).toBeVisible(); await expect(review.getByTestId("media-gate-status")).toHaveAttribute("data-status", "pass", { timeout: 15_000, }); // Consent is recorded on the people screen, never here. await page.goto(reviewUrl("render-paid-draft")); await page.getByRole("button", { name: "Record consent" }).click(); await expect(page.getByTestId("media-other-view")).toHaveAttribute("data-view", "people"); });