import { expect, test } from "@playwright/test" const USER_ID = "00000000-0000-0000-0000-000000000001" const PROJECT_ID = "75df675b-013b-4b1f-ab2d-075dadbcd0d9" const APPLY_JOB_ID = "00000000-0000-0000-0000-000000000051" const TRANSCRIPTION_JOB_ID = "00000000-0000-0000-0000-000000000052" const ORIGINAL_FILE_ID = "00000000-0000-0000-0000-000000000060" const CUT_FILE_ID = "00000000-0000-0000-0000-000000000061" const ORIGINAL_FILE_KEY = "projects/test/original-video.mp4" const ORIGINAL_FILE_URL = "http://localhost:4444/files/original-video.mp4" const CUT_FILE_KEY = "projects/test/cut-video.mp4" const CUT_FILE_URL = "http://localhost:4444/files/cut-video.mp4" const DEFAULT_USER = { id: USER_ID, username: "testuser", email: "test@example.com", first_name: "Test", last_name: "User", phone_number: null, avatar: null, email_verified: true, phone_verified: false, is_active: true, is_staff: false, is_superuser: false, date_joined: "2025-01-01T00:00:00Z", } const MOCK_SEGMENTS = [ { start_ms: 5000, end_ms: 8000 }, { start_ms: 15000, end_ms: 19000 }, ] test.describe("Silence Apply Flow", () => { test("should persist cuts via workflow actions and continue to transcription on processed source file", async ({ page, }) => { let applyStatus: "RUNNING" | "DONE" = "RUNNING" const workflowActions: Array> = [] let workspace: Record = { revision: 1, phase: "SILENCE", current_screen: "fragments", active_job: null, source_file_id: ORIGINAL_FILE_ID, workspace_view: { used_file_ids: [], selected_file_id: null, }, silence: { status: "REVIEW_READY", settings: { min_silence_duration_ms: 200, silence_threshold_db: 16, padding_ms: 100, }, detect_job_id: "00000000-0000-0000-0000-000000000050", detected_segments: MOCK_SEGMENTS, reviewed_cuts: [], duration_ms: 30000, applied_output_file_id: null, }, transcription: { status: "IDLE", job_id: null, request: null, artifact_id: null, transcription_id: null, reviewed: false, }, captions: { status: "IDLE", preset_id: null, style_config: null, render_job_id: null, output_file_id: null, }, } await page.context().addCookies([ { name: "access_token", value: "fake-access-jwt", domain: "localhost", path: "/", }, { name: "refresh_token", value: "fake-refresh-jwt", domain: "localhost", path: "/", }, ]) await page.route("**/api/users/me/", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(DEFAULT_USER), }) }) await page.route(`**/api/projects/${PROJECT_ID}/`, async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: PROJECT_ID, owner_id: USER_ID, name: "Тестовый проект", description: null, language: "auto", folder: null, status: "DRAFT", workspace_state: null, is_active: true, created_at: "2025-06-01T00:00:00Z", updated_at: "2025-06-01T00:00:00Z", }), }) }) await page.route(`**/api/projects/${PROJECT_ID}/workspace*`, async (route) => { if ( applyStatus === "DONE" && workspace.current_screen === "silence-apply-processing" ) { workspace = { ...workspace, revision: 4, phase: "TRANSCRIPTION", current_screen: "transcription-settings", active_job: null, source_file_id: CUT_FILE_ID, silence: { ...workspace.silence, status: "APPLIED", applied_output_file_id: CUT_FILE_ID, }, } } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(workspace), }) }) await page.route( `**/api/projects/${PROJECT_ID}/workflow/actions*`, async (route) => { const action = route.request().postDataJSON() as Record workflowActions.push(action) if (action.type === "SET_SILENCE_CUTS") { workspace = { ...workspace, revision: 2, silence: { ...workspace.silence, reviewed_cuts: action.cuts as typeof MOCK_SEGMENTS, }, } } if (action.type === "START_SILENCE_APPLY") { workspace = { ...workspace, revision: 3, current_screen: "silence-apply-processing", active_job: { job_id: APPLY_JOB_ID, job_type: "SILENCE_APPLY", }, silence: { ...workspace.silence, status: "APPLYING", }, } } if (action.type === "START_TRANSCRIPTION") { workspace = { ...workspace, revision: 5, current_screen: "transcription-processing", active_job: { job_id: TRANSCRIPTION_JOB_ID, job_type: "TRANSCRIPTION_GENERATE", }, transcription: { ...workspace.transcription, status: "RUNNING", job_id: TRANSCRIPTION_JOB_ID, request: action.request as { engine: "whisper" language?: string model: string }, }, } } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(workspace), }) }, ) await page.route("**/api/files/files/", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: ORIGINAL_FILE_ID, project_id: PROJECT_ID, owner_id: USER_ID, original_filename: "original-video.mp4", path: ORIGINAL_FILE_KEY, storage_backend: "S3", mime_type: "video/mp4", size_bytes: 1024, checksum: null, file_format: "mp4", is_uploaded: true, is_deleted: false, is_active: true, created_at: "2025-06-01T00:00:00Z", }, { id: CUT_FILE_ID, project_id: PROJECT_ID, owner_id: USER_ID, original_filename: "cut-video.mp4", path: CUT_FILE_KEY, storage_backend: "S3", mime_type: "video/mp4", size_bytes: 1024, checksum: null, file_format: "mp4", is_uploaded: true, is_deleted: false, is_active: true, created_at: "2025-06-01T00:00:00Z", }, ]), }) }) await page.route("**/api/files/files/*/resolve/", async (route) => { const fileId = route.request().url().split("/files/")[1]?.split("/")[0] const isCutFile = fileId === CUT_FILE_ID await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ file_id: isCutFile ? CUT_FILE_ID : ORIGINAL_FILE_ID, file_url: isCutFile ? CUT_FILE_URL : ORIGINAL_FILE_URL, file_path: isCutFile ? CUT_FILE_KEY : ORIGINAL_FILE_KEY, filename: isCutFile ? "cut-video.mp4" : "original-video.mp4", }), }) }) await page.route("**/api/files/files/*/", async (route) => { const fileId = route.request().url().split("/files/")[1]?.split("/")[0] const isCutFile = fileId === CUT_FILE_ID await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ id: isCutFile ? CUT_FILE_ID : ORIGINAL_FILE_ID, project_id: PROJECT_ID, owner_id: USER_ID, original_filename: isCutFile ? "cut-video.mp4" : "original-video.mp4", path: isCutFile ? CUT_FILE_KEY : ORIGINAL_FILE_KEY, storage_backend: "S3", mime_type: "video/mp4", size_bytes: 1024, checksum: null, file_format: "mp4", is_uploaded: true, is_deleted: false, is_active: true, created_at: "2025-06-01T00:00:00Z", }), }) }) await page.route("**/api/media/artifacts/", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]), }) }) await page.route("**/api/tasks/status/**", async (route) => { const url = route.request().url() if (url.includes(APPLY_JOB_ID)) { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ status: applyStatus, job_type: "SILENCE_APPLY", progress_pct: applyStatus === "DONE" ? 100 : 30, output_data: applyStatus === "DONE" ? { file_id: CUT_FILE_ID, file_path: CUT_FILE_KEY, } : null, }), }) return } if (url.includes(TRANSCRIPTION_JOB_ID)) { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ status: "RUNNING", job_type: "TRANSCRIPTION_GENERATE", progress_pct: 10, output_data: null, }), }) return } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ status: "RUNNING", progress_pct: 0, output_data: null, }), }) }) await page.goto(`/projects/${PROJECT_ID}`) await expect(page.locator("[data-testid='FragmentsStep']")).toBeVisible() await expect(page.locator("[data-testid='cut-region']")).toHaveCount(2) await page.getByRole("button", { name: "Применить" }).click() await expect.poll(() => workflowActions.length).toBe(2) expect(workflowActions[0]).toMatchObject({ type: "SET_SILENCE_CUTS", revision: 1, }) expect(workflowActions[1]).toMatchObject({ type: "START_SILENCE_APPLY", revision: 2, }) await expect(page.locator("[data-testid='ProcessingStep']")).toBeVisible() applyStatus = "DONE" await expect( page.locator("[data-testid='TranscriptionSettingsStep']"), ).toBeVisible() await page.getByRole("button", { name: "Сгенерировать субтитры" }).click() expect(workflowActions[2]).toMatchObject({ type: "START_TRANSCRIPTION", revision: 4, request: { engine: "whisper", model: "base", }, }) await expect(page.locator("[data-testid='ProcessingStep']")).toContainText( "ТРАНСКРИБАЦИЯ", ) }) })