import { expect, type Page, type Route, test } from "@playwright/test"; import { captureScreenshot, completeOnboarding, rpc, signup } from "./helpers"; /* * Media Studio library, collection, people and results views on the scripted * runtime (media emulators enabled). The library is seeded through the device * source RPCs with synthetic metadata; people and results need analysis and * publications the scripted stack does not produce, so those RPCs are stubbed * with synthetic records. Every name and value below is fake. */ type Json = Record; const DAY_MS = 24 * 60 * 60 * 1000; function syntheticItems(count: number) { const start = Date.UTC(2026, 4, 2, 9); const cities = [ { lat: 52.52, lon: 13.4 }, { lat: 48.14, lon: 11.58 }, { lat: 53.55, lon: 9.99 }, ]; return Array.from({ length: count }, (_, index) => { const session = index % 3; const city = cities[session] ?? cities[0]; const photo = index % 5 === 4; return { externalId: `e2e-clip-${index}`, kind: photo ? "photo" : "video", capturedAt: new Date(start + session * 3 * DAY_MS + index * 60_000).toISOString(), durationMs: photo ? null : 4000 + (index % 4) * 1000, width: 1920, height: 1080, fps: photo ? null : 30, gpsLat: city?.lat ?? null, gpsLon: city?.lon ?? null, deviceModel: null, albumNames: [], isFavorite: index % 7 === 0, fileName: `clip-${index}.${photo ? "jpg" : "mov"}`, }; }); } async function fulfill(route: Route, json: unknown) { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ json }), }); } async function stub(page: Page, procedure: string, respond: (input: Json) => unknown) { await page.route(`**/rpc/${procedure}`, async (route) => { const body = (route.request().postDataJSON() ?? {}) as { json?: Json }; await fulfill(route, respond(body.json ?? {})); }); } const now = "2026-10-01T12:00:00.000Z"; function person(id: string, name: string, overrides: Json = {}) { return { id, displayName: name, isSelf: false, confirmed: false, anonymizeByDefault: "none", consentRecordedAt: null, consentArtifactId: null, ageVerifiedAt: null, ageVerificationMethod: "none", faceTrackCount: 3, coverThumbAssetId: null, ...overrides, }; } function syntheticAsset(index: number) { return { id: `scale-${index}`, sourceId: "scale-source", externalId: `scale-${index}`, kind: "video", storage: "remote", capturedAt: now, durationMs: 4000, width: 1920, height: 1080, fps: 30, gpsLat: null, gpsLon: null, deviceModel: null, albumNames: [], isFavorite: false, ingestState: "indexed", hasProxy: false, hasThumbnail: false, qualityScore: null, createdAt: now, }; } async function openMediaStudio(page: Page) { await expect(page.getByTestId("media-studio-open")).toBeVisible({ timeout: 30_000 }); await page.getByTestId("media-studio-open").click(); const studio = page.getByTestId("media-studio"); await expect(studio).toBeVisible(); return studio; } test("media library groups sessions, selects clips and creates a project", async ({ page, }, testInfo) => { test.setTimeout(240_000); const stamp = Date.now(); await signup(page, `media-library-${stamp}@rakazo.test`, "password12", "Media Library"); await completeOnboarding(page); const capabilities = await rpc<{ configured: boolean }>(page, "media/capabilities", {}); expect(capabilities.configured).toBe(true); const source = await rpc<{ id: string }>(page, "media/sources/registerClient", { kind: "ios_library", label: "Test phone", deviceId: `e2e-device-${stamp}`, }); const pushed = await rpc<{ created: number }>(page, "media/sources/pushItems", { sourceId: source.id, items: syntheticItems(30), deletedExternalIds: [], syncToken: null, complete: true, }); expect(pushed.created).toBe(30); await rpc(page, "media/collections/recluster", {}); await expect .poll( async () => ( await rpc<{ items: unknown[] }>(page, "media/collections/list", { kind: "session", cursor: null, limit: 10, }) ).items.length, { timeout: 90_000, intervals: [1000] }, ) .toBeGreaterThan(0); const studio = await openMediaStudio(page); const sessions = studio.getByTestId("media-collection-card"); await expect(sessions.first()).toBeVisible(); await expect(studio.getByTestId("media-indexing")).toHaveCount(0); await captureScreenshot(page, testInfo, "media-library-sessions"); await studio.getByRole("tab", { name: "Themes" }).click(); await expect(studio.getByRole("tab", { name: "Themes" })).toHaveAttribute( "aria-selected", "true", ); await captureScreenshot(page, testInfo, "media-library-themes"); await studio.getByRole("tab", { name: "Sessions" }).click(); await studio.getByRole("button", { name: "Videos", exact: true }).click(); const grid = studio.getByTestId("media-asset-grid"); await expect(grid).toBeVisible(); await expect(grid.getByTestId("media-asset-tile").first()).toBeVisible(); await expect(grid).toHaveAttribute("data-count", "24"); await captureScreenshot(page, testInfo, "media-library-filter-videos"); await studio.getByRole("button", { name: "Videos", exact: true }).click(); await studio.getByTestId("media-collection-card").first().click(); await expect(studio).toHaveAttribute("data-view", "collection"); const tiles = studio.getByTestId("media-asset-tile"); await expect(tiles.first()).toBeVisible(); await tiles.nth(0).click(); await tiles.nth(1).click(); await expect(tiles.nth(0)).toHaveAttribute("aria-pressed", "true"); await expect(studio.getByRole("button", { name: "2 selected" })).toBeVisible(); await captureScreenshot(page, testInfo, "media-collection-selection"); await studio.getByTestId("media-create-project-open").click(); const create = page.getByTestId("media-create-project"); await expect(create).toBeVisible(); await expect(create.getByRole("checkbox").first()).toBeVisible(); await captureScreenshot(page, testInfo, "media-create-project"); await create.getByRole("button", { name: "Create project" }).click(); await expect(studio).toHaveAttribute("data-view", "project"); await studio.getByRole("button", { name: "Back" }).click(); await expect(studio).toHaveAttribute("data-view", "collection"); }); test("people view confirms, merges and records consent", async ({ page }, testInfo) => { const stamp = Date.now(); await signup(page, `media-people-${stamp}@rakazo.test`, "password12", "Media People"); await completeOnboarding(page); let people = [ person("person-a", "Person 1"), person("person-b", "Person 2", { confirmed: true }), person("person-c", "Person 3"), ]; const consentCalls: Json[] = []; await stub(page, "media/people/list", () => people); await stub(page, "media/people/update", (input) => { const { personId, ...patch } = input; people = people.map((row) => (row.id === personId ? { ...row, ...patch } : row)); return people.find((row) => row.id === personId); }); await stub(page, "media/people/merge", (input) => { const [target, ...others] = input.personIds as string[]; people = people .filter((row) => !others.includes(row.id)) .map((row) => (row.id === target ? { ...row, confirmed: true, faceTrackCount: 6 } : row)); return people.find((row) => row.id === target); }); await stub(page, "media/people/recordConsent", (input) => { consentCalls.push(input); people = people.map((row) => row.id === input.personId ? { ...row, consentRecordedAt: input.consentRecordedAt, ageVerificationMethod: input.ageVerificationMethod, ageVerifiedAt: input.ageVerifiedAt, } : row, ); return people.find((row) => row.id === input.personId); }); const studio = await openMediaStudio(page); await studio.getByTestId("media-connect-source-open").click(); const connect = page.getByTestId("media-connect-source"); await expect(connect).toBeVisible(); await captureScreenshot(page, testInfo, "media-library-connect-source"); await page.keyboard.press("Escape"); await expect(connect).toBeHidden(); await expect(studio).toBeVisible(); await studio.getByRole("tab", { name: "People" }).click(); await expect(studio).toHaveAttribute("data-view", "people"); const cards = studio.getByTestId("media-person"); await expect(cards).toHaveCount(3); await captureScreenshot(page, testInfo, "media-people"); const first = cards.filter({ hasText: "Person 1" }); await first.getByRole("button", { name: "Confirm" }).click(); await expect(first.getByText("Confirmed")).toBeVisible(); await first.getByRole("combobox", { name: "Anonymization" }).selectOption("blur"); await expect(first.getByRole("combobox", { name: "Anonymization" })).toHaveValue("blur"); await expect( first .getByRole("combobox", { name: "Anonymization" }) .locator("option[value=synthetic_identity]"), ).toBeDisabled(); await first.getByRole("button", { name: "Add consent" }).click(); const consent = page.getByTestId("media-consent"); await expect(consent).toBeVisible(); await consent.getByLabel("Consent given on").fill("2026-09-01"); await consent.getByLabel("Age proof").selectOption("platform_verified"); await consent.getByLabel("Verified on").fill("2026-09-02"); await captureScreenshot(page, testInfo, "media-people-consent"); await consent.getByRole("button", { name: "Save" }).click(); await expect(consent).toBeHidden(); expect(consentCalls).toEqual([ { personId: "person-a", consentArtifactId: null, consentRecordedAt: "2026-09-01T12:00:00.000Z", ageVerificationMethod: "platform_verified", ageVerifiedAt: "2026-09-02T12:00:00.000Z", }, ]); await expect(first.locator("[data-consent=complete]")).toBeVisible(); await cards .filter({ hasText: "Person 2" }) .getByRole("checkbox", { name: "Select Person 2" }) .click(); await cards .filter({ hasText: "Person 3" }) .getByRole("checkbox", { name: "Select Person 3" }) .click(); await studio.getByRole("button", { name: "Merge" }).click(); await page.getByRole("alertdialog").getByRole("button", { name: "Merge" }).click(); await expect(cards).toHaveCount(2); }); test("results import a CSV with a mapping and show the funnel", async ({ page }, testInfo) => { const stamp = Date.now(); await signup(page, `media-results-${stamp}@rakazo.test`, "password12", "Media Results"); await completeOnboarding(page); let imported = false; const saved: Json[] = []; await stub(page, "media/metrics/funnel", () => imported ? [ { publicationId: "pub-1", projectId: "project-1", profileId: "example-short-vertical", variant: "15s", thumbnailVariant: "a", views: 12000, clicks: 840, conversions: 42, revenueCents: 41958, currency: "EUR", }, { publicationId: "pub-2", projectId: "project-1", profileId: "example-paid-video", variant: null, thumbnailVariant: null, views: 3000, clicks: 300, conversions: 30, revenueCents: 29970, currency: "EUR", }, ] : [], ); await stub(page, "media/metrics/segments", () => imported ? [ { segmentId: "segment-1", assetId: "asset-1", startMs: 1000, endMs: 4000, publications: 2, views: 15000, clicks: 1140, conversions: 72, revenueCents: 71928, currency: "EUR", }, ] : [], ); await stub(page, "media/publications/list", () => ({ items: [], nextCursor: null })); await stub(page, "media/metrics/mappings", () => []); await stub(page, "media/metrics/saveMapping", (input) => { saved.push(input); return { ...input, id: "mapping-1" }; }); await stub(page, "media/metrics/importCsv", () => { imported = true; return { imported: 2, skipped: [] }; }); const studio = await openMediaStudio(page); await studio.getByRole("tab", { name: "Results" }).click(); await expect(studio.getByText("No results yet.")).toBeVisible(); await studio.getByRole("button", { name: "Import CSV" }).click(); const dialog = page.getByTestId("media-csv-import"); await dialog.getByLabel("CSV file").setInputFiles({ name: "example-platform.csv", mimeType: "text/csv", buffer: Buffer.from( "date;utm_content;views;clicks;purchases;revenue\n2026-09-01;variant-a;12000;840;42;419.58\n2026-09-01;variant-b;3000;300;30;299.70\n", ), }); await expect(dialog.getByLabel("Views")).toHaveValue("views"); await expect(dialog.getByLabel("Purchases")).toHaveValue("purchases"); await captureScreenshot(page, testInfo, "media-results-mapping"); await dialog.getByRole("button", { name: /^Import/ }).click(); await expect(dialog.getByTestId("media-csv-imported")).toHaveText("2 rows imported"); expect(saved[0]).toMatchObject({ name: "example-platform", delimiter: ";", publicationKey: "utm_content", columns: { publication: "utm_content", observedAt: "date", views: "views" }, }); await dialog.getByRole("button", { name: "Done" }).click(); const funnel = studio.getByTestId("media-funnel"); await expect(funnel).toBeVisible(); await expect(funnel).toContainText("15,000"); await expect(funnel).toContainText("1,140"); await expect(funnel).toContainText("72"); await expect(studio.getByTestId("media-results-segments")).toBeVisible(); await captureScreenshot(page, testInfo, "media-results-funnel"); await studio.getByRole("button", { name: "More" }).click(); await page.getByRole("menuitem", { name: "Reset teaser model" }).click(); await page.getByRole("alertdialog").getByRole("button", { name: "Reset" }).click(); await expect(studio.getByText("Reset", { exact: true })).toBeVisible(); }); test("the asset grid stays virtualized across 50k assets", async ({ page }) => { test.setTimeout(240_000); const stamp = Date.now(); await signup(page, `media-scale-${stamp}@rakazo.test`, "password12", "Media Scale"); await completeOnboarding(page); const total = 50_000; await stub(page, "media/sources/list", () => [ { id: "scale-source", kind: "folder", label: "Archive", status: "idle", assetCount: total, lastSyncedAt: now, error: null, createdAt: now, }, ]); await stub(page, "media/assets/search", (input) => { if (Array.isArray(input.ingestStates)) return { items: [], nextCursor: null }; const offset = Number(input.cursor ?? 0); const limit = Number(input.limit ?? 100); const end = Math.min(total, offset + limit); return { items: Array.from({ length: end - offset }, (_, index) => syntheticAsset(offset + index)), nextCursor: end < total ? String(end) : null, }; }); const studio = await openMediaStudio(page); await studio.getByRole("button", { name: "Videos", exact: true }).click(); const grid = studio.getByTestId("media-asset-grid"); await expect(grid.getByTestId("media-asset-tile").first()).toBeVisible(); let loaded = 0; while (loaded < total) { await grid.evaluate((element) => { element.scrollTop = element.scrollHeight; }); await expect .poll(async () => Number(await grid.getAttribute("data-count")), { timeout: 10_000 }) .toBeGreaterThan(loaded); loaded = Number(await grid.getAttribute("data-count")); expect(await grid.getByTestId("media-asset-tile").count()).toBeLessThan(400); } expect(loaded).toBe(total); await grid.evaluate((element) => { element.scrollTop = element.scrollHeight; }); await expect(grid.locator('[data-asset-id="scale-49999"]')).toBeVisible(); expect(await grid.getByTestId("media-asset-tile").count()).toBeLessThan(400); });