npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

orbittest

v3.4.0

Published

Browser automation test runner built on Chrome DevTools Protocol.

Downloads

435

Readme

OrbitTest

npm version License: Apache-2.0 Node.js

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

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 orbittest

Or install globally:

npm install -g orbittest

Verify the installation:

orbittest --version

Requirements: 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 init

This 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 run

Output:

Passed: 1
Failed: 0
Report: reports/runs/<run-id>/report.html

No 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 exists

Page 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 dialogs

waitForAlert() 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/mobile

Android requirements:

  • Android SDK platform tools installed
  • USB debugging enabled on the device
  • adb devices shows the device as device
  • OrbitTest Desktop may inject DEVICE_SERIAL, ADB_PATH, and PROJECT_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, and PROJECT_ROOT to 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 --trace is used)
  • Smart browser evidence (when --smart-report is 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 --trace

The 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-report

Smart 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 --step

Step 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 port

Report retention

Preview what would be removed:

orbittest clean-reports --dry-run

Remove old reports:

orbittest clean-reports
orbittest clean-reports --passed 20 --failed 50 --max-age-days 60

Default 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 4

CI 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/4

Each 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 failures

Flaky 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-annotations

Emits 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/staging

OrbitTest 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.js

By 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 ambiguous

Wait 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);                   // avoid

Scope 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 run

Contributing

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

  1. Fork the repository and create a branch from master.
  2. Branch naming: feat/<short-description>, fix/<short-description>, or chore/<short-description>.
  3. Make your changes. Do not introduce new dependencies without prior discussion.
  4. Run the test suite and confirm all tests pass:
    node --test tests/
  5. 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.