[Fix] CI: e2e_ui_testing tests stale bundle on Ubuntu (cp -r merge semantics)#26047
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR adds a "Build UI from source" step to the Note: the PR description extensively documents a Confidence Score: 4/5Safe to merge; the CI change is correct and a real improvement, but the guard fixture described as the primary deliverable was not committed. The single changed file is a CI improvement that is mechanically correct. Remaining inline findings are P2 style/robustness suggestions. Score is 4 rather than 5 because the PR description commits to implementing the guarded-page.ts fixture and related spec migrations as part of this PR, but none of those files exist, leaving the 'no assertion path that would fail on a broken bundle' gap unaddressed. No files require special attention. The only changed file (.circleci/config.yml) has two minor P2 findings.
|
| Filename | Overview |
|---|---|
| .circleci/config.yml | Adds "Build UI from source" step to e2e_ui_testing that runs npm run build, replaces the checked-in bundle via rm -rf+mv, and pre-restructures HTML files. The core fix (test against fresh build) is correct and well-motivated. Two minor issues: the HTML restructuring is redundant with the proxy's own startup logic, and pipe-to-while subshell prevents mv failures from propagating. |
Sequence Diagram
sequenceDiagram
participant CI as CircleCI e2e_ui_testing
participant FS as Filesystem
participant Proxy as LiteLLM Proxy
participant PW as Playwright
CI->>FS: npm run build → out/
Note over CI,FS: BEFORE: cp -r out/ → silently nested
CI->>FS: rm -rf _experimental/out
CI->>FS: mv out → _experimental/out (atomic replace)
CI->>FS: find *.html → restructure to dir/index.html
CI->>Proxy: Start proxy (--config)
Proxy->>FS: _is_ui_pre_restructured() → True (skip)
Proxy->>FS: StaticFiles mount /ui → _experimental/out
CI->>PW: npx playwright test
PW->>Proxy: HTTP requests against fresh build
Note over PW,Proxy: Tests now exercise the actual compiled bundle
Reviews (3): Last reviewed commit: "fix(ci): make e2e_ui_testing actually te..." | Re-trigger Greptile
| const ALLOWED_ERROR_RESPONSES: Array<{ re: RegExp; reason: string }> = [ | ||
| // Example of a future entry: | ||
| // { re: /\/v3\/login\/exchange/, reason: "login.spec tests invalid-code path" }, | ||
| ]; |
There was a problem hiding this comment.
Empty allow-list may produce immediate false failures in the full suite
ALLOWED_ERROR_RESPONSES starts empty, but several migrated specs (e.g. unauthenticatedRedirect.spec.ts) may issue XHR/fetch calls whose responses legitimately land in the 4xx range (e.g. a 401 on an unauthenticated probe). Because the guard fires on any status >= 400 for xhr/fetch resource types, those tests will fail as soon as the full e2e_ui_testing job runs — not because the routing is broken, but because no allow-list entry exists for the expected-error path.
The PR description calls out "shake down ALLOWED_ERROR_RESPONSES" as a follow-up, which is the right approach. Just be prepared that the first CI run may flag pre-existing legitimate error responses and need entries added before the suite turns green.
| - build_and_test: | ||
| requires: | ||
| - build_docker_database_image | ||
| - e2e_ui_testing |
There was a problem hiding this comment.
e2e_ui_testing serialization increases critical-path wall-time
Adding e2e_ui_testing to requires for build_and_test means the full Playwright suite (browser launch, globalSetup login flow, multi-spec run) must complete before either build_and_test or db_migration_disable_update_check can start. Previously these ran in parallel. Any transient UI flakiness or infra hiccup in the Playwright job will now block the entire merge pipeline. This is the intended design, but consider pairing it with a concrete flakiness budget or retry count on e2e_ui_testing so a single flaky test doesn't orphan an otherwise-clean backend change.
The Build UI from source step used:
cp -r out/ ../../litellm/proxy/_experimental/out/
GNU cp (CircleCI's Ubuntu image, coreutils 8.32) interprets this as
copy the source directory as a CHILD of the destination when the
destination already exists — so the command silently created
litellm/proxy/_experimental/out/out/ instead of replacing the served
bundle at litellm/proxy/_experimental/out/*.
The proxy continued serving whatever bundle was checked in, so every
e2e_ui_testing run between this job's introduction (d09d98a,
2026-04-08) and the bundle-rebuild commit (de790fd, 2026-04-18) was
effectively testing a STALE bundle — not the fresh build. That is why
the double-prefix regression (NEXT_PUBLIC_BASE_URL="ui/" combined with
networking.tsx reading the env var) was never caught in CI even though
the source contained the trigger the whole time: the bundle the proxy
served never picked up the source change.
Replace cp -r with rm + mv so the destination is cleanly swapped.
Verified end-to-end on an Ubuntu 22.04 / GNU coreutils 8.32 container:
- Before fix: fresh build has 9 "ui/" literals in chunks; after cp,
_experimental/out/* still has 0 (stale); _experimental/out/out/ is a
nested dir the proxy does not serve.
- After fix: _experimental/out/* has 9 "ui/" literals — the proxy now
serves the freshly built (broken, in this repro) bundle, so
globalSetup fails at login and every spec is blocked. Removing the
bug from .env.production and rebuilding brings the count back to 0
and the suite passes.
No spec changes, no fixtures, no new infrastructure. The existing
Playwright suite already catches this class of regression via the
login flow in globalSetup; it just needs the CI to actually hand it
the freshly built bundle.
9ee6f48 to
0f5d503
Compare
Summary
The problem
Every
e2e_ui_testingrun between the job's introduction on 2026-04-08 (commitd09d98a70a, adding theBuild UI from sourcestep) and the bundle-rebuild commit on 2026-04-18 (de790fd273) was testing a stale UI bundle — not the one produced from the branch's source.The bundle that ended up in production had the double-prefix bug (
NEXT_PUBLIC_BASE_URL="ui/"in.env.productioncombined withnetworking.tsxreading that env var, inlined by Next.js into a minifiedproxyBaseUrl = "/ui/"in the compiled JS). The trigger was in source from PR #25109 on 2026-04-06 — roughly 12 days before the broken bundle landed in_experimental/out/. During that window,e2e_ui_testingran many times, built the broken bundle from source each time, and passed anyway.Failure path (before fix)
The
Build UI from sourcestep ran:cd ui/litellm-dashboard npm run build cp -r out/ ../../litellm/proxy/_experimental/out/GNU
cp(coreutils 8.32, which is what CircleCI's Ubuntu image ships) interpretscp -r src/ dest/as "copy the source directory as a child of the destination" when the destination already exists. The command silently createdlitellm/proxy/_experimental/out/out/— a nested dir the proxy does not serve — and left the served rootlitellm/proxy/_experimental/out/*untouched.The proxy continued serving whatever bundle was checked into the repo. Playwright's
globalSetuplogged in against the stale (pre-PR) bundle, which worked fine. The freshly built bundle — the one that actually carried the bug — never reached the HTTP layer.Fix
Replace the ambiguous
cp -rwith an unambiguousrm -rf+mv:This cleanly swaps the destination for the freshly built bundle with no merging, no nested subdirectory, and no dependence on the quirks of whichever
cpimplementation the CI image ships.Testing
Reproduced and verified end-to-end on Ubuntu 22.04 / GNU coreutils 8.32 (matching CircleCI):
ui/literal occurrences in served bundlecp -rcommandrm -rf/mvfixglobalSetuptimes out at login — suite fully blocked, which is the intended signalrm -rf/mvfixtest.skip'd)Local repro was done with the same stack CI uses: postgres 16, mock LLM server on 8090,
python -m litellm.proxy.proxy_clion 4000,cimg/python:3.12-browsers-equivalent build.Why no new test infrastructure
The existing Playwright
globalSetupalready exercises what the user sees — it logs in as each role and waits for the post-login redirect. If the UI bundle can't reach the API, login POST 404s, the redirect never happens,waitForURLtimes out, and every downstream spec is blocked. That's the canonical catch for this class of bug; it just needed CI to actually hand it the freshly built bundle.An earlier draft of this PR added a
guardedPagefixture with request/response listeners. It was removed: status-based 4xx detection produced false positives on legitimate Next.js internals like/__next._tree.txt?_rsc=…, and the URL-pattern alternative required maintaining a ~50-entry API-verb list in parallel withnetworking.tsx. Once the CI rebuild-step bug is fixed, the existingglobalSetupis sufficient.Type
🐛 Bug Fix
🚄 Infrastructure
Follow-ups not in this PR
e2e_ui_testingworkflow filter runs onmainand/litellm_.*/. Bundle-rebuild commits pushed to branches that don't match that pattern (e.g.yj_ui_build_apr18, which is how the broken bundle actually reachedlitellm_internal_stagingon 2026-04-18) skip the UI suite entirely. Worth broadening the filter or path-triggering on_experimental/out/**changes.e2e_ui_testingstatus as a required check onlitellm_internal_staging/mainin the repo settings UI.