The e2e battery is the browser test run of Cluster ERP. One Node script, web-client/_run_battery.mjs, runs every _e2e_*.mjs suite in turn against one test instance and writes a log. Four shared modules give the suites their target address, their test account, their fixtures, and a clean home-screen layout. This page explains the run and the isolation rules; the suites themselves are listed in the e2e suite catalogue.
In short¶
- The runner executes the suites one after another, because they share one server and one database (
web-client/_run_battery.mjs:15-16). - A suite passes only when it prints a line of the form
N passed, M failedwithMequal to zero; a suite that exits without that line counts as a failure (web-client/_run_battery.mjs:18-20,web-client/_run_battery.mjs:110-119). - The target address and the test database name come from one shared module, and the default target is the local test instance, not the development server (
web-client/_shared_e2e_base.mjs:4-17,web-client/_shared_e2e_base.mjs:40-46). - Suites that touch the home layout must use the ui-state guard, which saves and restores the test account's layout (
web-client/_ui_state_guard.mjs:1-11,tests/test_e2e_db_isolation.py:177-200). - CI does not run the battery; it runs on the staging node before a tag is cut (
.github/workflows/ci.yml:21-22).
How it works¶
The run¶
The flowchart shows one battery run.
flowchart TD
start["node _run_battery.mjs"] --> list["collect _e2e_*.mjs, apply --filter and --exclude, sort"]
list --> h1{"GET /health equals 200?"}
h1 -->|no| stop["exit 2: server not answering"]
h1 -->|yes| loop["spawn next suite with node"]
loop --> wait{"result line printed before timeout?"}
wait -->|yes| add["add passed and failed to totals"]
wait -->|no| broken["count 1 failure, name the suite"]
add --> more{"more suites?"}
broken --> more
more -->|yes| loop
more -->|no| h2{"GET /health equals 200?"}
h2 -->|no| inc["exit 2: run inconclusive"]
h2 -->|yes| done["exit 0 if no failures, else 1"]In prose:
- The runner lists the files in
web-client/whose names start with_e2e_and end with.mjs, applies--filterand--exclude, and sorts them (web-client/_run_battery.mjs:42-46). With--listit prints the list and stops (web-client/_run_battery.mjs:48-52). - It asks the target's
/healthroute. If the answer is not 200, it exits with code 2 before running anything (web-client/_run_battery.mjs:54-70). - It starts each suite as a separate Node process and collects its output (
web-client/_run_battery.mjs:78-99). A suite that runs longer than the timeout is killed and recorded as timed out. The default timeout is 900 seconds;--timeoutchanges it (web-client/_run_battery.mjs:39-41,web-client/_run_battery.mjs:83-93). - It reads the result line, adds the counts, and names every suite with failures or without a result line (
web-client/_run_battery.mjs:110-119). - At the end it asks
/healthagain, writes the totals and the failing suites to the log, and exits with 0 when nothing failed, 1 when something failed, and 2 when the server stopped answering (web-client/_run_battery.mjs:124-137).
Runner options¶
| Option | Effect | Source |
|---|---|---|
--log <name> |
Log file name; the default is battery.log |
web-client/_run_battery.mjs:8-9, web-client/_run_battery.mjs:33 |
--filter <text> |
Run only suites whose file name contains the text | web-client/_run_battery.mjs:10, web-client/_run_battery.mjs:44 |
--exclude <a,b> |
Leave out suites whose file name contains any of the texts | web-client/_run_battery.mjs:11, web-client/_run_battery.mjs:35-38 |
--list |
Print the suites that would run, then stop | web-client/_run_battery.mjs:12, web-client/_run_battery.mjs:48-52 |
--timeout <seconds> |
Per-suite limit, default 900 | web-client/_run_battery.mjs:13, web-client/_run_battery.mjs:41 |
A typical full run, from web-client/:
node _run_battery.mjs --log battery
Source: web-client/_run_battery.mjs:8-13.
The shared base module¶
Every suite imports web-client/_shared_e2e_base.mjs (197 of 197 suites, counted with grep -L). It provides:
| Export | Purpose | Source |
|---|---|---|
APP_URL, API_URL, E2E_BASE |
Target addresses built from CLUSTER_E2E_BASE |
web-client/_shared_e2e_base.mjs:14-17 |
E2E_TENANT_DOMAIN, asLogin |
Sign-in through one tenant on a multi-tenant node, from CLUSTER_E2E_TENANT_DOMAIN |
web-client/_shared_e2e_base.mjs:19-38 |
E2E_DB_NAME, E2E_DB_HOST |
The database a suite may reach directly, from CLUSTER_E2E_DB and CLUSTER_E2E_DB_HOST |
web-client/_shared_e2e_base.mjs:40-48 |
E2E_SERVER_ENV, SELF_HOSTED_MT_BASE |
Environment and address for suites that start their own server | web-client/_shared_e2e_base.mjs:61-90, web-client/_shared_e2e_base.mjs:108 |
ROOT, PG_BIN, pgTool |
Repository path and PostgreSQL client tools, resolved per platform | web-client/_shared_e2e_base.mjs:110-130 |
waitForServerHealth, killTree |
Wait for a self-started server; stop a process tree on every platform | web-client/_shared_e2e_base.mjs:139-184 |
goHome, gotoHome |
Return to the home screen even when a background app was restored | web-client/_shared_e2e_base.mjs:186-228 |
holdPress |
A long press that holds until the page confirms the gesture | web-client/_shared_e2e_base.mjs:230-261 |
appModuleUrl |
The URL of a web module as the page loaded it | web-client/_shared_e2e_base.mjs:277-287 |
report |
Print the result line and wait until it reaches the pipe | web-client/_shared_e2e_base.mjs:263-295 |
Passwords used by the direct database helpers come from environment variables; their values are omitted under the security disclosure policy.
The ui-state guard¶
The home layout belongs to the user account on the server (see server-side UI state). guardUiState signs in as the dedicated test account, refuses to continue as the administrator account, saves the account's stored layout keys, writes a known test board, and restores the originals when the process ends, also on a crash or an interrupt (web-client/_ui_state_guard.mjs:34-49, web-client/_ui_state_guard.mjs:114-195). The keys it guards are listed in DEFAULT_KEYS (web-client/_ui_state_guard.mjs:106-113). guardUiStateWithApps adds app tiles to the test board (web-client/_ui_state_guard.mjs:91-97). 118 suites call guardUiState (counted with grep -l).
Fixtures¶
web-client/_shared_fixtures.mjs creates preconditions through the public API, the way a user would, and does nothing when the data already exists (web-client/_shared_fixtures.mjs:1-4). Examples are ensureGroupWorkweek, ensureRfqWithVendor, and ensureRichBom (web-client/_shared_fixtures.mjs:42, web-client/_shared_fixtures.mjs:61, web-client/_shared_fixtures.mjs:266). 18 suites import it. web-client/_shared_history.mjs builds sales-order and production history for the analytics suites in a window relative to today (web-client/_shared_history.mjs:1-13, web-client/_shared_history.mjs:22-29). 7 suites import it.
Why it is built this way¶
The reasons are stated in the code. The default target is the test instance so that "a careless run cannot reach the user's data" (web-client/_shared_e2e_base.mjs:4-13). The database name travels with the base address because a suite once wrote to a different database than the one under test (tests/test_e2e_db_isolation.py:3-18). The runner kills a hung suite so that one wedged browser does not stop a nightly run (web-client/_run_battery.mjs:75-77). report waits for the write because process.exit() lost result lines in a pipe under load (web-client/_shared_e2e_base.mjs:263-276).
Rules and boundaries¶
tests/test_e2e_db_isolation.py reads every suite as text and enforces these rules:
| Rule | Source |
|---|---|
A suite that starts a child process able to reach the database sets CLUSTER_ERP_DB_NAME from E2E_DB_NAME |
tests/test_e2e_db_isolation.py:64-106 |
A self-hosted suite names only databases that carry the test marker |
tests/test_e2e_db_isolation.py:51-91 |
| No suite writes a server address or port as a literal | tests/test_e2e_db_isolation.py:109-128 |
| The shared helper is not named like a suite | tests/test_e2e_db_isolation.py:131-139 |
No single-quoted string tries to interpolate ${...} |
tests/test_e2e_db_isolation.py:142-174 |
| A suite that reads or writes the home layout uses the ui-state guard | tests/test_e2e_db_isolation.py:177-200 |
Seven suites start their own server and are self-hosted (counted with grep -l CLUSTER_ERP_SERVER_PORT): _e2e_operator_console.mjs, _e2e_residency.mjs, _e2e_sep_companies.mjs, _e2e_sep_roles.mjs, _e2e_tenant_isolation.mjs, _e2e_tenant_regions.mjs, and _e2e_tenant_signup.mjs. The runner comment says these and the region suite are excluded when the battery runs against a remote node (web-client/_run_battery.mjs:35-37).
Interaction with other parts¶
The battery needs a running test instance; how to start a server is on local development setup. The promotion from staging to production uses the battery result; see environments and promotion. The pytest isolation guard runs in CI even though the battery does not (.github/workflows/ci.yml:124).
Limits and open points¶
- The
web-client/folder also holds older numbered PowerShell run scripts, one per historical run, and many log files from past runs. The runner header says it replaces those per-run scripts (web-client/_run_battery.mjs:5-6). They are not part of the current procedure and are not described one by one. - Not determined from code: a schedule for the battery. No scheduler entry for
_run_battery.mjsexists in.github/workflows/ordeploy/.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| e2e battery | _run_battery.mjs |
The sequential run of every browser suite against one test instance, with one log and one total. | E2e bataryası | web-client/_run_battery.mjs:1-20 |
| e2e suite | _e2e_*.mjs |
One Node script that drives the web app or the API and prints a result line. | E2e süiti | web-client/_run_battery.mjs:42-46 |
| result line | report |
The line N passed, M failed that a suite prints and the runner reads. |
Sonuç satırı | web-client/_shared_e2e_base.mjs:289-295 |
| self-hosted suite | SELF_HOSTED_MT_BASE |
A suite that starts its own server and databases instead of using the shared test instance. | Kendi sunuculu süit | tests/test_e2e_db_isolation.py:51-60 |
| ui-state guard | guardUiState |
The helper that gives a suite a known home layout and restores the account's layout afterwards. | Arayüz durumu bekçisi | web-client/_ui_state_guard.mjs:114 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.