LibreChat/.github/workflows/playwright-mock.yml
Danny Avila dfd4d9dd81
🧩 ci: Close Workflow Path-Filter Gaps (#14728)
* 🧩 ci: Close Workflow Path-Filter Gaps

Six trigger-filter gaps found by reading all workflows against the live
dependency graph (AI-1755, codegraph FINDINGS §6l):

- backend-review/frontend-review: root package.json/package-lock.json now
  trigger unit tests — a lockfile-only dependency bump previously ran zero
  backend or frontend unit tests while every test job installs from it
- agents-integration-tests: widen to the three package src trees it builds
  and imports (was only src/agents/**)
- cache-integration-tests: same shape — verified live that
  packages/api/src/flow/manager.ts (imported by mcp/oauth) matched neither
  integration filter
- docker-smoke: plain Dockerfile had no PR-time validation despite shipping
  via dev-images/tag-images; new node-image-smoke job builds it, gated by
  paths-filter to Dockerfile/.dockerignore changes
- dev-images/dev-branch-images: add config/**, skill/**, .dockerignore —
  the single-stage image COPYs the full build context
- static-checks: eslint.config.mjs now re-triggers the lint job (gap carried
  over from eslint-ci.yml in the #14716 consolidation)
- delete generate_embeddings.yml: fired on docs/**, which no longer exists,
  and its docs-root-path pointed at the same missing directory

* 🧩 ci: Address Codex Review Findings

- Build caches: all 26 build-* keys across 8 workflows now lead with
  root package.json + package-lock.json so manifest-only bumps cannot
  restore stale dists (data-provider embeds the root version); unifies
  the split key families (playwright already hashed the lockfile)
- static-checks: config changes now gate on the ESLint config loading
  and applying to representative files, plus a report-only full-tree
  sweep (70 pre-existing errors at dev HEAD block a hard gate for now)
- docker-smoke: the workflow file itself triggers the plain-Dockerfile
  build so job edits are validated
- dev-images/dev-branch-images: re-include skill/**/*.md after !**.md
  so shipped deployment-skill Markdown rebuilds images

* 🧩 ci: Gate Config Lint Sweep on Regression vs Base Config

Second-round codex finding: the report-only sweep swallowed config-wide
breakage in scoped blocks the representative files don't exercise. The
sweep now lints the same tree under the PR's config and the base ref's
config and fails only when the PR's config produces more diagnostics for
some (file, rule) pair — pre-existing debt never fails the gate, and
fixes are never penalized. Base-config unavailability degrades to the
load gate with a notice. Outcome surfaced in the failure summary.

* 🧩 ci: Harden Config Lint Gate per External Review

- Coverage direction: fail when the PR config stops linting files the
  base config covered (set difference on linted files) — a mis-scoped
  ignores previously only removed diagnostics and passed both gates
- Severity-aware fingerprints: (file, rule, severity) so warn->error
  escalations gate on a clean tree for that rule; downgrades still free
- Hard-fail when the base commit is missing so a future shallow-checkout
  change cannot silently disable the gate; annotate fetch-depth: 0
- Tab-separated fingerprint keys (space-in-path proof), --config on
  both sweeps, EXIT trap for the base config copy, comment on why it
  must live at the repo root (flat-config pattern base paths)
- Narrow skill md re-include with !skill/README.md: top-level README is
  documentation-only; runtime skill Markdown still rebuilds images
2026-08-10 17:26:50 -04:00

292 lines
11 KiB
YAML

name: Playwright E2E Tests
on:
pull_request:
paths:
- '**'
- '!**.md'
- '!.github/workflows/**'
- '.github/workflows/playwright-mock.yml'
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual trigger'
required: false
default: 'Manual e2e run'
permissions:
contents: read
concurrency:
group: playwright-mock-${{ github.ref }}
cancel-in-progress: true
env:
NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}'
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
jobs:
e2e_shards:
name: e2e (${{ matrix.stream_store }}, shard ${{ matrix.shard }}/2)
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request != null &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association))
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
stream_store: [memory, redis]
shard: [1, 2]
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
env:
E2E_CHROMIUM_CHANNEL: chrome
E2E_STREAM_STORE: ${{ matrix.stream_store }}
REDIS_URI: redis://127.0.0.1:6379
steps:
- uses: actions/checkout@v5
- name: Use Node.js 24.16.0
uses: actions/setup-node@v5
with:
node-version: '24.16.0'
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v5
with:
path: |
node_modules
client/node_modules
packages/client/node_modules
packages/data-provider/node_modules
packages/data-schemas/node_modules
packages/api/node_modules
api/node_modules
key: node-modules-e2e-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Restore data-provider build cache
id: cache-data-provider
uses: actions/cache@v5
with:
path: packages/data-provider/dist
key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
- name: Build data-provider
if: steps.cache-data-provider.outputs.cache-hit != 'true'
run: npm run build:data-provider
- name: Restore data-schemas build cache
id: cache-data-schemas
uses: actions/cache@v5
with:
path: packages/data-schemas/dist
key: build-data-schemas-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
- name: Build data-schemas
if: steps.cache-data-schemas.outputs.cache-hit != 'true'
run: npm run build:data-schemas
- name: Restore api build cache
id: cache-api
uses: actions/cache@v5
with:
path: packages/api/dist
key: build-api-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/api/src/**', 'packages/api/tsconfig*.json', 'packages/api/tsdown.config.mjs', 'packages/api/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json', 'packages/data-schemas/src/**', 'packages/data-schemas/tsconfig*.json', 'packages/data-schemas/tsdown.config.mjs', 'packages/data-schemas/package.json') }}
- name: Build api
if: steps.cache-api.outputs.cache-hit != 'true'
run: npm run build:api
- name: Restore client-package build cache
id: cache-client-package
uses: actions/cache@v5
with:
path: packages/client/dist
key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
- name: Build client-package
if: steps.cache-client-package.outputs.cache-hit != 'true'
run: npm run build:client-package
- name: Restore client app build cache
id: cache-client-app
uses: actions/cache@v5
with:
path: client/dist
key: build-client-app-e2e-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'client/src/**', 'client/public/**', 'client/scripts/post-build.cjs', 'client/index.html', 'client/package.json', 'client/vite.config.*', 'client/tsconfig*.json', 'client/tailwind.config.*', 'client/postcss.config.*', 'packages/client/src/**', 'packages/client/tailwind.preset.cjs', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }}
- name: Build client app
if: steps.cache-client-app.outputs.cache-hit != 'true'
run: npm run build:client
- name: Install Playwright runtime dependencies
timeout-minutes: 5
run: |
google-chrome --version
npx playwright install-deps chrome
- name: Run mock-LLM Tier-1 e2e
run: npx playwright test --config=e2e/playwright.config.mock.ts --shard=${{ matrix.shard }}/2
env:
CI: 'true'
- name: Upload Playwright HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v6
with:
name: playwright-report-${{ matrix.stream_store }}-${{ matrix.shard }}
path: e2e/playwright-report/**
retention-days: 7
if-no-files-found: ignore
- name: Upload traces & screenshots
if: failure()
uses: actions/upload-artifact@v6
with:
name: playwright-test-results-${{ matrix.stream_store }}-${{ matrix.shard }}
path: e2e/specs/.test-results/**
retention-days: 7
if-no-files-found: ignore
mcp_tool_list_changed:
name: MCP list_changed (replica count ${{ matrix.replicas }})
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request != null &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association))
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
replicas: [1, 2]
env:
CI: 'true'
E2E_CHROMIUM_CHANNEL: chrome
E2E_MCP_LIST_CHANGED: 'true'
E2E_REPLICAS: ${{ matrix.replicas }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js 24.16.0
uses: actions/setup-node@v4
with:
node-version: '24.16.0'
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: |
node_modules
client/node_modules
packages/client/node_modules
packages/data-provider/node_modules
packages/data-schemas/node_modules
packages/api/node_modules
api/node_modules
key: node-modules-e2e-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Build e2e dependencies
run: npm run e2e:prepare
- name: Install Playwright and Redis runtime dependencies
timeout-minutes: 5
run: |
google-chrome --version
npx playwright install-deps chrome
sudo apt-get update
sudo apt-get install -y redis-server redis-tools
- name: Start standalone Redis and Redis Cluster
run: |
redis-server --daemonize yes --port 6379
redis-cli -p 6379 ping
chmod +x redis-config/start-cluster.sh redis-config/stop-cluster.sh
./redis-config/start-cluster.sh
redis-cli -p 7001 cluster info
- name: Test MCP notifications with in-memory cache
env:
E2E_STREAM_STORE: memory
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Test MCP notifications with standalone Redis cache
env:
E2E_STREAM_STORE: redis
REDIS_URI: redis://127.0.0.1:6379
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Test MCP notifications with Redis Cluster cache
env:
E2E_STREAM_STORE: redis-cluster
REDIS_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
run: >-
npx playwright test --config=e2e/playwright.config.mock.ts
mcp-tool-list-changed.spec.ts --retries=0
- name: Upload Playwright HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-mcp-list-changed-${{ matrix.replicas }}-replicas
path: e2e/playwright-report/**
retention-days: 7
if-no-files-found: ignore
- name: Upload traces & screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-mcp-list-changed-results-${{ matrix.replicas }}-replicas
path: e2e/specs/.test-results/**
retention-days: 7
if-no-files-found: ignore
- name: Stop Redis processes
if: always()
run: |
./redis-config/stop-cluster.sh || true
redis-cli -p 6379 shutdown || true
e2e:
name: e2e
if: >-
always() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.pull_request != null &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)))
needs: [e2e_shards, mcp_tool_list_changed]
runs-on: ubuntu-latest
steps:
- name: Verify every Playwright job passed
if: >-
needs.e2e_shards.result != 'success' ||
needs.mcp_tool_list_changed.result != 'success'
run: exit 1