import { test, expect } from "#tests/e2e/fixtures/upload" test.describe("File Upload (Integration)", () => { test.describe("Initial State", () => { test("should display the upload drop zone with correct instructions", async ({ uploadPage, }) => { const { dropZone } = uploadPage await expect( dropZone.getByText("Перетащите видеофайл сюда"), ).toBeVisible() await expect( dropZone.getByText("или нажмите для выбора файла"), ).toBeVisible() await expect( dropZone.locator("button", { hasText: "Выбрать файл" }), ).toBeVisible() }) test("should have a file input that accepts only video types", async ({ uploadPage, }) => { const { fileInput } = uploadPage await expect(fileInput).toHaveAttribute("accept", "video/*") await expect(fileInput).not.toBeDisabled() }) test("should not show progress bar or error in initial state", async ({ uploadPage, }) => { const { dropZone } = uploadPage await expect( dropZone.getByText("Загрузка файла..."), ).not.toBeVisible() await expect( dropZone.getByText("Не удалось загрузить файл"), ).not.toBeVisible() }) }) test.describe("Successful Upload", () => { test("should upload a valid video file and advance to the Verify step", async ({ uploadPage, }) => { const { page, testVideoPath } = uploadPage await uploadPage.uploadFile(testVideoPath) // Wait for wizard to advance to Verify step await expect( page.locator("[data-testid='VerifyStep']"), ).toBeVisible({ timeout: 30_000 }) }) test("should show upload progress during file upload", async ({ uploadPage, }) => { const { dropZone, testVideoPath } = uploadPage await uploadPage.uploadFile(testVideoPath) // Progress UI should appear (may be brief for small files) // We check that either progress appeared or the step already advanced const progressOrVerify = await Promise.race([ dropZone .getByText("Загрузка файла...") .waitFor({ timeout: 5_000 }) .then(() => "progress" as const) .catch(() => null), uploadPage.page .locator("[data-testid='VerifyStep']") .waitFor({ timeout: 30_000 }) .then(() => "verify" as const), ]) expect(["progress", "verify"]).toContain(progressOrVerify) }) test("should show media info on Verify step after upload", async ({ uploadPage, }) => { const { page, testVideoPath } = uploadPage await uploadPage.uploadFile(testVideoPath) const verifyStep = page.locator("[data-testid='VerifyStep']") await expect(verifyStep).toBeVisible({ timeout: 30_000 }) // Badge should show "Готово к обработке" for MP4 await expect( verifyStep.getByText("Готово к обработке"), ).toBeVisible({ timeout: 10_000 }) // File info card should show the filename await expect(verifyStep.getByText("Файл")).toBeVisible() await expect(verifyStep.getByText("Размер и формат")).toBeVisible() }) test("should persist wizard state after upload completes", async ({ uploadPage, }) => { const { page, testVideoPath } = uploadPage await uploadPage.uploadFile(testVideoPath) await expect( page.locator("[data-testid='VerifyStep']"), ).toBeVisible({ timeout: 30_000 }) // Wait for debounced state save (1000ms debounce + network) await page.waitForTimeout(2500) await page.reload() await page.locator("[data-testid='ProjectWizard']").waitFor() // Should remain on Verify step after reload await expect( page.locator("[data-testid='VerifyStep']"), ).toBeVisible({ timeout: 10_000 }) }) }) test.describe("Error States", () => { test("should show error message when upload fails due to network error", async ({ uploadPage, }) => { const { page, dropZone, testVideoPath } = uploadPage // Intercept the upload XHR endpoint to abort await page.route("**/api/files/upload/**", (route) => route.abort()) await page.route("**/api/files/upload/", (route) => route.abort()) await uploadPage.uploadFile(testVideoPath) await expect( dropZone.getByText("Не удалось загрузить файл"), ).toBeVisible({ timeout: 10_000 }) // Wizard stays on upload step await expect(dropZone).toBeVisible() await expect( page.locator("[data-testid='VerifyStep']"), ).not.toBeVisible() }) test("should show error message when server returns 500", async ({ uploadPage, }) => { const { page, dropZone, testVideoPath } = uploadPage await page.route("**/api/files/upload/**", (route) => route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ detail: "Internal Server Error" }), }), ) await page.route("**/api/files/upload/", (route) => route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ detail: "Internal Server Error" }), }), ) await uploadPage.uploadFile(testVideoPath) await expect( dropZone.getByText("Не удалось загрузить файл"), ).toBeVisible({ timeout: 10_000 }) // Stays on upload step await expect(dropZone).toBeVisible() }) test("should allow retrying upload after a failure", async ({ uploadPage, }) => { const { page, dropZone, testVideoPath } = uploadPage // First attempt: network error await page.route("**/api/files/upload/**", (route) => route.abort()) await page.route("**/api/files/upload/", (route) => route.abort()) await uploadPage.uploadFile(testVideoPath) await expect( dropZone.getByText("Не удалось загрузить файл"), ).toBeVisible({ timeout: 10_000 }) // Remove intercepts and retry await page.unrouteAll({ behavior: "ignoreErrors" }) await uploadPage.uploadFile(testVideoPath) // Should succeed now and advance to Verify await expect( page.locator("[data-testid='VerifyStep']"), ).toBeVisible({ timeout: 30_000 }) }) test("should show error message when server returns 413", async ({ uploadPage, }) => { const { page, dropZone, testVideoPath } = uploadPage await page.route("**/api/files/upload/**", (route) => route.fulfill({ status: 413, contentType: "application/json", body: JSON.stringify({ detail: "File too large" }), }), ) await page.route("**/api/files/upload/", (route) => route.fulfill({ status: 413, contentType: "application/json", body: JSON.stringify({ detail: "File too large" }), }), ) await uploadPage.uploadFile(testVideoPath) await expect( dropZone.getByText("Не удалось загрузить файл"), ).toBeVisible({ timeout: 10_000 }) }) }) test.describe("Edge Cases", () => { test("should disable file input during active upload", async ({ uploadPage, }) => { const { page, dropZone, fileInput, testVideoPath } = uploadPage // Delay the upload response to observe the uploading state await page.route("**/api/files/upload/**", async (route) => { await new Promise((r) => setTimeout(r, 3000)) await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ file_id: "00000000-0000-0000-0000-000000000101", file_path: "projects/test/video.mp4", file_url: "http://localhost:9000/projects/test/video.mp4", }), }) }) await page.route("**/api/files/upload/", async (route) => { await new Promise((r) => setTimeout(r, 3000)) await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ file_id: "00000000-0000-0000-0000-000000000101", file_path: "projects/test/video.mp4", file_url: "http://localhost:9000/projects/test/video.mp4", }), }) }) await uploadPage.uploadFile(testVideoPath) // During upload, the file input should be disabled await expect( dropZone.getByText("Загрузка файла..."), ).toBeVisible({ timeout: 5_000 }) await expect(fileInput).toBeDisabled() }) }) })