---
url: https://docs.nemo12.com/ops/e2e-select.md
description: >-
  E2E left GitHub CI (SRC-1421): sessions run affected specs before a learn
  push; the weekly check runs the whole suite on the Mac (SRC-1444).
---

# 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

| When | What | Cost |
| --- | --- | --- |
| Before pushing a change to learn, tutors or marlins UI | the 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 weekly` | since 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 CI | nothing; 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.
