Skip to content

E2E runs on the Mac only (SRC-1421) ​

Owner 16.10.2026 (SRC-1421): "Bỏ cái e2e ngay đi". E2E was two thirds of the GitHub Actions bill (measured 09-16.10.2026: 96 PR runs with E2E at about 50 billed minutes each, 4,800 of 7,143 minutes). The e2e job is gone from ci.yml, and scope.sh no longer emits e2e* flags. The specs stay.

Where E2E runs now ​

WhenWhatCost
Before pushing a change to learn, tutors or marlins UIthe affected specs: git diff --name-only origin/main...HEAD | node scripts/e2e-affected.mjs, then cd apps/learn && npm run test:e2e -- <specs>$0
Every Sunday, npm run weeklysince 18.10.2026 every spec of every app (~1,130 cases; owner: "Nếu e2e test chạy trên local, ko tốn tiền thì có thể mở rộng gấp 10 lần số lượng test", SRC-1444); -- --quick-e2e runs only the ~10% list in scripts/lib/weekly-e2e.mjs. A red run keeps the weekly dependency upgrade back$0, about 8 minutes
GitHub CInothing; gate check-no-ci-e2e refuses a workflow that starts Playwright$0

Accepted risk: a regression that the affected specs miss reaches main and production until that night's full local run. A PR landed on its own during the day (hotfix, npm run land) gets no full run before deploy, so run the full suite locally for it.

History: selection on pull requests (SRC-1397, 15.10.2026) ​

The section below describes the selector while CI still ran E2E. The selector itself (scripts/e2e-affected.mjs, tested in npm run check:code) is now the local tool from the table above.

E2E was about 65% of the GitHub Actions bill: every push to a PR that touched apps/learn ran the whole suite (~33 billed minutes). Since SRC-1397 an ordinary PR runs only the specs it can affect.

The rule ​

scripts/e2e-affected.mjs reads the changed files (called from .github/scripts/scope.sh) and returns one of three modes. The first match wins for the whole change set:

  1. Full suite when any file is a shared dependency: root package.json or lockfile, any packages/* except the area-owned walk, grammar-catalog, books; workers/api/; learn's package.json, playwright.config.ts, vite.config.ts, index.html, public/, scripts/; any file at the root of apps/learn/src/ (router, auth, App.tsx, api.ts); src/components, src/lib, src/illustrations, src/figures; e2e fixtures and mocks; the selector itself, ci.yml and scope.sh. A learn file outside every known area also means full: unknown is treated as unsafe.

  2. Partial: the specs of each touched area (for example src/sat/ gives sat*.spec.ts, src/ielts/ gives ielts* and speak*, src/walk/ gives walk*, speakWalk, speakMatch and programs), any changed spec file itself, plus the baseline: smoke, console, and the two guards that check every program's pages, learnNoWwwLinks (SRC-1290) and headerMobile (320px NavBar). mobileAudit.spec.ts (~12 minutes) runs only if picked.

    Importers count. The selector reads the import graph of apps/learn/src (relative, @/ and @nemo12/* imports, static and dynamic) and walks it backwards from each changed file. Every area whose code imports the file, directly or through other files, adds its specs. If a chain reaches a file at the root of src/ that is not a router (App.tsx, main.tsx, learnRoutes.ts only mount pages), for example Programs.tsx rendering ielts/Chrome, the run is full. So the shared modules that live under src/ielts/ (Chrome, auth, api, PayCard, Motion...) run the full suite, while a page used by one area stays partial. A test checks this invariant for every area file.

  3. None when no learn area is touched, including docs-only PRs and learn unit-test-only PRs. Docs-only PRs already skipped the whole E2E job before this change.

The area table lives in the script (AREAS); its tests (node --test scripts/e2e-affected.test.mjs) run in npm run check:code, which CI runs on every code change. Adding a new top-level folder under apps/learn/src/ runs the full suite until it gets an area.

The selector only narrows. Whether the learn E2E step runs at all is still decided by e2e_learn in scope.sh: learn, tutors, and every @nemo12/* package that learn or tutors depends on (learn_pkgs: ai-teen-content, books, design-system, grammar-catalog, illustrations, rum, sentry, walk; check-scope-rules fails when a dependency is missing). So a PR touching only workers/api/ still runs no learn E2E, as before; one touching workers/api/ and apps/learn/ runs it all.

The safety net ​

  • The weekly check (npm run weekly; nightly merge until 18.10.2026) runs the whole suite locally (SRC-1444; before it, the critical 10%, SRC-1429, owner 18.10.2026). This replaces the owner decision of 14.10.2026 that the nightly PR always ran the full suite; --full-e2e keeps the old behaviour for a night that needs it.
  • A scope that cannot be computed (no changed files, script error) runs the full suite.
  • The push to main does not rerun E2E after a green PR (owner 15.10.2026, SRC-1373: "Tôi ko chấp nhận việc test full cả main"). Every green PR stamps its tree (ci/verified-tree), partial or full, and main skips E2E for a stamped tree and deploys at once. Before this change a partial PR left no stamp and main ran the full suite (~30 minutes) before deploying.

Accepted risk: a regression outside the changed area that a partial run misses can reach production. Since SRC-1444 the weekly check runs the full suite again, on the Mac. Between two Sundays, run npm run weekly -- --full-e2e (or the full npm run test:e2e) after a large or cross-cutting change. A PR landed on its own during the day is not covered by a full run before deploy.

Check names ​

Nothing is renamed. The E2E learn (1) and E2E learn (2) jobs still run and pass when their specs are skipped, and Cổng chất lượng still accepts success or skipped. npm run land waits for the ci run conclusion and npm run nightly waits for gh pr checks, so both see green.