tests/e2e/ui could only ever run against a locally provisioned stack, so it has
never run in the e2e pod. Three things pinned it there.
playwright.config.ts hardcoded baseURL http://localhost:4000 and globalSetup.ts
hardcoded the same host for /update/ui_settings and /ui/login. Both now derive
from LITELLM_PROXY_URL, falling back to localhost:4000 so local runs are
unchanged.
Storage state paths were repo-relative, so globalSetup wrote
admin.storageState.json into cwd. That is read-only in the runner image under
readOnlyRootFilesystem. They now sit under PLAYWRIGHT_STATE_DIR, defaulting to
"." for local runs. The per-role constants the specs import directly were
switched over too, so writer and readers stay in agreement.
globalSetup logged in as five roles whose users only exist if fixtures/seed.sql
was applied, and it throws when a login fails, so against an unseeded target the
whole suite died before a single spec ran. It now creates those users through the
proxy API first. ProxyAdmin is untouched since it logs in as "admin" with the
master key and needs no user row.
Verified against a live proxy that the seeding path works: /user/new returns 200,
/user/update sets the password, and /v2/login then authenticates as that user.
Worth knowing that /user/new accepts a password field but does not persist it
("User has no password set" on login), which is why the password is applied in a
second call.
All 25 spec files and 86 tests still collect, and the resolved values are correct
(trailing slash stripped, storage state redirected).
Teams, keys and the org from seed.sql are not self-seeded yet, so specs that
reference those ids still need them present on the target.
* fix(proxy): restore atomic user upsert when adding team members
Parallel /team/new calls naming the same not-yet-existing member were
returning 500 "Unique constraint failed on the fields: (`user_id`)".
The upsert in add_new_member passed an empty update branch. Prisma only
compiles an upsert down to a single INSERT ... ON CONFLICT when that branch
writes something; with an empty one it emits SELECT-then-INSERT instead, so
concurrent requests all read "no such user" and all insert. Postgres
statement logs confirm it: the empty form logs BEGIN/SELECT/INSERT/COMMIT,
the non-empty form logs INSERT ... ON CONFLICT ("user_id") DO UPDATE SET.
Re-state user_id in the update branch as a no-op so the native upsert path
comes back. The teams append stays in the filtered update below it, so an
already-existing member still cannot pick up a duplicate team id.
tests/test_team.py::test_team_new failed 9 of 15 runs against a live proxy
before this and 0 of 15 after. The existing unit test asserted only that
upsert had been called on a mock, so it passed either way; it now pins the
shape of both branches and fails when the update branch goes back to empty.
* test: point the live codex tests at gpt-5.3-codex
OpenAI deprecated gpt-5.2-codex, so test_openai_codex and
test_openai_codex_stream started failing against the live API with
model_not_found. gpt-5.3-codex is the current codex model; both tests pass
on it. The remaining gpt-5.2-codex references in the suite are mocked
transformation tests and are unaffected.
* test(e2e): update models page specs for the shared DataTable
The DataTable migration in #34363 changed three things the models page
specs were pinned to, and five tests went red.
Row click no longer opens the detail view; the Model ID cell owns that
now, so both specs click its `model-id-<id>` test id instead of the row.
The search box placeholder switched from an ASCII "..." to a real
ellipsis, so the specs use getByPlaceholder with a substring instead of
an exact attribute match that punctuation can break again. The results
count moved from `models-results-count` ("Showing 1 - 50 of 137 results")
to the shared pagination's `pagination-range` ("Showing 1-50 of 137").
The Team-BYOK test also filtered rows on the team alias, which the Team
ID column has never rendered in either the old or the new table; it
filters on the team id now, which is what the column actually shows and
what the assertion's own comment intends.
Verified against a local proxy serving a fresh build with the seeded
e2e postgres and mock upstream: all five failing tests pass, and the
full suite is 82 passed / 4 skipped at CI parity (workers=1).
Relocates ui/litellm-dashboard/e2e_tests to tests/e2e/ui so all end to end
suites live under tests/e2e. The suite stays in TypeScript and becomes a
self-contained npm package with its own package.json, lockfile and tsconfig
instead of leaning on the dashboard's toolchain; the dashboard drops its
@playwright/test dependency, e2e scripts and knip/vitest/tsconfig carve-outs.
CI paths follow the move: both CircleCI jobs (main e2e and the
SERVER_ROOT_PATH migration smoke) and the test_server_root_path workflow now
install and run Playwright from tests/e2e/ui, with the node cache keyed on
both lockfiles. classify_changes.sh treats tests/e2e/ui as client so spec
edits keep skipping backend jobs. The suite's mock LLM fixture is excluded
from the e2e basedpyright zero-error gate in pyrightconfig.json since it
belongs to the TS suite, not the typed Python harness.