iter 2
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
import { test, expect } from "#tests/e2e/fixtures/processing"
|
||||
|
||||
const MOCK_SEGMENTS = [
|
||||
{ start_ms: 5000, end_ms: 8000 },
|
||||
{ start_ms: 15000, end_ms: 19000 },
|
||||
{ start_ms: 32000, end_ms: 35000 },
|
||||
{ start_ms: 45000, end_ms: 50000 },
|
||||
]
|
||||
const MOCK_DURATION_MS = 60000
|
||||
|
||||
function buildNotification(
|
||||
jobId: string,
|
||||
overrides: Record<string, unknown> = {},
|
||||
) {
|
||||
return {
|
||||
event: "task_update",
|
||||
notification_id: null,
|
||||
job_id: jobId,
|
||||
project_id: null,
|
||||
job_type: "SILENCE_DETECT",
|
||||
status: "RUNNING",
|
||||
progress_pct: 0,
|
||||
message: null,
|
||||
title: null,
|
||||
created_at: new Date().toISOString(),
|
||||
is_read: false,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
test.describe("Processing Step (Integration)", () => {
|
||||
test.describe("Initial State", () => {
|
||||
test("should display the processing step with progress and status label", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { processingStep } = processingPage
|
||||
|
||||
await expect(processingStep).toBeVisible()
|
||||
await expect(processingStep.getByText("0%")).toBeVisible()
|
||||
await expect(processingStep.getByText("АНАЛИЗ")).toBeVisible()
|
||||
})
|
||||
|
||||
test("should display the default status message", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { processingStep } = processingPage
|
||||
|
||||
await expect(
|
||||
processingStep.getByText("Подождите, идёт обработка..."),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("should display the info card about server processing", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { processingStep } = processingPage
|
||||
|
||||
await expect(
|
||||
processingStep.getByText(
|
||||
"Обработка выполняется на сервере. Вы можете покинуть страницу — прогресс сохранится.",
|
||||
),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("should display the cancel button", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { processingStep } = processingPage
|
||||
|
||||
const cancelButton = processingStep.getByRole("button", {
|
||||
name: "Отменить обработку",
|
||||
})
|
||||
await expect(cancelButton).toBeVisible()
|
||||
await expect(cancelButton).toBeEnabled()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Progress Updates", () => {
|
||||
test("should display updated progress percentage from notifications", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep, jobId } = processingPage
|
||||
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
progress_pct: 45,
|
||||
message: "Анализируем аудио...",
|
||||
}),
|
||||
)
|
||||
|
||||
await expect(processingStep.getByText("45%")).toBeVisible()
|
||||
await expect(
|
||||
processingStep.getByText("Анализируем аудио..."),
|
||||
).toBeVisible()
|
||||
await expect(processingStep.getByText("АНАЛИЗ")).toBeVisible()
|
||||
})
|
||||
|
||||
test("should update progress when notification changes", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep, jobId } = processingPage
|
||||
|
||||
// First update: 25%
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
progress_pct: 25,
|
||||
message: "Обработка...",
|
||||
}),
|
||||
)
|
||||
await expect(processingStep.getByText("25%")).toBeVisible()
|
||||
|
||||
// Second update: 75%
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
progress_pct: 75,
|
||||
message: "Почти готово...",
|
||||
}),
|
||||
)
|
||||
await expect(processingStep.getByText("75%")).toBeVisible()
|
||||
await expect(
|
||||
processingStep.getByText("Почти готово..."),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Auto-Advance on Completion", () => {
|
||||
test("should auto-advance to fragments step when task status returns DONE", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page } = processingPage
|
||||
|
||||
// Replace the route to return DONE
|
||||
await page.unrouteAll({ behavior: "ignoreErrors" })
|
||||
await page.route("**/api/tasks/status/**", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
status: "DONE",
|
||||
progress_pct: 100,
|
||||
output_data: {
|
||||
silent_segments: MOCK_SEGMENTS,
|
||||
duration_ms: MOCK_DURATION_MS,
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// Wait for auto-advance (polls every 2s)
|
||||
await expect(
|
||||
page.locator("[data-testid='FragmentsStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='ProcessingStep']"),
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test("should auto-advance when notification reports DONE", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, jobId } = processingPage
|
||||
|
||||
// Dispatch DONE notification (auto-advance also checks notifications)
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
status: "DONE",
|
||||
progress_pct: 100,
|
||||
}),
|
||||
)
|
||||
|
||||
// The WizardContext auto-advance also needs the polling to confirm,
|
||||
// so update the route too
|
||||
await page.unrouteAll({ behavior: "ignoreErrors" })
|
||||
await page.route("**/api/tasks/status/**", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
status: "DONE",
|
||||
progress_pct: 100,
|
||||
output_data: {
|
||||
silent_segments: MOCK_SEGMENTS,
|
||||
duration_ms: MOCK_DURATION_MS,
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='FragmentsStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Failure State", () => {
|
||||
test("should display error state when notification status is FAILED", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep, jobId } = processingPage
|
||||
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
status: "FAILED",
|
||||
message: "Файл повреждён",
|
||||
}),
|
||||
)
|
||||
|
||||
await expect(processingStep.getByText("ОШИБКА")).toBeVisible()
|
||||
await expect(
|
||||
processingStep.getByText("Файл повреждён"),
|
||||
).toBeVisible()
|
||||
|
||||
// Button should change from "Отменить обработку" to "Назад"
|
||||
await expect(
|
||||
processingStep.getByRole("button", { name: "Назад" }),
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
processingStep.getByRole("button", {
|
||||
name: "Отменить обработку",
|
||||
}),
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test("should show default error message when FAILED notification has no message", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep, jobId } = processingPage
|
||||
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
status: "FAILED",
|
||||
message: null,
|
||||
}),
|
||||
)
|
||||
|
||||
await expect(
|
||||
processingStep.getByText("Произошла ошибка при обработке"),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("should navigate back to silence settings when clicking back in error state", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep, jobId } = processingPage
|
||||
|
||||
await page.evaluate(
|
||||
(payload) => {
|
||||
;(window as any).__REDUX_STORE__?.dispatch({
|
||||
type: "notifications/addNotification",
|
||||
payload,
|
||||
})
|
||||
},
|
||||
buildNotification(jobId, {
|
||||
status: "FAILED",
|
||||
message: "Ошибка обработки",
|
||||
}),
|
||||
)
|
||||
|
||||
await processingStep
|
||||
.getByRole("button", { name: "Назад" })
|
||||
.click()
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='SilenceSettingsStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
await expect(processingStep).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Cancel", () => {
|
||||
test("should navigate back to silence settings when cancel button is clicked", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep } = processingPage
|
||||
|
||||
await processingStep
|
||||
.getByRole("button", { name: "Отменить обработку" })
|
||||
.click()
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='SilenceSettingsStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
await expect(processingStep).not.toBeVisible()
|
||||
})
|
||||
|
||||
test("should start a new job when clicking Далее again after cancel", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page, processingStep } = processingPage
|
||||
|
||||
// Cancel
|
||||
await processingStep
|
||||
.getByRole("button", { name: "Отменить обработку" })
|
||||
.click()
|
||||
|
||||
const silenceStep = page.locator(
|
||||
"[data-testid='SilenceSettingsStep']",
|
||||
)
|
||||
await silenceStep.waitFor({ timeout: 10_000 })
|
||||
|
||||
// Capture new request
|
||||
let newPostMade = false
|
||||
page.on("request", (req) => {
|
||||
if (
|
||||
req.url().includes("/api/tasks/silence-detect/") &&
|
||||
req.method() === "POST"
|
||||
) {
|
||||
newPostMade = true
|
||||
}
|
||||
})
|
||||
|
||||
// Click "Далее" again
|
||||
await silenceStep
|
||||
.getByRole("button", { name: "Далее" })
|
||||
.click()
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='ProcessingStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
|
||||
expect(newPostMade).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("State Persistence", () => {
|
||||
test("should restore processing step after page reload", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page } = processingPage
|
||||
|
||||
// Wait for debounced save
|
||||
await page.waitForTimeout(2500)
|
||||
|
||||
// Reload — route intercepts are lost, re-add before reload completes
|
||||
await page.reload()
|
||||
await page.route("**/api/tasks/status/**", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
status: "RUNNING",
|
||||
progress_pct: 0,
|
||||
output_data: null,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
await page
|
||||
.locator("[data-testid='ProjectWizard']")
|
||||
.waitFor({ timeout: 10_000 })
|
||||
|
||||
await expect(
|
||||
page.locator("[data-testid='ProcessingStep']"),
|
||||
).toBeVisible({ timeout: 10_000 })
|
||||
})
|
||||
|
||||
test("should resume polling and auto-advance after page reload", async ({
|
||||
processingPage,
|
||||
}) => {
|
||||
const { page } = processingPage
|
||||
|
||||
// Wait for debounced save
|
||||
await page.waitForTimeout(2500)
|
||||
|
||||
// Reload and set up route to return DONE
|
||||
await page.reload()
|
||||
await page.route("**/api/tasks/status/**", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
status: "DONE",
|
||||
progress_pct: 100,
|
||||
output_data: {
|
||||
silent_segments: MOCK_SEGMENTS,
|
||||
duration_ms: MOCK_DURATION_MS,
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
await page
|
||||
.locator("[data-testid='ProjectWizard']")
|
||||
.waitFor({ timeout: 10_000 })
|
||||
|
||||
// Should auto-advance to fragments after polling picks up DONE
|
||||
await expect(
|
||||
page.locator("[data-testid='FragmentsStep']"),
|
||||
).toBeVisible({ timeout: 15_000 })
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user