orbittest
v3.4.0
Published
Browser automation test runner built on Chrome DevTools Protocol.
Downloads
435
Maintainers
Readme
OrbitTest
Intent-first end-to-end browser testing built on Chrome DevTools Protocol.
OrbitTest lets you write tests using what users see — visible labels, roles, and text — rather than CSS selectors or XPath expressions. It handles browser lifecycle, test parallelism, retries, trace capture, structured failure reporting, and CI pipeline integration out of the box.
Test what users see, not what developers write.
Table of Contents
- Overview
- Installation
- Quick Start
- Configuration
- Writing Tests
- CLI Reference
- Environment Variables
- Browser Actions
- Locators
- Working with Elements
- Frames and Shadow DOM
- Browser Storage and Sessions
- Alerts, Notifications, and Windows
- Mobile Testing
- Visual Automation
- Reports and Diagnostics
- CI/CD Integration
- UI
- Forge — Test Recorder
- TypeScript
- Best Practices
- Troubleshooting
- Contributing
- License
Overview
Feature Status
| Feature | Status |
|---|---|
| UI automation | Stable |
| Test lifecycle hooks | Stable |
| Intent-based locator engine | Stable |
| Text readers (text, visibleText, domText) | Stable |
| Browser storage and session management | Stable |
| Alerts, notifications, and window/tab management | Stable |
| Frames and Shadow DOM traversal | Stable |
| HTML/JSON/JUnit reports | Stable |
| CI/CD mode (sharding, fail-fast, annotations) | Stable |
| Step trace and trace timeline | Stable |
| Smart Report (browser evidence collection) | Stable |
| UI local dashboard | Stable |
| Forge test recorder | Stable |
| Android mobile provider (@orbittest/mobile) | Phase 1 |
| Visual automation (canvas, WebGL, pixel checks) | Experimental |
| API testing | Planned |
Official Project
OrbitTest is created and maintained by Abhay. Forks and modified versions are permitted under the Apache License 2.0, but must preserve copyright, license, and notice attribution and must not imply affiliation with the official project. The OrbitTest name, logo, and branding are governed by TRADEMARKS.md.
Installation
Install as a project dependency:
npm install orbittestOr install globally:
npm install -g orbittestVerify the installation:
orbittest --versionRequirements: Node.js 18 or later. A Chromium-based browser is required at runtime. OrbitTest will use the system Chrome installation by default, or you can specify a path via ORBITTEST_CHROME_PATH.
Quick Start
Initialize a new project:
orbittest initThis creates:
orbittest.config.js
tests/
example.test.js
reports/Write a test:
const { test, expect } = require("orbittest");
test("Login flow", async (orbit) => {
await orbit.open("https://example.com");
await orbit.click("Login");
await orbit.type("Email", "[email protected]");
await orbit.type("Password", "secret");
expect(await orbit.hasText("Dashboard")).toBe(true);
});Run it:
orbittest runOutput:
Passed: 1
Failed: 0
Report: reports/runs/<run-id>/report.htmlNo CSS selectors. No XPath. No framework configuration needed for basic use.
Configuration
OrbitTest reads orbittest.config.js from the project root. All fields are optional — the defaults work for most projects.
module.exports = {
// Test discovery
testDir: "tests",
testMatch: ["**/*.test.js", "**/*.spec.js"],
// Output
reportsDir: "reports",
// Hooks
globalSetup: [],
// Parallelism
workers: 1,
maxWorkers: 4,
// Retry policy
retries: 0,
// Timeouts (milliseconds)
testTimeout: 30000,
actionTimeout: 0,
// Browser display
browser: {
display: "auto" // "auto" | "show" | "hide"
},
// Optional web/mobile provider config
use: {
web: {
browser: "chrome",
headless: null
},
mobile: null
},
// Experimental features
experimental: {
ui: true,
visualAutomation: true,
apiTesting: false
},
// Local failure report server
openReportOnFailure: {
enabled: !process.env.CI,
port: 0
},
// CI/CD mode
ci: {
enabled: Boolean(process.env.CI),
retries: 1,
trace: "on-failure", // "always" | "on-failure" | "never"
screenshot: "on-failure", // "always" | "on-failure" | "never"
failFast: false,
maxFailures: 0,
shard: process.env.ORBITTEST_SHARD || null,
summary: true,
junit: true,
githubAnnotations: Boolean(process.env.GITHUB_ACTIONS)
},
// Smart Report
smartReport: false,
smartReportSlowRequestMs: 2000,
// Named environments
environments: {
staging: {
reportsDir: "reports/staging"
}
}
};Browser display defaults to "auto": the browser is visible on local runs, hidden in CI, and always visible in --step mode. Override for a single run with --show-browser or --hide-browser.
Named environments can be activated with --env <name>, which merges environment-specific config values over the base config.
CLI flags always override config file values.
Writing Tests
Basic test
const { test, expect } = require("orbittest");
test("Home page loads", async (orbit) => {
await orbit.open("https://example.com");
expect(await orbit.hasText("Example Domain")).toBe(true);
});Test options
test("Slow flow", { retries: 2, timeout: 60000 }, async (orbit) => {
await orbit.open("https://example.com");
});Lifecycle hooks
Hooks run at the run level or around each test. They receive run info or test info from OrbitTest.
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}`);
});testInfo fields: name, file, index, attempt, retry, retries, timeout, status, startedAt, endedAt, durationMs, error, artifacts.
Global setup file
Use globalSetup to place shared hooks in a single file:
// orbittest.config.js
module.exports = {
globalSetup: "tests/setup.js"
};// tests/setup.js
const { beforeAll, afterAll, beforeEach, afterEach } = require("orbittest");
beforeAll(async (runInfo) => {
// runs once before all tests in the suite
});
beforeEach(async (orbit, testInfo) => {
// runs before each test
});
afterEach(async (orbit, testInfo) => {
// runs after each test
});
afterAll(async (runInfo) => {
// runs once after all tests complete
});Test files do not need an explicit run() call when invoked through the CLI.
CLI Reference
Commands
| Command | Description |
|---|---|
| orbittest run [files] | Run tests |
| orbittest init | Initialize a new project |
| orbittest ui | Open the local UI dashboard |
| orbittest forge [url] | Open the Forge test recorder |
| orbittest clean-reports | Remove old report runs |
orbittest run flags
| Flag | Description |
|---|---|
| --workers <n> | Number of parallel workers |
| --retries <n> | Retry count per test |
| --timeout <ms> | Test timeout in milliseconds |
| --env <name> | Activate a named environment from config |
| --reports-dir <path> | Override the reports output directory |
| --ci | Enable CI mode |
| --fail-fast | Stop after the first test failure |
| --max-failures <n> | Stop after N test failures |
| --shard <n/total> | Run a shard of the test suite (e.g. 1/4) |
| --github-annotations | Emit GitHub Actions annotation format on failure |
| --trace | Capture a step-by-step trace |
| --smart-report | Capture browser evidence for failure diagnosis |
| --step | Live step-by-step debugging with the Orbit Inspector |
| --show-browser | Force the browser to be visible |
| --hide-browser | Force the browser to be hidden |
| --verbose | Print OrbitTest's internal browser/action logs |
| --no-open-report-on-failure | Suppress the local failure report server |
| --report-port <port> | Set a fixed port for the local report server |
orbittest clean-reports flags
| Flag | Description |
|---|---|
| --dry-run | Preview what would be removed without deleting |
| --passed <n> | Number of passed runs to keep (default: 10) |
| --failed <n> | Number of failed runs to keep (default: 30) |
| --max-age-days <n> | Remove runs older than N days (default: 30) |
orbittest ui flags
| Flag | Description |
|---|---|
| --port <n> | Port for the UI server |
| --no-open | Start UI without opening a browser tab |
| --reports-dir <path> | Reports directory to browse |
orbittest forge flags
| Flag | Description |
|---|---|
| --output <path> | Write the recorded script to a file |
Environment Variables
| Variable | Description |
|---|---|
| ORBITTEST_CHROME_PATH | Path to a custom Chrome/Chromium executable |
| ORBITTEST_SHARD | Shard assignment in <n>/<total> format (e.g. 1/4) |
| ORBITTEST_UI_EVENTS | Internal: enables SSE event stream for the UI |
| ORBITTEST_STEP_AUTO_CONTINUE | Internal: auto-continue in step mode |
| CI | Standard CI flag; enables headless browser and CI mode defaults |
| GITHUB_ACTIONS | Standard GitHub Actions flag; enables GitHub annotation output |
Browser Actions
Navigation
await orbit.open("https://example.com");Clicks
await orbit.click("Login");
await orbit.doubleClick("Open");
await orbit.rightClick("File");
await orbit.hover("Menu");Click actions render a brief red dot at the actual click coordinate, making live debugging and trace screenshots easier to follow. Disable the marker for a single action:
await orbit.click("Login", { visualize: false });Typing
await orbit.type("Email", "[email protected]");OrbitTest resolves the target input by label, placeholder, name attribute, or accessible text.
Text assertions
const present = await orbit.hasText("Welcome");Screenshots
await orbit.screenshot("reports/home.png");Waiting
await orbit.waitForText("Dashboard");
await orbit.waitForText("Dashboard", { timeout: 10000 });
await orbit.waitFor(orbit.css(".toast"));
await orbit.waitFor(orbit.css(".toast"), { timeout: 5000 });Prefer waitFor and waitForText over fixed delays. Fixed delays are fragile on slow machines and in CI.
await orbit.wait(1000); // use only when no event-based alternative existsPage state
const title = await orbit.title();
const url = await orbit.url();
const page = await orbit.pageState();
expect(page.title).toContain("Dashboard");
expect(page.url).toContain("example.com");JavaScript evaluation
const value = await orbit.evaluate(() => document.title);
await orbit.evaluate((text) => console.log(text), "hello");Locators
OrbitTest is intent-first: most actions accept a visible text string and resolve the target automatically. Use explicit locators when the text-based resolution is ambiguous or when you need to target a specific structural element.
| Locator | Usage |
|---|---|
| Text string | await orbit.click("Login") |
| CSS selector | await orbit.click(orbit.css("#login-btn")) |
| XPath | await orbit.click(orbit.xpath("//button[text()='Login']")) |
| Role | await orbit.click(orbit.getByRole("button", "Login")) |
| Attribute | await orbit.click(orbit.getByAttribute("data-testid", "submit")) |
| Object literal | await orbit.click({ css: "#login" }) |
Working with Elements
Existence check
expect(await orbit.exists(orbit.css(".success"))).toBe(true);
expect(await orbit.exists(orbit.css(".error"), { timeout: 0 })).toBe(false);Reading text
Three helpers cover different use cases:
| Helper | Returns | Hidden content |
|---|---|---|
| orbit.text(locator) | User-facing text: DOM text, form values, ARIA label, title, alt | May include useful ARIA sources |
| orbit.visibleText(locator) | Rendered visible text only | Excluded |
| orbit.domText(locator) | Full DOM text from the element tree | Included |
const label = await orbit.visibleText(orbit.css("#save-button"));
expect(label).toBe("Save");
const domContent = await orbit.domText(orbit.css("#server-message"));
expect(domContent).toContain("Hidden diagnostic id");All text readers trim and normalize whitespace.
Collections
Use orbit.all() when a locator matches multiple elements:
const buttons = await orbit.all(orbit.css("button"));
for (const button of buttons) {
if (button.visible) {
await orbit.click(button);
}
}Each item in the returned array is a reusable locator snapshot with tag, text, visible, and attributes fields:
{
type: "nth",
locator: { type: "css", selector: "button" },
index: 0,
tag: "button",
text: "Add to cart",
visible: true,
attributes: { id: "add", "data-testid": "add-button" }
}Supported actions on collection items: click, hover, doubleClick, rightClick, type, exists, waitFor, text, visibleText, domText.
If clicking an item removes or reorders elements, re-fetch the collection before the next interaction:
while (await orbit.exists(orbit.css(".delete"), { timeout: 0 })) {
const deleteButtons = await orbit.all(orbit.css(".delete"));
await orbit.click(deleteButtons[0]);
}orbit.elements() is an alias for orbit.all().
Frames and Shadow DOM
iFrames
orbit.frame() returns a scoped frame object with the same intent-first API:
const billing = await orbit.frame(orbit.getByAttribute("title", "Billing"));
await billing.type("Email", "[email protected]");
await billing.click("Save billing");
expect(await billing.exists("Saved")).toBe(true);Nested frames can be resolved step by step or as a path:
// Step by step
const checkout = await orbit.frame(orbit.getByAttribute("title", "Checkout"));
const vault = await checkout.frame(orbit.getByAttribute("title", "Vault"));
// As a path
const vault = await orbit.frame([
orbit.getByAttribute("title", "Checkout"),
orbit.getByAttribute("title", "Vault")
]);For short-lived frame scopes:
await orbit.withFrame(orbit.css("iframe.payment"), async (frame) => {
await frame.click("Pay");
});Shadow DOM
orbit.shadow() works with both open and closed shadow roots:
const profile = await orbit.shadow(orbit.css("user-profile"));
await profile.type("Email", "[email protected]");
await profile.click("Save");
expect(await profile.text(orbit.css("#status"))).toBe("Saved");Nested shadow roots can be chained or resolved as a path:
// Step by step
const shell = await orbit.shadow(orbit.css("app-shell"));
const panel = await shell.shadow(orbit.css("settings-panel"));
// As a path
const panel = await orbit.shadow([
orbit.css("app-shell"),
orbit.css("settings-panel")
]);For short-lived shadow scopes:
await orbit.withShadow(orbit.css("confirm-card"), async (shadow) => {
await shadow.click("Confirm");
});Browser Storage and Sessions
OrbitTest starts each test with a clean browser profile. Use orbit.storage when a test needs explicit cookies, localStorage, sessionStorage, or a persisted login state.
Cookies
await orbit.storage.setCookie({ name: "session", value: "abc123", httpOnly: true, secure: true });
await orbit.storage.setCookie("theme", "dark");
const cookies = await orbit.storage.cookies();
await orbit.storage.deleteCookie("session");
await orbit.storage.clearCookies();Local and session storage
await orbit.storage.setLocal("token", "abc123");
const token = await orbit.storage.getLocal("token");
await orbit.storage.removeLocal("token");
await orbit.storage.clearLocal();
await orbit.storage.setSession("view", "compact");
const view = await orbit.storage.getSession("view");
await orbit.storage.removeSession("view");
await orbit.storage.clearSession();Storage values follow browser behavior and are stored as strings.
Save and restore login state
// Save after login
await orbit.open("https://example.com/login");
await orbit.type("Email", "[email protected]");
await orbit.type("Password", "secret");
await orbit.click("Login");
await orbit.waitForText("Dashboard");
await orbit.storage.saveSession("auth/session.json");
// Restore in a later test
await orbit.open("https://example.com");
await orbit.storage.loadSession("auth/session.json");
await orbit.open("https://example.com/dashboard");
expect(await orbit.hasText("Dashboard")).toBe(true);saveSession() captures cookies, current-origin localStorage, and current-origin sessionStorage. HttpOnly cookies are supported via CDP.
Session health
expectHealthySession() fails a test early when a saved session is missing, expired, or about to expire:
await orbit.storage.loadSession("auth/admin-session.json");
await orbit.storage.expectHealthySession({ minMinutes: 15, requireCookie: true });
await orbit.open("https://example.com/admin");inspect() provides a safe summary of the current browser state with redacted values:
const state = await orbit.storage.inspect();
console.log(state.auth.present);
console.log(state.auth.signalCount);
console.log(state.cookies.authLikeCount);
console.log(state.recommendations);Full storage API
orbit.storage.cookies()
orbit.storage.setCookie(cookieOrName, value?)
orbit.storage.setCookies(cookieArray)
orbit.storage.deleteCookie(name)
orbit.storage.clearCookies()
orbit.storage.local()
orbit.storage.getLocal(key)
orbit.storage.setLocal(key, value)
orbit.storage.removeLocal(key)
orbit.storage.clearLocal()
orbit.storage.session()
orbit.storage.getSession(key)
orbit.storage.setSession(key, value)
orbit.storage.removeSession(key)
orbit.storage.clearSession()
orbit.storage.saveSession(path)
orbit.storage.loadSession(path)
orbit.storage.clear()
orbit.storage.inspect()
orbit.storage.health()
orbit.storage.expectHealthySession(options?)
orbit.storage.expectSession()Alerts, Notifications, and Windows
JavaScript dialogs
await orbit.open("https://example.com");
await orbit.click("Trigger Alert");
expect(await orbit.alertText()).toBe("hello");
await orbit.acceptAlert();
await orbit.acceptAlert({ promptText: "Abhay" }); // for prompt dialogs
await orbit.dismissAlert(); // for confirm dialogswaitForAlert() returns dialog details: type, message, url, handled.
Browser notifications
await orbit.grantNotifications();
expect(await orbit.getNotificationPermission()).toBe("granted");
await orbit.denyNotifications("https://example.com");
expect(await orbit.getNotificationPermission()).toBe("denied");
await orbit.resetNotificationPermission();Pass an origin string or { origin } to target a specific site.
Windows and tabs
const windows = await orbit.listWindows();
const main = windows.find((w) => w.active);
await orbit.click("Open report");
const popup = await orbit.waitForWindow({ switchTo: true });
expect(await orbit.hasText("Report")).toBe(true);
await orbit.switchToWindow(0); // by index
await orbit.switchToWindow(main.id); // by target ID
await orbit.closeWindow(popup.id);Windows can be selected by index, target ID, URL/title text, regular expression, or predicate function. newWindow(url) opens a new tab and switches to it by default.
Mobile Testing
OrbitTest keeps mobile support in a separate provider package so the main web runner stays small and stable.
npm install orbittest @orbittest/mobileAndroid requirements:
- Android SDK platform tools installed
- USB debugging enabled on the device
adb devicesshows the device asdevice- OrbitTest Desktop may inject
DEVICE_SERIAL,ADB_PATH, andPROJECT_ROOT
Configure mobile in orbittest.config.js:
module.exports = {
use: {
mobile: {
provider: "@orbittest/mobile",
platform: "android",
adbPath: process.env.ADB_PATH || "adb",
deviceSerial: process.env.DEVICE_SERIAL,
apk: "./app.apk",
appPackage: "com.myapp",
appActivity: ".MainActivity",
artifactsDir: "orbittest-results",
screenshotOnFailure: true,
logcatOnFailure: true,
uiDumpOnFailure: true
}
}
};Write a mobile test:
const { test, expect } = require("orbittest");
test("mobile smoke test", async ({ orbit }) => {
await orbit.wakeUp();
await orbit.installApp();
await orbit.launchApp();
await orbit.waitForText("Login", 10000);
await expect(orbit).toHaveText("Login");
});Hybrid web + mobile tests use both contexts:
test("same user works on web and mobile", async ({ page, orbit }) => {
await page.goto("https://app.example.com");
await page.clickText("Create account");
await orbit.installApp();
await orbit.launchApp();
await orbit.waitForText("Welcome");
});Useful commands:
orbittest devices
orbittest doctor
orbittest run tests/mobile-smoke.test.js@orbittest/mobile uses ADB and UIAutomator directly. It does not use Appium, WebdriverIO, Detox, Maestro, or Playwright mobile. Mobile test reports include a Mobile Evidence section with device/app details, screenshot preview, UIAutomator summary, and artifact links. On failed mobile tests, OrbitTest asks the provider to save screenshot, UI dump, logcat, error, and result artifacts under orbittest-results.
Troubleshooting:
- If a device is
unauthorized, unlock it and accept the USB debugging prompt. - If UIAutomator dump fails, verify the screen is awake and no system permission dialog is blocking the app.
- If Desktop runs tests, confirm it passes
DEVICE_SERIAL,ADB_PATH, andPROJECT_ROOTto the child Node.js process.
Visual Automation
Use visual automation when an application renders pixels rather than standard HTML elements — canvas games, WebGL scenes, maps, charts, remote desktops, or custom UI frameworks.
expect(await orbit.exists(orbit.css("canvas"))).toBe(true);
const changed = await orbit.visual.changed(async () => {
await orbit.evaluate(() => window.app.update());
});
expect(changed).toBe(true);Coordinate actions
await orbit.mouse.click(680, 450);
await orbit.mouse.drag({ x: 680, y: 450 }, { x: 760, y: 360 });
await orbit.mouse.wheel(0, -500);Visual checks
await orbit.visual.snapshot("reports/state.png");
await orbit.visual.expectPixel({ x: 30, y: 30 }, "#ff0000", { tolerance: 5 });
await orbit.visual.waitForStable();
await orbit.visual.expectChanged(async () => { /* action */ });
const point = await orbit.visual.findColor("#df1f1f", { tolerance: 70 });
await orbit.visual.clickColor("#df1f1f", { tolerance: 70 });
const pixel = await orbit.visual.pixel({ x: 100, y: 100 });Visual automation is experimental. The API is stable but coverage of edge cases may be limited.
Reports and Diagnostics
Output structure
Every run produces:
reports/
latest.html
latest.json
latest-summary.json
latest-junit.xml
runs/
<run-id>/
report.html
report.json
summary.json
junit.xml
artifacts/
screenshots/
traces/Report contents
- Total tests, pass/fail/flaky/skip counts, and duration
- Failure diagnostics with likely cause and recommended next actions
- Inline failure screenshot
- Error message and stack trace
- Source code frame for the failing line
- Trace timeline (when
--traceis used) - Smart browser evidence (when
--smart-reportis used) - Mobile evidence for Android tests: device details, app context, screenshots, UI dumps, and logcat links on failure
Trace
--trace captures a step-by-step timeline for every test:
orbittest run tests/login.test.js --traceThe HTML report includes a Trace Timeline showing every orbit.* step with status, duration, URL, title, and screenshot links. Raw trace files are written to reports/runs/<run-id>/artifacts/traces/.
Smart Report
--smart-report captures browser evidence for failure diagnosis:
orbittest run tests/login.test.js --smart-reportSmart Report collects console errors, JavaScript exceptions, failed network requests, slow requests, and recent navigation events. If it detects a clear application failure (failed API request, unauthorized response, client-side crash), it marks the test failed even without an explicit assertion.
Step mode
--step opens the Orbit Inspector for live debugging:
orbittest run tests/login.test.js --stepStep mode runs with one worker, disables all timeouts, pauses before each orbit.* action, and keeps the browser open. A trace is written automatically. Click actions display a red dot at the exact coordinate.
Failure report server
On failed local runs, OrbitTest starts a local report server and opens the report in your browser automatically. Disable for one run:
orbittest run --no-open-report-on-failure
orbittest run --report-port 9323 # fixed portReport retention
Preview what would be removed:
orbittest clean-reports --dry-runRemove old reports:
orbittest clean-reports
orbittest clean-reports --passed 20 --failed 50 --max-age-days 60Default retention: 1 latest run, 10 passed runs, 30 failed runs, maximum age 30 days.
CI/CD Integration
Enable CI mode with --ci:
orbittest run --ci --workers 4CI mode suppresses the local report server, captures traces and screenshots only on failure by default, and writes machine-readable output files.
Sharding
Split a suite across parallel jobs:
orbittest run --ci --shard 1/4
orbittest run --ci --shard 2/4
orbittest run --ci --shard 3/4
orbittest run --ci --shard 4/4Each job receives a disjoint slice of the registered test list. Skipped shards are reflected in summary.json, junit.xml, and the HTML report.
Failure control
orbittest run --ci --fail-fast # stop after the first failure
orbittest run --ci --max-failures 3 # stop after three failuresFlaky detection
When a test fails on the first attempt and passes on a retry, OrbitTest marks it flaky, preserves the failure evidence, and exits with success unless another test fails. Flaky tests are reported in all output formats.
GitHub Actions annotations
orbittest run --ci --github-annotationsEmits failure annotations in GitHub Actions format, pointing to the failing source line.
Example GitHub Actions workflow
name: e2e
on:
pull_request:
push:
branches: [main]
jobs:
orbittest:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- shard: 1/2
artifact: shard-1
- shard: 2/2
artifact: shard-2
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx orbittest run --ci --workers 2 --shard ${{ matrix.shard }} --github-annotations
- uses: actions/upload-artifact@v4
if: always()
with:
name: orbittest-report-${{ matrix.artifact }}
path: reports/UI
OrbitTest UI is a local web dashboard for day-to-day test work:
orbittest ui
orbittest ui --port 9323
orbittest ui --no-open
orbittest ui --reports-dir reports/stagingOrbitTest UI provides:
- Run all tests or a single test file
- Run presets: Local, Evidence (with Smart Report), CI mode
- Live command preview before launching
- Toggle trace, Smart Report, CI mode, and headless/headed browser
- Live streaming command output
- Filter test files and reports
- Browse previous run reports
- Open HTML, JSON, and JUnit artifacts inline
- Inline failure messages without navigating folders
Forge — Test Recorder
Forge opens a recorder panel alongside a fresh browser window. It records your interactions and generates an OrbitTest script.
orbittest forge
orbittest forge https://example.com
orbittest forge https://example.com --output tests/login.test.jsBy default, Forge copies the script to the clipboard only. Pass --output to write a file. Press Ctrl+C in the terminal to stop and print the script.
Forge captures: clicks, double-clicks, right-clicks, typing (without passwords or token-like values), select changes, visible checks, navigation, and stable locators (role, text, attribute, fallback CSS).
Generated scripts are plain OrbitTest files:
const { test, expect } = require("orbittest");
test("Login flow", async (orbit) => {
await orbit.open("https://example.com");
await orbit.click("Login");
await orbit.type("Email", "[email protected]");
await orbit.type("Password", process.env.ORBITTEST_PASSWORD || "");
expect(await orbit.exists(orbit.getByRole("heading", "Dashboard"))).toBe(true);
});TypeScript
OrbitTest ships TypeScript type definitions in index.d.ts. Import types directly in .ts files or use them in JSDoc:
import { test, expect } from "orbittest";// @ts-check
const { test, expect } = require("orbittest");Type definitions cover test, expect, lifecycle hooks, orbit.* actions, locator types, testInfo, and runInfo.
Best Practices
Use intent-based actions by default. They are readable, resilient to markup changes, and match what users actually see.
await orbit.click("Login"); // preferred
await orbit.click("#login-btn"); // use when text-based resolution is ambiguousWait for state, not time. Fixed delays fail on slow machines and are invisible in traces.
await orbit.waitForText("Dashboard"); // preferred
await orbit.waitFor(orbit.css(".ready")); // preferred
await orbit.wait(2000); // avoidScope frame and shadow interactions. Use withFrame() and withShadow() for single-action scopes to avoid leaving dangling frame references.
Restore session state explicitly. OrbitTest starts each test with a clean browser. If your test suite needs a logged-in state, use saveSession / loadSession rather than repeating the login flow.
Use --smart-report in CI. Smart Report provides network and console evidence that makes failures understandable without manual browser reproduction.
Keep assertions close to actions. Assert on the result of each significant step rather than batching assertions at the end of a test.
Troubleshooting
Element not found
The element may not be visible yet or the text does not match exactly. Add a waitFor before interacting and verify the visible text in the running browser.
await orbit.waitFor(orbit.css(".modal"));
await orbit.click("Confirm");Multiple elements match
Use a more specific locator to disambiguate.
await orbit.click(orbit.getByRole("button", "Submit"));
await orbit.click(orbit.getByAttribute("data-testid", "submit-form"));Timeout exceeded
Increase the timeout on the specific action or globally in config.
await orbit.waitForText("Dashboard", { timeout: 15000 });// orbittest.config.js
module.exports = { testTimeout: 60000 };Content inside a frame or shadow root
OrbitTest actions target the top-level page by default. Use orbit.frame() or orbit.shadow() to scope actions to embedded content.
Custom Chrome path
# Windows
set ORBITTEST_CHROME_PATH=C:\Path\To\chrome.exe
orbittest run
# macOS / Linux
ORBITTEST_CHROME_PATH=/path/to/chrome orbittest runContributing
Contributions are welcome. Please review the guidelines below before opening a pull request.
Prerequisites
- Node.js 18 or later
- All changes must pass the existing test suite
Workflow
- Fork the repository and create a branch from
master. - Branch naming:
feat/<short-description>,fix/<short-description>, orchore/<short-description>. - Make your changes. Do not introduce new dependencies without prior discussion.
- Run the test suite and confirm all tests pass:
node --test tests/ - Open a pull request against
master. Include a clear description of what changed and why.
Code standards
- Plain JavaScript (CommonJS modules), Node.js 18 compatible.
- No transpilation, no build step.
- New modules must have a single, clearly named responsibility.
- Prefer explicit, readable code over clever abstractions.
- Tests for new behavior are expected unless the change is purely cosmetic.
Security
To report a security vulnerability, open a private security advisory on GitHub or contact the maintainer directly. Do not file public issues for security reports.
License
Apache License 2.0
Copyright 2026 Abhay. See LICENSE, NOTICE, and TRADEMARKS.md.
