LibreChat/e2e
Marco Beretta 3c72c3fb4a
Some checks are pending
Backend Unit Tests / Build packages (push) Waiting to run
Backend Unit Tests / Codegraph select (push) Waiting to run
Backend Unit Tests / TypeScript type checks (push) Blocked by required conditions
Backend Unit Tests / Circular dependency checks (push) Waiting to run
Backend Unit Tests / Tests: api (shard 1/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 2/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: api (shard 3/3) (push) Blocked by required conditions
Backend Unit Tests / Tests: data-provider (push) Blocked by required conditions
Backend Unit Tests / Tests: data-schemas (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 1/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 2/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 3/4) (push) Blocked by required conditions
Backend Unit Tests / Tests: @librechat/api (shard 4/4) (push) Blocked by required conditions
Codegraph E2E Votes / vote (full suite) (push) Waiting to run
Docker Dev Branch Images Build / publish (push) Waiting to run
Frontend Unit Tests / Client build recovery regression (push) Waiting to run
Frontend Unit Tests / Codegraph select (push) Waiting to run
Frontend Unit Tests / Build packages (push) Waiting to run
Frontend Unit Tests / TypeScript type checks (client) (push) Blocked by required conditions
Frontend Unit Tests / Tests: @librechat/client (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 1/2) (push) Blocked by required conditions
Frontend Unit Tests / Tests: Ubuntu (shard 2/2) (push) Blocked by required conditions
Frontend Unit Tests / Vite build verification (push) Blocked by required conditions
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Docker Dev Images Build / publish (push) Waiting to run
Sync Helm Chart Tags / Ignore non-main push (push) Waiting to run
Sync Helm Chart Tags / Sync chart tags (push) Waiting to run
🐢 perf: Collapse Settled Reasoning Text in AnimatedText (#16403)
* perf: Collapse Settled Reasoning Text in AnimatedText

AnimatedText re-split and re-classified the entire accumulated reasoning
string on every streamed token and rendered one element per word, making
a long reasoning stream quadratic. Classification is now append-only: the
leading run of words that finished fading is committed as a single text
node and never re-split, and only the unsettled tail is classified.

* perf: Probe the Settled Reasoning Prefix Instead of Rescanning It

The append check compared the whole settled prefix on every token, which
kept the stream quadratic in character comparisons. It now probes the
prefix head and the region at its boundary, and the settled region is
rendered from the current text so a rewrite can never show stale
characters. Adds a long reasoning stream scenario to the mock harness.

* perf: Settle Spaceless Reasoning Runs Before Their Final Segment

A CJK or Thai reasoning stream never reaches whitespace, so the settled
prefix could not advance and the whole run was re-segmented per token.
Inside a spaceless last word everything before its final segment is now
treated as stable; re-segmenting there only changes fade granularity.

* perf: Bound the Unsettled Reasoning Tail by Length

Settling at part boundaries left any run that never reaches whitespace
(unspaced Hangul, a spaceless script without Intl.Segmenter, long URLs)
in the tail, re-classified on every token. The unsettled region is now
capped at 64 characters regardless of segmentation, replacing the
spaceless-specific rule; only non-animated text is ever cut, so a cut
inside a part is invisible. Tests measure the classified length per
render instead of the DOM shape.

* fix: Keep the Settled Reasoning Cut on a Glyph Boundary

The length cap could cut inside a surrogate pair or a combining or ZWJ
sequence, splitting one glyph across two text nodes. The cut now steps
back past continuation code units.

* fix: Cut the Settled Reasoning Prefix at Grapheme Boundaries

Code-unit rules missed emoji modifiers and regional-indicator flags. The
cut now takes the last grapheme boundary from Intl.Segmenter over the
unsettled tail, which starts at a committed cluster boundary and is
bounded by the cap; the code-unit rule remains as the fallback.

* fix: Skip the Reasoning Tail Cap Without a Grapheme Segmenter

The code-unit fallback could still split emoji modifiers and flags. With
no Intl.Segmenter there is no safe cut, so the cap is not applied and the
tail stops at the last word, as it did before.

---------

Co-authored-by: Lia <lia@librechat.ai>
2026-09-26 22:08:22 -04:00
..
benchmarks
benchmarks-mobile-chat
benchmarks-navigation
benchmarks-reasoning
benchmarks-tree
bombadil
byom 🧲 feat: Attach or Leave a Workspace on a Saved Chat (#16124) 2026-09-25 10:08:28 -04:00
config
fixtures
lighthouse 🚚 chore: Point Repository and Image References at LibreChat-AI (#16005) 2026-09-23 23:08:36 -04:00
perf
recordings
screenshots 📷 test: Capture Revision-Pinned PR Screenshot Evidence (#16191) 2026-09-22 06:32:31 -04:00
setup 🐢 perf: Collapse Settled Reasoning Text in AnimatedText (#16403) 2026-09-26 22:08:22 -04:00
specs 🐢 perf: Collapse Settled Reasoning Text in AnimatedText (#16403) 2026-09-26 22:08:22 -04:00
client-build.test.mjs 🐾 style: Show the Multiplier Only on a Generic Tool Line (#16225) 2026-09-22 21:44:52 -04:00
config.local.example.ts
jestSetup.js 🚀 v0.8.8-rc4 (#16147) 2026-09-22 23:30:28 -04:00
playwright.config.a11y.ts
playwright.config.benchmark.ts
playwright.config.bombadil.ts
playwright.config.deployed.ts
playwright.config.lighthouse.ts
playwright.config.local.ts
playwright.config.mermaid.ts
playwright.config.mobile-chat-perf.ts
playwright.config.mock.ts
playwright.config.navigation-perf.ts
playwright.config.real.ts
playwright.config.reasoning-perf.ts
playwright.config.redis.ts
playwright.config.tree-parity.ts
playwright.config.tree-perf-prod.ts
playwright.config.tree-perf.ts
playwright.config.ts
README.md 📷 test: Capture Revision-Pinned PR Screenshot Evidence (#16191) 2026-09-22 06:32:31 -04:00
types.ts

LibreChat e2e

The mock e2e profile is the safest default for generated tests. It starts LibreChat with e2e/config/librechat.e2e.yaml, injects an in-process fake LLM (via LIBRECHAT_TEST_RUN_HOOK), creates an authenticated e2e user, and avoids real provider credentials.

Deployed-instance smoke test

The deployed profile exercises an existing LibreChat deployment without starting another app or database. It uses the deployment's configured model provider and persists a real conversation, so run it only with a dedicated test account in an environment where that traffic is expected.

First, create Playwright storage state by signing in through the deployment's normal login flow:

npx playwright codegen \
  --save-storage=e2e/storageState.json \
  https://librechat.example.com/c/new

Close codegen after sign-in, then run the smoke test:

E2E_BASE_URL=https://librechat.example.com \
  npm run e2e:deployed

The storage-state file contains session credentials. The default path is ignored by Git; do not commit it or include it in test artifacts.

Set E2E_STORAGE_STATE when the auth file is mounted elsewhere. If the account has no default model, set E2E_DEPLOYED_MODEL to the exact configured model label. E2E_DEPLOYED_PROMPT can replace the short default prompt, and E2E_IGNORE_HTTPS_ERRORS=true supports deployments using a self-signed certificate.

The profile deliberately has no global setup, database access, or web server. It verifies the authenticated shell, sends one real prompt, reloads the resulting conversation, and deletes only the conversation created by that run through LibreChat's authenticated API. Keep deterministic provider behavior and destructive database fixtures in the mock profile instead.

Stream Stores and Shards

The mock profile uses the in-memory generation stream store by default. To exercise the same browser scenarios through a real Redis job store and pub/sub transport, start Redis on port 6379 and run:

npm run e2e:mock:redis

Memory mode explicitly disables Redis. Redis mode defaults to database 15 with a LibreChatE2E key prefix, and fails closed: the test server pings Redis and verifies that the generation job manager did not silently fall back to memory. Override REDIS_URI or E2E_REDIS_KEY_PREFIX when needed.

Pull request CI runs the complete mock suite in memory mode across three shards, plus a focused Redis transport suite. The Redis suite covers streaming fidelity, steering, interrupts, resumptions, HITL approvals, completion, thread folding, model icons, and usage:

npx playwright test --config=e2e/playwright.config.mock.ts --shard=1/3
npm run e2e:mock:redis:transport

The nightly schedule and manual workflow dispatch run the complete mock suite in both stream modes across two shards per mode. Every shard keeps one worker so tests do not contend for its authenticated user and database.

Property-based browser testing

Bombadil explores randomized sequences across the core chat loop, message branches, parallel multi-conversation responses, model changes, reloads, and sidebar conversation lifecycle operations:

npm run e2e:bombadil

Set BOMBADIL_TIME_LIMIT for longer local or scheduled runs. Failures leave a reproducible trace under e2e/.generated/bombadil-output; rerun it with:

BOMBADIL_REPRODUCE=e2e/.generated/bombadil-output npm run e2e:bombadil:run

Reproducing a real violation is expected to fail the Playwright test. Before a new run overwrites the active output, the harness archives it under e2e/.generated/bombadil-history/. Reproduction can diverge when streaming timing changes; Bombadil reports that explicitly.

The harness uses the credential-free mock-LLM profile, so exploration never sends billable provider requests.

CI runs the broad property exploration for five minutes in the non-blocking Bombadil Property Exploration workflow. If a property fails, download the bombadil-reproduction-* artifact into e2e/.generated/bombadil-output/, then reproduce it locally:

BOMBADIL_REPRODUCE=e2e/.generated/bombadil-output npm run e2e:bombadil:run

The accompanying bombadil-diagnostics-* artifact contains the captured CI log, Playwright HTML report, and Playwright test results. A Bombadil failure produces a workflow warning but does not block merge.

The default instruments inline JavaScript only because instrumenting LibreChat's full Vite bundle can exceed Bombadil's driver timeout during stateful runs. Set BOMBADIL_INSTRUMENT_JAVASCRIPT=files,inline for shorter coverage-guided experiments.

The branch reload, fork submission, model/conversation, HITL pause/resume, and mid-run steering lifecycle properties can be run independently:

npm run e2e:bombadil:branch-reload
npm run e2e:bombadil:fork-lifecycle
npm run e2e:bombadil:model-lifecycle
npm run e2e:bombadil:hitl
npm run e2e:bombadil:steering

These focused commands are diagnostic properties: they exit nonzero when they reproduce a product invariant violation. Reproduce a focused trace with its matching :run script and output directory, for example:

BOMBADIL_REPRODUCE=e2e/.generated/bombadil-output-hitl npm run e2e:bombadil:hitl:run

HITL drives a real ask_user_question checkpoint through the answer/resume controller, reloads while the question is paused, answers it once, and reloads the completed conversation. Steering submits an in-flight steer during a slow MCP-backed run, checks that it moves exactly once from the composer anchor into the response at the tool boundary, and reloads the applied state. The model lifecycle property is the passing control. The branch reload and fork properties preserve their minimal failing traces.

Recording Tests

Use Playwright codegen when you want to turn an exploratory browser session into a draft test:

npm run e2e:record

That command builds the app, starts the LibreChat test server (with an in-process fake LLM) when needed, writes e2e/storageState.json, and opens Playwright codegen at /c/new. The npm script uses http://localhost:3333 so it does not collide with a normal dev server on 3080. Raw recordings are written to e2e/recordings/ and ignored by git.

For a real local LibreChat config instead of the mock profile:

npm run e2e:record:local

Useful direct options:

node e2e/setup/record.js --url=http://localhost:3080/c/new
node e2e/setup/record.js --profile=local --no-output
node e2e/setup/record.js --auth-only
node e2e/setup/record.js --output=e2e/recordings/settings-draft.spec.ts

LLM-Assisted Loop

  1. Start npm run e2e:record.
  2. Let the LLM use Computer Use to operate the headed Playwright browser.
  3. Stop codegen after the workflow is captured.
  4. Move the useful parts from e2e/recordings/ into a committed spec under e2e/specs/mock/.
  5. Replace brittle generated selectors with role, label, text, or data-testid locators.
  6. Add assertions that prove the behavior, not just the clicked path.
  7. Run the finished spec with npm run e2e:mock -- <spec name>.

Generated recordings are a draft, not the final test. The committed version should use the shared helpers in e2e/specs/mock/helpers.ts where possible, wait on network or visible UI state instead of fixed sleeps, and keep test data deterministic.

PR screenshot pilot

e2e/screenshots/playwright.config.ts is an opt-in evidence capture lane, not a pixel-baseline suite. It runs the real app with the mock profile and a private ephemeral MongoDB, then captures welcome screens at desktop/mobile sizes in both themes. Desktop captures also include temporary chat and the settings dialog. No model request is made. Motion is reduced, so these stills do not prove transitions or streaming behavior.

Build and run each revision in its own clean worktree with its own locked install:

npm ci
npm run frontend
npx playwright install chromium
E2E_CAPTURE_SHA=$(git rev-parse HEAD) \
E2E_CAPTURE_DIR="$PWD/e2e/.generated/evidence-before" \
E2E_USE_MEMORY_MONGO=true \
E2E_BASE_URL=http://127.0.0.1:3333 \
npx playwright test --config=e2e/screenshots/playwright.config.ts

For a historical revision without this lane, copy the two e2e/screenshots/*.ts files into its worktree, leaving its application code and lockfile unchanged. Run the same scenario source on both revisions. Use a new output directory for every attempt: existing images are never overwritten. Run revisions sequentially, or give every fixture service its own port using the mock profile's E2E_*_PORT settings. Do not reuse a running development server or a real user's database.

Each PNG has a JSON sidecar containing the revision, browser version, viewport, theme, scenario hash, lockfile hash, built HTML hash, and image hash. Visible images must decode, fonts must finish loading, greeting springs must settle, and consecutive captures must match. A passing run and human inspection are both required before treating the pair as reviewed evidence; sidecars from a failed run are not complete evidence. Compare matching filenames between revisions. Pin the browser and font environment as well as the app revisions.

Keep storage state, traces, logs, and session data private. Check that the PNGs contain only intended synthetic test data before uploading with the attachment-capable gh described in the PR template, then read back the PR body to verify real asset URLs. Mark any pending visual review explicitly. Do not commit the images. A new surface has no before state; label it accordingly rather than substituting another screen.