Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Contributing

Explanation

E2e battery

How the browser e2e battery of Cluster ERP runs every _e2e_*.mjs suite against one test instance, and how the shared helpers keep the test database isolated.

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 failed with M equal 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:

  1. The runner lists the files in web-client/ whose names start with _e2e_ and end with .mjs, applies --filter and --exclude, and sorts them (web-client/_run_battery.mjs:42-46). With --list it prints the list and stops (web-client/_run_battery.mjs:48-52).
  2. It asks the target's /health route. If the answer is not 200, it exits with code 2 before running anything (web-client/_run_battery.mjs:54-70).
  3. 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; --timeout changes it (web-client/_run_battery.mjs:39-41, web-client/_run_battery.mjs:83-93).
  4. 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).
  5. At the end it asks /health again, 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.mjs exists in .github/workflows/ or deploy/.

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

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13