Skip to content

Lifecycle Hooks

Hooks run code around your tests. There are two run-level hooks and two test-level hooks:

HookRunsReceives
beforeAllOnce, before any test in the runrunInfo
beforeEachBefore every testorbit, testInfo
afterEachAfter every test (including failed ones)orbit, testInfo
afterAllOnce, after the whole runrunInfo
const { beforeAll, afterAll, beforeEach, afterEach, test } = require("orbittest");
beforeAll(async (runInfo) => {
console.log(`Run started: ${runInfo.runId}`);
});
beforeEach(async (orbit, testInfo) => {
console.log(`Starting ${testInfo.name}, attempt ${testInfo.attempt}`);
});
afterEach(async (orbit, testInfo) => {
if (testInfo.status === "failed") {
await orbit.screenshot(`reports/${testInfo.name}.png`);
}
});
afterAll(async (runInfo) => {
console.log(`Run finished: ${runInfo.status}`);
});

All four hooks in one file.

Test-level hooks (and the test itself, as a second argument) receive testInfo, which describes the current test:

FieldMeaning
name, file, indexWhich test this is and where it lives.
attempt, retry, retriesRetry state: attempt 2 means the first try failed.
timeoutThe effective time budget.
statusIn afterEach: “passed” or “failed”.
startedAt, endedAt, durationMsTiming.
errorThe failure error, when there is one.
artifactsPaths of screenshots and other evidence captured so far.

The most common pattern is conditional cleanup or evidence in afterEach, like the failure screenshot above. Because afterEach runs even when the test failed, it is the right place for anything that must always happen.

When every test file needs the same hooks, do not copy them around. Put them in one file and point the config at it:

orbittest.config.js
module.exports = {
globalSetup: "tests/setup.js"
};
tests/setup.js
const { beforeAll, afterEach } = require("orbittest");
beforeAll(async (runInfo) => {
console.log(`Starting run ${runInfo.runId}`);
});
afterEach(async (orbit, testInfo) => {
if (testInfo.status === "failed") {
await orbit.screenshot(`reports/${testInfo.name}.png`);
}
});

One setup file shared by the whole suite.

Hooks have a failure mode: they can make tests unreadable by moving the important parts off-screen. A reviewer opening a test should still be able to tell what user behavior it covers. Two rules keep you safe:

  • Put technical preparation in hooks: opening the base URL, seeding data, clearing artifacts. These are not part of the user story.
  • Keep user actions visible in the test, or in a clearly named helper called from the test (await loginAs(orbit, "admin") is fine; an invisible auto-login in beforeEach that the test never mentions is not).

Also be careful with beforeAll state. Anything created once and shared across tests is a flakiness risk in parallel runs: two workers can race on the same record. If shared state causes intermittent failures, move the setup into beforeEach or into the tests themselves and measure the actual cost; it is often smaller than feared.

Yes. Each attempt is a full test run, so beforeEach and afterEach run again. Check testInfo.attempt if a hook should behave differently on retries.

Can a test file have hooks and tests together?

Section titled “Can a test file have hooks and tests together?”

Yes. File-local hooks apply to that file. Suite-wide hooks belong in a globalSetup file.

The error is reported against the test run. Keep cleanup code defensive: check that the thing you are cleaning actually exists first.

  • Test Basics — How to structure readable end-to-end tests: arrange-act-assert, the orbit object, per-test options, and strong assertions.
  • Configuration — Every orbittest.config.js option explained: workers, retries, timeouts, browser display, CI behavior, and named environments.
  • Browser Storage — Cookies, localStorage, sessionStorage, saved login sessions with saveSession/loadSession, and session health checks.
  • Reports & Diagnostics — HTML, JSON, and JUnit reports, trace timelines, Smart Report browser evidence, step mode, and report cleanup.