Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NLSCTnhge4umMnemGXWr1w
118 lines
5.6 KiB
TypeScript
118 lines
5.6 KiB
TypeScript
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");
|
|
});
|