test(e2e): fix flaky Extract pipeline E2E document-processing timeout (#1844)#1846
Conversation
…CI flakiness The 'Extract pipeline (PDF upload → run → CSV)' check failed intermittently (issue #1844) with 'Document still processing (data-processing="true")' after the 480000ms (8 min) waitForDocumentReady ceiling, then cascaded into 'row has no non-empty extracted cell' when the extract raced an unprocessed document. On cold GitHub runners the Docling parse + embed of the large US Code fixture occasionally exceeds 8 minutes. - Raise waitForDocumentReady default ceiling 8 min -> 14 min. Both PDFs are uploaded before either wait, so Celery processes them concurrently and the binding constraint is the slowest single document, not the sum. - Raise the spec's overall test timeout 20 min -> 30 min to clear the worst case where the two waits serialize (2x14 min) plus extract/CSV/fork/diff. - Correct the stale spec docstring/skip-reason: CI is NOT skipped — the frontend-e2e-extract.yml workflow sets E2E_RUN_LLM_TESTS and replays the LLM call from a VCR cassette; only PDF parsing (Docling) runs live.
Code ReviewPR: test(e2e): fix flaky Extract pipeline E2E document-processing timeout (#1844) OverviewThis is a focused, well-justified fix for a flaky E2E test. The two changes are:
The PR description is excellent — root cause analysis, concurrency reasoning (both PDFs upload before either wait, so Celery processes them in parallel), and the fact that the budget must cover Code Quality✅ Positives
Per the project's
Both timeout values are raw arithmetic expressions scattered across two files. If the Docling ceiling needs to change again, a reviewer has to update Suggested refactor: // frontend/src/assets/configurations/constants.ts (or a test-specific constants file)
export const DOCLING_PARSE_TIMEOUT_MS = 14 * 60 * 1000; // cold-runner ceiling per #1844
export const EXTRACT_E2E_SUITE_TIMEOUT_MS = 30 * 60 * 1000; // 2×DOCLING_PARSE + extract/CSV/diff// helpers.ts
import { DOCLING_PARSE_TIMEOUT_MS } from "../../src/assets/configurations/constants";
...
export async function waitForDocumentReady(
page: Page,
documentTitle: string,
timeoutMs: number = DOCLING_PARSE_TIMEOUT_MS
)// extract-pdf-workflow.spec.ts
import { EXTRACT_E2E_SUITE_TIMEOUT_MS } from "../../src/assets/configurations/constants";
...
test.setTimeout(EXTRACT_E2E_SUITE_TIMEOUT_MS);This is a non-blocking nit — the existing inline comments mitigate the risk — but it would make future tuning safer and keeps the project's own conventions. Correctness
Security / PerformanceNo security concerns. These are test-only changes with no production code paths affected. Summary✅ Approve with one non-blocking suggestion. The fix is correct, well-documented, and appropriately scoped. The only actionable suggestion is extracting the timeout values into named constants per project conventions. Happy to see this merged as-is if the team prefers to keep test-only constants inline. |
Backend logs from the failing run show the extract actually SUCCEEDS — e.g. 'Raw extraction result: TITLE 1-GENERAL PROVISIONS' / 'Successfully extracted and saved data for cell 6' — yet the spec reported 'row has no non-empty extracted cell'. The 'Extraction in progress' overlay (which runExtractAndWaitForFinish waits on) clears when the backend marks the extract complete, a beat before AG-Grid finishes its per-cell Apollo data fetch. The one-shot cell read raced that hydration and saw an empty cell. Wrap the per-row cell check in expect(...).toPass so it retries until the cell hydrates (120s ceiling). A genuinely empty extract still fails when the poll times out, so the assertion is not weakened. Complements the document-processing timeout bump for the other (rarer) failure mode. Refs #1844.
Code ReviewOverviewThis PR fixes two distinct flaky-test failure modes in the Mode A — Document-Processing TimeoutFix: Default timeout in The JSDoc update in Minor nit: // current
}).toPass({ timeout: 120_000, intervals: [1_000, 2_000, 5_000] });
// suggested (matches the style of the surrounding timeouts)
}).toPass({ timeout: 2 * 60 * 1000, intervals: [1_000, 2_000, 5_000] });Mode B — Frontend Grid-Hydration Race (the dominant failure)Fix: Wrap the per-row cell read in This is idiomatic Playwright for retrying flaky assertions and is the right tool here. A few observations:
|
| Assessment | |
|---|---|
| Correctness | ✅ Both fixes are sound |
| Idiomatic Playwright | ✅ toPass is the right approach for grid-hydration races |
| Assertion strength | ✅ Not weakened |
| Code style | 120_000 literal vs 2 * 60 * 1000 expression (minor) |
| Test budget math | ✅ Adequate, tight but justified |
| Comments | ✅ Detailed where the WHY is non-obvious |
The one style nit aside, this is a clean, well-reasoned fix. Approving with that suggestion to address at your discretion.
Summary
Fixes the intermittently-failing
Extract pipeline (PDF upload → run → CSV)check (issue #1844). The check is not live-LLM gated —.github/workflows/frontend-e2e-extract.ymlruns the backend withOC_LLM_VCR_MODE=replayso the extract LLM call is served from a recorded cassette; only Docling PDF parsing runs live. It is flaky, not broken (the workflow has recentsuccessruns).There are two distinct failure modes, both fixed here:
Mode A — document-processing timeout (rarer)
waitForDocumentReadywaited at most 8 min for parse + embed. On a cold runner, Docling parsing of the large US Code fixture occasionally exceeds 8 min →Fix: ceiling 8 → 14 min (both PDFs upload before either wait, so Celery processes them concurrently — the constraint is the slowest single doc, not the sum); spec
test.setTimeout20 → 30 min.Mode B — frontend grid-hydration race (dominant / consistent)
This was the real blocker. The backend extract succeeds — the failing run's Celery logs show:
…yet the spec reported
row has no non-empty extracted cell. The "Extraction in progress" overlay (whichrunExtractAndWaitForFinishwaits on) clears when the backend marks the extract complete — a beat before AG-Grid finishes its per-cell Apollodatafetch. The spec's one-shot cell read raced that hydration and saw an empty cell.Fix: wrap the per-row cell check in
expect(...).toPass({ timeout: 120s })so it retries until the cell hydrates. A genuinely empty extract still fails when the poll times out, so the assertion is not weakened.Also
Corrected the stale spec docstring/skip-reason that claimed "CI does not set the gate" — CI sets it and replays the LLM via VCR.
Verification
Closes #1844