Skip to content

Complete Documentation

This documentation is the primary user guide for OrbitTest, the OrbitTest mobile testing layer, and Orbittest Studio. It combines the project README, usage guide, v3.4.0 release notes, and Studio README into a single organized handbook for QA engineers, developers, and teams adopting OrbitTest in local and CI workflows.

  1. Product Overview
  2. Feature Status
  3. System Requirements
  4. Installation
  5. Quick Start
  6. Project Structure
  7. Configuration
  8. Writing Browser Tests
  9. Lifecycle Hooks
  10. Locators and Element Targeting
  11. Working With Elements
  12. Browser Storage and Sessions
  13. Alerts, Notifications, Frames, Shadow DOM, and Windows
  14. Visual Automation
  15. Reports, Traces, and Diagnostics
  16. CI/CD Integration
  17. OrbitTest UI
  18. Forge Test Recorder
  19. Mobile Testing
  20. Mobile Test API
  21. Orbittest Studio
  22. Studio Installation
  23. Studio Quick Start
  24. Studio Run Configurations and Artifacts
  25. Release Notes: OrbitTest v3.4.0
  26. Installer Integrity
  27. Troubleshooting
  28. Best Practices
  29. Contributing
  30. License and Attribution

OrbitTest is an intent-first end-to-end testing framework for browser and Android workflows. Its core idea is simple: tests should describe what users see and do, not the internal markup developers happen to write.

Instead of starting every browser test with CSS selectors or XPath, OrbitTest lets you interact with visible labels, roles, text, accessible names, and stable attributes. That makes test files easier to review, easier to debug, and less likely to fail after harmless implementation refactors.

OrbitTest includes:

  • Browser automation through Chrome DevTools Protocol.
  • A readable JavaScript testing API.
  • Intent-based locators for visible text, labels, roles, CSS, XPath, and attributes.
  • Test lifecycle hooks.
  • Parallel execution, retries, timeouts, and sharding.
  • HTML, JSON, summary JSON, and JUnit reports.
  • Screenshot and trace artifacts.
  • Smart Report diagnostics for console errors, page errors, failed requests, slow requests, and recent navigation.
  • CI mode for GitHub Actions, Jenkins, GitLab CI, Azure DevOps, and similar systems.
  • A local OrbitTest UI dashboard.
  • Forge, a browser test recorder.
  • Android mobile automation through ADB and UIAutomator.
  • Orbittest Studio, a Windows desktop IDE for Android QA.

Orbittest Studio is a companion desktop application for Android QA engineers and mobile developers. It brings live device mirroring, UI hierarchy inspection, logcat filtering, test editing, execution, and evidence reports into one Windows application.

AreaStatusNotes
UI automationStableIntent-first browser actions and assertions.
Lifecycle hooksStablebeforeAll, beforeEach, afterEach, and afterAll.
Locator engineStableText, role, CSS, XPath, attribute, and locator objects.
Text readersStabletext, visibleText, and domText.
Browser storage and sessionsStableCookies, localStorage, sessionStorage, saved sessions, health checks.
Alerts, notifications, tabs, and windowsStableDialog and multi-window handling.
Frames and Shadow DOMStableScoped automation for iframe and web component content.
ReportsStableHTML, JSON, summary JSON, JUnit, screenshots, and traces.
CI/CD modeStableRetries, sharding, fail-fast, max failures, annotations, artifacts.
Step debuggingStableLive inspector-style debugging with visible browser.
Smart ReportStableBrowser evidence collection and failure diagnosis.
OrbitTest UIStableLocal dashboard for running and inspecting tests.
Forge recorderStableRecords browser flows and generates OrbitTest scripts.
Android mobile providerPhase 1ADB and UIAutomator-based mobile automation.
Visual automationExperimentalCanvas, WebGL, pixel checks, visual snapshots, coordinate actions.
API testingPlannedFuture capability.

OrbitTest is currently in an architecture cleanup phase. Large new feature work should be kept limited while existing functionality is organized into clearer modules and stronger documentation.

RequirementDetails
Node.jsVersion 18 or later.
npmRequired for installation and project scripts.
BrowserA Chromium-based browser. OrbitTest can use Puppeteer’s managed Chrome, system Chrome, or ORBITTEST_CHROME_PATH.
Operating systemWindows, macOS, or Linux for browser testing.
RequirementDetails
Android SDK platform-toolsRequired for adb.
Android deviceAndroid 8.0/API 26 or later recommended.
USB debuggingMust be enabled and authorized on the device.
USB cableMust support data transfer. Charge-only cables will not work.
Optional ffmpegEnables WebM preview generation in some report workflows.
RequirementDetails
Operating systemWindows 10 or later, 64-bit.
Android deviceAndroid 8.0/API 26 or later recommended.
ADBBundled with Android Studio or standalone platform-tools.
USBData cable with USB debugging enabled.
Companion appInstalled from Studio onto the connected Android device.

Project-local installation is recommended for real test suites because it keeps the OrbitTest version locked with the repository.

Terminal window
npm install --save-dev orbittest

Run through npx:

Terminal window
npx orbittest --version
npx orbittest run

Global installation is useful for quick experiments or local CLI access:

Terminal window
npm install -g orbittest
orbittest --version

For team projects, keep a project dependency even if some developers also install the CLI globally.

OrbitTest uses Chrome at runtime. Depending on installation and environment, Puppeteer may download a compatible browser. To skip browser download:

Terminal window
PUPPETEER_SKIP_DOWNLOAD=1 npm install -g orbittest

PowerShell:

Terminal window
$env:PUPPETEER_SKIP_DOWNLOAD = "1"
npm install -g orbittest

If browser download is skipped, install Chrome locally or set ORBITTEST_CHROME_PATH.

PowerShell:

Terminal window
$env:ORBITTEST_CHROME_PATH = "C:\Path\To\chrome.exe"
orbittest run

macOS/Linux:

Terminal window
ORBITTEST_CHROME_PATH=/path/to/chrome orbittest run

OrbitTest looks for Chrome in this order:

  1. Puppeteer’s managed Chrome.
  2. ORBITTEST_CHROME_PATH.
  3. A local system Chrome or Chromium installation.

Run:

Terminal window
orbittest init

This creates a starter structure:

orbittest.config.js
tests/
example.test.js
reports/

If possible, OrbitTest also adds a package script:

{
"scripts": {
"test:e2e": "orbittest run"
}
}

Create tests/home.test.js:

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);
});

Run it:

Terminal window
orbittest run tests/home.test.js

Expected console output is intentionally quiet:

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

The dashboard opens at a local URL similar to:

http://127.0.0.1:9323/

Use it to run tests, inspect reports, filter files, and review artifacts without manually opening report directories.

A typical OrbitTest project looks like this:

project-root/
orbittest.config.js
package.json
tests/
home.test.js
login.test.js
reports/
latest.html
latest.json
latest-summary.json
latest-junit.xml
runs/
<run-id>/
report.html
report.json
summary.json
junit.xml
artifacts/

Recommended conventions:

  • Put test files under tests/.
  • Use .test.js or .spec.js suffixes.
  • Keep reusable helpers small and named by behavior.
  • Keep reports out of source control unless a specific artifact is intentionally committed for documentation.
  • Store secrets in environment variables, not test files.

OrbitTest reads orbittest.config.js from the project root. All fields are optional.

module.exports = {
testDir: "tests",
testMatch: ["**/*.test.js", "**/*.spec.js"],
reportsDir: "reports",
globalSetup: [],
workers: 1,
maxWorkers: 4,
retries: 0,
testTimeout: 30000,
actionTimeout: 0,
browser: {
display: "auto"
},
use: {
web: {
browser: "chrome",
headless: null
},
mobile: null
},
experimental: {
ui: true,
visualAutomation: true,
apiTesting: false
},
openReportOnFailure: {
enabled: !process.env.CI,
port: 0
},
ci: {
enabled: Boolean(process.env.CI),
retries: 1,
trace: "on-failure",
screenshot: "on-failure",
failFast: false,
maxFailures: 0,
shard: process.env.ORBITTEST_SHARD || null,
summary: true,
junit: true,
githubAnnotations: Boolean(process.env.GITHUB_ACTIONS)
},
smartReport: false,
smartReportSlowRequestMs: 2000,
environments: {
staging: {
reportsDir: "reports/staging"
}
}
};

browser.display supports:

ValueBehavior
"auto"Show browser locally, hide browser in CI.
"show"Always show browser unless a CLI flag overrides it.
"hide"Hide browser unless a CLI flag overrides it.

CLI overrides:

Terminal window
orbittest run --show-browser
orbittest run --hide-browser

--step always uses a visible browser because live debugging requires a browser that can be watched and controlled.

Use environments for configuration overlays:

module.exports = {
reportsDir: "reports/local",
environments: {
staging: {
reportsDir: "reports/staging"
}
}
};

Run with:

Terminal window
orbittest run --env staging

CLI flags override config values.

Each browser test receives an orbit object.

const { test, expect } = require("orbittest");
test("login flow", async (orbit) => {
await orbit.open("https://example.com/login");
await orbit.type("Email", "user@example.com");
await orbit.type("Password", "secret");
await orbit.click("Login");
await orbit.waitForText("Dashboard", { timeout: 10000 });
expect(await orbit.hasText("Dashboard")).toBe(true);
expect(await orbit.exists(orbit.css(".account-menu"))).toBe(true);
});
test("slow checkout flow", { retries: 1, timeout: 60000 }, async (orbit) => {
await orbit.open("https://example.com/checkout");
});

Use retries carefully. Retries can protect CI against temporary infrastructure problems, but they should not hide deterministic product or test defects.

Good test names describe behavior:

test("registered user can open dashboard", async (orbit) => {});
test("guest sees pricing page", async (orbit) => {});

Avoid names that describe implementation details:

test("clicks blue button", async (orbit) => {});
test("checks div class", async (orbit) => {});

OrbitTest supports run-level and test-level hooks:

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 includes:

  • name
  • file
  • index
  • attempt
  • retry
  • retries
  • timeout
  • status
  • startedAt
  • endedAt
  • durationMs
  • error
  • artifacts

Use globalSetup when multiple test files need shared hooks:

module.exports = {
globalSetup: "tests/setup.js"
};

tests/setup.js:

const { beforeAll, afterAll, beforeEach, afterEach } = require("orbittest");
beforeAll(async (runInfo) => {
console.log(`Starting run ${runInfo.runId}`);
});
beforeEach(async (orbit, testInfo) => {
console.log(`Starting ${testInfo.name}`);
});
afterEach(async (orbit, testInfo) => {
if (testInfo.status === "failed") {
await orbit.screenshot(`reports/${testInfo.name}.png`);
}
});
afterAll(async (runInfo) => {
console.log(`Completed ${runInfo.results.length} results`);
});

Keep hooks transparent. If a hook logs in, creates data, or changes application state, make that behavior obvious to future readers.

OrbitTest is intent-first. Start with what users can see.

await orbit.click("Login");
await orbit.type("Email", "user@example.com");
await orbit.waitForText("Dashboard");

Use explicit locators when text is ambiguous or when the application exposes a stable automation handle.

await orbit.click(orbit.css("#login"));
await orbit.type(orbit.css("input[name='email']"), "user@example.com");
await orbit.click(orbit.xpath("//button[text()='Login']"));
await orbit.waitFor(orbit.xpath("//h1[contains(text(), 'Dashboard')]"));
await orbit.click(orbit.getByRole("button", "Login"));
await orbit.click(orbit.getByRole("link", "Docs"));
expect(await orbit.text(orbit.getByRole("heading", "Welcome"))).toContain("Welcome");
await orbit.click(orbit.getByAttribute("data-testid", "submit"));
await orbit.type(orbit.getByAttribute("name", "email"), "user@example.com");
await orbit.click({ css: "#login" });
await orbit.click({ xpath: "//button" });
await orbit.click({ role: "button", name: "Login" });
await orbit.click({ attribute: "data-testid", value: "submit" });

Prefer this order:

  1. Visible text or label when it is stable and unique.
  2. Accessible role and name when it communicates user intent.
  3. Stable attributes such as data-testid.
  4. CSS selectors for structure that is expected to remain stable.
  5. XPath only when other options cannot express the target clearly.

Avoid generated class names, deep DOM paths, and positional selectors unless the UI genuinely has no better target.

await orbit.open("https://example.com/");
await orbit.click("Login");
await orbit.hover("Menu");
await orbit.doubleClick("Open");
await orbit.rightClick("File");

Click actions show a short red dot at the browser coordinate before the click is sent. This helps during --step debugging and can appear in trace screenshots when the page remains on the same screen long enough.

Disable the marker for a specific action:

await orbit.click("Login", { visualize: false });

OrbitTest resolves fields by label, placeholder, name, or accessible text:

await orbit.type("Email", "user@example.com");
expect(await orbit.hasText("Welcome")).toBe(true);
expect(await orbit.exists(orbit.css(".success"))).toBe(true);
const title = await orbit.title();
const url = await orbit.url();
expect(title).toContain("Dashboard");
expect(url).toContain("/dashboard");

Use pageState() when both values are needed:

const page = await orbit.pageState();
expect(page.title).toContain("Dashboard");
expect(page.url).toContain("/dashboard");
await orbit.screenshot("reports/home.png");

Use orbit.all() when a locator matches multiple elements:

const buttons = await orbit.all(orbit.css("button"));
for (const button of buttons) {
console.log(button.text);
}

orbit.elements() is an alias:

const products = await orbit.elements(orbit.css(".product-title"));

Each returned item can be reused with actions and checks:

const products = await orbit.all(orbit.css(".product-title"));
for (const product of products) {
const name = await orbit.text(product);
if (name.includes("iPhone")) {
await orbit.click(product);
break;
}
}

Returned items include snapshot details:

const items = await orbit.all(orbit.css(".product-title"));
const first = items[0];
console.log(first.tag);
console.log(first.text);
console.log(first.visible);
console.log(first.attributes);

If clicking an item removes or reorders elements, fetch the collection again before the next click:

while (await orbit.exists(orbit.css(".delete"), { timeout: 0 })) {
const deleteButtons = await orbit.all(orbit.css(".delete"));
await orbit.click(deleteButtons[0]);
}

Prefer event-based or state-based waits:

await orbit.waitForText("Dashboard");
await orbit.waitForText("Dashboard", { timeout: 10000, interval: 200 });
await orbit.waitFor(orbit.css(".toast"));
await orbit.waitFor(orbit.getByRole("button", "Save"));

Fixed waits are available but should be rare:

await orbit.wait(500);

Use fixed waits only when there is no reliable state to wait for.

OrbitTest starts each test with a clean browser profile. Use orbit.storage when a test needs explicit cookies, localStorage, sessionStorage, or saved login state.

await orbit.open("https://example.com");
await orbit.storage.setCookie({
name: "session",
value: "abc123",
httpOnly: true,
secure: true
});
const cookies = await orbit.storage.cookies();
expect(cookies.some(cookie => cookie.name === "session")).toBe(true);

Common methods:

await orbit.storage.cookies();
await orbit.storage.setCookie("theme", "dark");
await orbit.storage.setCookie({ name: "session", value: "abc123" });
await orbit.storage.deleteCookie("session");
await orbit.storage.clearCookies();
await orbit.storage.setLocal("token", "local-token");
await orbit.storage.setSession("view", "compact");
expect(await orbit.storage.getLocal("token")).toBe("local-token");
expect(await orbit.storage.getSession("view")).toBe("compact");

Useful methods:

await orbit.storage.local();
await orbit.storage.setLocal("token", "abc123");
await orbit.storage.getLocal("token");
await orbit.storage.clearLocal();
await orbit.storage.session();
await orbit.storage.setSession("view", "compact");
await orbit.storage.getSession("view");
await orbit.storage.clearSession();

Save login state after a real login:

await orbit.open("https://example.com/login");
await orbit.type("Email", "user@example.com");
await orbit.type("Password", "secret");
await orbit.click("Login");
await orbit.waitForText("Dashboard");
await orbit.storage.saveSession("auth/session.json");

Load it later:

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);

inspect() does not print cookie or token values by default. It reports counts, key names, auth-like signals, JWT expiry, and recommendations.

const state = await orbit.storage.inspect();
console.log(state.auth.present);
console.log(state.auth.signalCount);
console.log(state.recommendations);

Fail early when a saved login is not healthy:

await orbit.storage.loadSession("auth/admin-session.json");
await orbit.storage.expectHealthySession({
minMinutes: 15,
requireCookie: true
});
await orbit.open("https://example.com/admin");

Alerts, Notifications, Frames, Shadow DOM, and Windows

Section titled “Alerts, Notifications, Frames, Shadow DOM, and Windows”
await orbit.open("data:text/html,<button onclick='alert(\"hello\")'>Alert</button>");
await orbit.click("Alert");
expect(await orbit.alertText()).toBe("hello");
await orbit.acceptAlert();

Prompt:

await orbit.click("Ask name");
await orbit.acceptAlert({ promptText: "Abhay" });

Confirm:

await orbit.click("Delete");
await orbit.dismissAlert();
await orbit.open("https://example.com");
await orbit.grantNotifications();
expect(await orbit.getNotificationPermission()).toBe("granted");
await orbit.denyNotifications("https://example.com");
expect(await orbit.getNotificationPermission()).toBe("denied");
await orbit.resetNotificationPermission();
const main = (await orbit.listWindows()).find(window => window.active);
await orbit.click("Open details");
const popup = await orbit.waitForWindow({ switchTo: true });
expect(await orbit.hasText("Details")).toBe(true);
await orbit.switchToWindow(0);
await orbit.switchToWindow(main.id);
await orbit.closeWindow(popup.id);

switchToWindow() and closeWindow() accept:

  • Index.
  • Target ID.
  • URL text.
  • Title text.
  • Regular expression.
  • Predicate function.

newWindow(url) opens a new tab and switches to it unless { switchTo: false } is passed.

Use orbit.frame() when a form, button, or widget lives inside an iframe:

const billing = await orbit.frame(orbit.getByAttribute("title", "Billing"));
await billing.type("Email", "team@example.test");
await billing.click("Save billing");
expect(await billing.exists("Saved")).toBe(true);

Nested frames:

const checkout = await orbit.frame(orbit.getByAttribute("title", "Checkout"));
const vault = await checkout.frame(orbit.getByAttribute("title", "Vault"));
await vault.click("Approve");

Or pass the frame path:

const vault = await orbit.frame([
orbit.getByAttribute("title", "Checkout"),
orbit.getByAttribute("title", "Vault")
]);

Use orbit.shadow() for web components:

const profile = await orbit.shadow(orbit.css("user-profile"));
await profile.type("Email", "team@example.test");
await profile.click("Save");
expect(await profile.text(orbit.css("#status"))).toBe("Saved");

Nested shadow roots:

const shell = await orbit.shadow(orbit.css("app-shell"));
const panel = await shell.shadow(orbit.css("settings-panel"));
await panel.click("Enable");

Or pass a path:

const panel = await orbit.shadow([
orbit.css("app-shell"),
orbit.css("settings-panel")
]);

Visual automation is useful when the application renders pixels instead of standard DOM elements. Common examples include canvas games, WebGL experiences, maps, charts, remote desktops, and custom UI surfaces.

await orbit.open("https://www.pinthing.com/");
expect(await orbit.exists(orbit.css("canvas"))).toBe(true);
await orbit.evaluate(() => window.stopClock && window.stopClock());
const changed = await orbit.visual.changed(async () => {
await orbit.evaluate(() => window.pinthing.down());
});
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.waitForStable();
await orbit.visual.snapshot("reports/canvas-state.png");
await orbit.visual.expectPixel({ x: 30, y: 30 }, "#ff0000", { tolerance: 5 });
const red = await orbit.visual.findColor("#df1f1f", { tolerance: 70 });
await orbit.visual.clickColor("#df1f1f", { tolerance: 70 });

Visual automation is experimental. Use it for UI surfaces where semantic locators are not available, and keep tests focused on clear visual states.

After each run, OrbitTest writes:

reports/latest.html
reports/latest.json
reports/latest-summary.json
reports/latest-junit.xml
reports/runs/<run-id>/report.html
reports/runs/<run-id>/report.json
reports/runs/<run-id>/summary.json
reports/runs/<run-id>/junit.xml
reports/runs/<run-id>/artifacts/

Use each file this way:

FilePurpose
report.htmlHuman-readable dashboard.
report.jsonFull run data, results, errors, artifacts, traces, and smart evidence.
summary.jsonCompact CI-friendly summary for scripts and dashboards.
junit.xmlCI test report format.
latest.*Shortcut to the newest run.

The HTML report includes:

  • Total tests.
  • Passed tests.
  • Failed tests.
  • Flaky tests.
  • Skipped tests.
  • Duration.
  • Failure diagnostics with likely cause and next actions.
  • Inline failure screenshot.
  • Error details and stack trace.
  • Source code frame for the failing line.
  • Embedded trace timeline when --trace is used.
  • Smart browser evidence when --smart-report is used.
  • Mobile evidence when Android tests are used.
Terminal window
orbittest run tests/login.test.js --trace

Trace captures orbit.* steps, assertion failures, status, duration, URL/title, failed step details, and links to step screenshots.

Raw trace files are stored under:

reports/runs/<run-id>/artifacts/traces/
Terminal window
orbittest run tests/login.test.js --smart-report

Smart Report captures:

  • Console errors.
  • Page JavaScript errors.
  • Failed requests.
  • Slow requests.
  • Recent navigation.
  • Current page URL.

When Smart Report detects a clear application failure such as invalid credentials, unauthorized access, failed API calls, or serious browser errors, OrbitTest can mark the test failed even if the script itself did not assert after the action.

For local failures, OrbitTest can start a report server and open the failed report automatically:

http://127.0.0.1:<port>/

Disable it for one run:

Terminal window
orbittest run tests/login.test.js --no-open-report-on-failure

Use a fixed port:

Terminal window
orbittest run tests/login.test.js --report-port 9323
const fs = require("fs");
const summary = JSON.parse(fs.readFileSync("reports/latest-summary.json", "utf8"));
console.log(`OrbitTest status: ${summary.status}`);
console.log(`Passed: ${summary.summary.passed}`);
console.log(`Failed: ${summary.summary.failed}`);
console.log(`Flaky: ${summary.summary.flaky}`);
console.log(`Report: ${summary.reportPaths.html}`);
if (summary.status !== "passed") {
process.exit(1);
}

Preview cleanup:

Terminal window
orbittest clean-reports --dry-run

Remove old reports:

Terminal window
orbittest clean-reports

Customize retention:

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

Default cleanup keeps:

  • The latest report.
  • The last 10 passed runs.
  • The last 30 failed runs.
  • No runs older than 30 days.

Use --ci in automation:

Terminal window
orbittest run --ci

CI mode:

  • Does not open the local failure report server.
  • Hides the browser by default.
  • Prints a CI summary.
  • Writes summary.json.
  • Writes junit.xml.
  • Captures trace files only when a test fails by default.
  • Marks a retried test as flaky when it fails first and later passes.

Example CI output:

OrbitTest CI Summary
--------------------
Status: PASSED
Total: 5
Passed: 5
Flaky: 0
Failed: 0
Skipped: 0
Duration: 28.42s
Report: reports/runs/<run-id>/report.html
Summary: reports/runs/<run-id>/summary.json
JUnit: reports/runs/<run-id>/junit.xml

Config:

module.exports = {
retries: 0,
ci: {
enabled: Boolean(process.env.CI),
retries: 1
}
};

CLI:

Terminal window
orbittest run --ci --retries 2

If a test passes only after retry, the run can still pass, but the result is marked flaky in the HTML report, report.json, and summary.json.

Terminal window
orbittest run --ci --fail-fast

Stop after three failures:

Terminal window
orbittest run --ci --max-failures 3

Tests that were not scheduled because of the stop condition are reported as skipped.

Terminal window
orbittest run --ci --shard 1/4
orbittest run --ci --shard 2/4
orbittest run --ci --shard 3/4
orbittest run --ci --shard 4/4

--shard current/total splits registered tests by index. Every shard writes its own HTML, JSON, summary, and JUnit files.

PowerShell environment variable:

Terminal window
$env:ORBITTEST_SHARD = "1/2"
orbittest run --ci
Terminal window
orbittest run --ci --github-annotations

Failed tests are printed as GitHub error annotations. Flaky tests are printed as warning annotations.

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/

Start the local dashboard:

Terminal window
orbittest ui

Custom port:

Terminal window
orbittest ui --port 9400

Start without opening the browser:

Terminal window
orbittest ui --no-open

Use another reports folder:

Terminal window
orbittest ui --reports-dir reports/staging

OrbitTest UI supports:

  • Viewing test files and registered test names.
  • Running all tests or one file.
  • Local, Evidence, and CI presets.
  • Previewing the exact command before launch.
  • Turning on Trace or Smart Report.
  • Running with CI mode or hidden browser mode.
  • Live command output.
  • Filtering tests and reports.
  • Browsing previous HTML, JSON, and JUnit reports.
  • Opening failed run artifacts quickly.

Forge opens a recorder panel alongside a fresh browser window. It records interactions and generates an OrbitTest script.

Terminal window
orbittest forge
orbittest forge https://example.com
orbittest forge https://example.com --output tests/login.test.js

Forge captures:

  • Clicks.
  • Double-clicks.
  • Right-clicks.
  • Typing, excluding passwords and token-like values.
  • Select changes.
  • Visible checks.
  • Navigation.
  • Stable locators such as role, text, attribute, and 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", "user@example.com");
await orbit.type("Password", process.env.ORBITTEST_PASSWORD || "");
expect(await orbit.exists(orbit.getByRole("heading", "Dashboard"))).toBe(true);
});

OrbitTest v3.4.0 adds the first publish-ready Android mobile testing layer while keeping the existing browser runner flow intact.

Mobile support uses ADB and UIAutomator directly. It does not require Appium, WebdriverIO, Detox, Maestro, or Playwright mobile.

Terminal window
npm install orbittest @orbittest/mobile
  • Android SDK platform-tools installed.
  • USB debugging enabled.
  • adb devices shows the device as device.
  • OrbitTest Desktop or Studio may inject DEVICE_SERIAL, ADB_PATH, and PROJECT_ROOT.
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
}
}
};
const { test, expect } = require("orbittest/mobile");
test("login smoke test", async (orbit) => {
await orbit.wakeUp();
await orbit.launchApp("com.example.myapp", "com.example.myapp.MainActivity");
await orbit.waitForId("username_field", 10000);
await orbit.tapById("username_field");
await orbit.typeText("testuser@example.com");
await orbit.tapById("password_field");
await orbit.typeText("Test@123");
await orbit.tapByText("Sign In");
await orbit.waitForText("Welcome", 10000);
expect(await orbit.hasText("Welcome")).toBe(true);
});
const { test } = require("orbittest");
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");
});
Terminal window
orbittest devices
orbittest doctor
orbittest run tests/mobile-smoke.test.js

Mobile failure reports can include:

  • Screenshots.
  • UI XML.
  • UI JSON.
  • Logcat output.
  • Device metadata.
  • App metadata.
  • HTML report evidence.
MethodPurpose
launchAppLaunch an installed app.
stopAppStop the target app.
installAppInstall an APK.
uninstallAppRemove an app from the device.
clearAppDataClear app data.
isAppInstalledCheck whether a package is installed.
MethodPurpose
tapTap screen coordinates.
longPressLong-press screen coordinates or target.
swipeSwipe between points.
scrollDownScroll down.
scrollUpScroll up.
MethodPurpose
typeTextType text into focused input.
clearTextClear focused text.
pressKeySend Android key input.
MethodPurpose
tapByIdTap by Android resource ID.
tapByTextTap by visible text.
tapByDescriptionTap by content description.
hasTextCheck whether text exists.
hasIdCheck whether an ID exists.
MethodPurpose
waitForIdWait for resource ID.
waitForTextWait for visible text.
waitForDescriptionWait for content description.
waitForGoneIdWait until ID disappears.
waitForGoneTextWait until text disappears.
MethodPurpose
wakeUpWake device.
sleepScreenTurn screen off.
isScreenOnCheck screen state.
getScreenSizeRead screen size.
getCurrentActivityRead current Android activity.
MethodPurpose
screenshotCapture screenshot.
saveScreenshotSave screenshot to file.
getLogcatRead logcat output.
saveLogcatSave logcat to file.

v3.4.0 adds mobile-aware assertions:

await expect(orbit).toHaveText("Login");
await expect(orbit).toHaveId("username_field");
await expect(orbit).toMatchScreenshot("login-screen");

Orbittest Studio is a Windows desktop IDE for Android QA. It connects to Android devices over USB and combines device mirroring, UI inspection, logcat, test editing, test execution, and evidence reports in a focused workspace.

The source code is private. The public repository is used for distribution and release downloads.

CapabilityDescription
Live Device MirrorStreams the connected Android device screen into Studio.
UI InspectorDumps and browses the UIAutomator hierarchy.
Device LogsCaptures and filters logcat output.
Mobile Test RunnerRuns JavaScript tests through the orbittest/mobile API.
Reports and EvidenceProduces HTML reports with screenshots, logs, and recordings.
Built-in IDEFile explorer, Monaco editor, tabs, terminal, Git, and npm manager.
Companion AppAndroid APK that enables bridge, screen capture, and diagnostics.

Studio streams the Android screen using the OrbiStream engine. It supports:

  • Auto mode.
  • Low Latency mode.
  • High Quality mode.
  • ADB screenshot fallback.
  • Snapshot capture.

Android may show a MediaProjection permission dialog the first time screen capture is used. Approve the dialog on the device.

The Inspector window lets users:

  • Dump the UIAutomator hierarchy.
  • Browse nodes.
  • Highlight selected nodes on the live preview.
  • Copy locators.
  • Generate tap code for test scripts.

The Logs panel supports filtering by:

  • Package name.
  • PID.
  • Tag.
  • Severity level.
  • Text match.
  • Regex.
  • Time range.

Filtered logs can be copied or downloaded as a file.

Studio includes a VS Code-style workspace:

  • File explorer.
  • Monaco editor.
  • Tabs.
  • Dirty-state tracking.
  • Integrated terminal.
  • Git source control.
  • npm package manager.
  • Active test project scope.

Download the latest Windows installer from the official releases page:

https://github.com/abhay-1994/Orbittest_Studio_download/releases/latest

Double-click:

Orbittest-Studio-Setup-x.x.x.exe

Follow the prompts.

Windows SmartScreen may show a warning because the installer is not yet Authenticode-signed. Use More info and then Run anyway only after verifying that the file came from the official release page. Use the SHA256 integrity process described later in this document.

  1. Open Android Settings.
  2. Go to About Phone.
  3. Tap Build Number 7 times to unlock Developer Options.
  4. Open Developer Options.
  5. Enable USB Debugging.
  6. Connect the phone to the PC with a USB data cable.
  7. Approve the “Allow USB debugging?” prompt on the phone.
  1. Open Orbittest Studio.
  2. Wait for the device to appear in the Devices panel.
  3. Select the device.
  4. Click Install on the device card.
  5. Approve installation prompts on the phone.

If Android blocks installation, enable Install via USB in Developer Options and retry.

  1. Open Studio.
  2. Click New Project or open an existing folder.
  3. Create a test file under tests/, for example tests/smoke.test.js.
  4. Select a connected device in the Devices panel.
  5. Open the test file in the editor.
  6. Choose a run configuration.
  7. Click Run.
  8. Watch live output in the Console panel.
  9. Open the Reports panel to inspect results.

Example Studio mobile test:

const { test, expect } = require("orbittest/mobile");
test("app opens to welcome screen", async (orbit) => {
await orbit.wakeUp();
await orbit.launchApp("com.example.myapp", "com.example.myapp.MainActivity");
await orbit.waitForText("Welcome", 10000);
expect(await orbit.hasText("Welcome")).toBe(true);
});
ConfigurationTraceSmart ReportVideo Recording
Quick RunOffOnAlways
Trace RunOnOnAlways
Evidence RunOnOnAlways
Minimal RunOffOffOff

Each run writes artifacts to:

<project>/.orbitest/runs/<timestamp>-<run-id>/

Typical artifacts:

run.json
stdout.log
studio-report.html
final-screenshot.png
device-recording.mp4

Reports are viewable in-app and include:

  • Pass/fail status.
  • Action logs.
  • Final screenshot.
  • Device recording.
  • Frame evidence player.
  • Fullscreen mode.

OrbitTest v3.4.0 adds the first publish-ready mobile testing layer for Android automation while preserving the existing browser runner flow.

  • Added mobile test configuration through use.mobile.
  • Added optional @orbittest/mobile provider loading.
  • Added .env support for ADB and device settings.
  • Added Android automation APIs for install, uninstall, launch, stop, gestures, keyboard input, UIAutomator selectors, screenshots, logcat, device state, and raw ADB access.
  • Added mobile-aware test contexts with page/web for browser automation and orbit/mobile for Android automation.
  • Added mobile assertions: expect(orbit).toHaveText(), toHaveId(), and toMatchScreenshot().
  • Added orbittest devices for connected Android device discovery.
  • Added orbittest doctor for environment diagnostics.
  • Added mobile failure and report artifacts including screenshots, UI XML/JSON, logcat output, device/app metadata, and HTML report evidence.
  • Added the orbittest/mobile export and TypeScript declarations for mobile-first tests.
  • Added the publishable packages/mobile provider package with ADB, UIAutomator parsing, selector matching, artifacts, visual comparison, and doctor utilities.
  • The root orbittest package now includes the mobile provider files needed by orbittest/mobile.
  • Mobile provider runtime dependencies are declared at the root package level so the bundled mobile export resolves correctly after npm install.
  • Packaging tests were added to prevent the mobile export from regressing in future releases.
  • Unit coverage includes mobile parser and provider foundations.
  • Unit coverage includes the root mobile export packaging guard.
  • npm pack --dry-run verifies that these files are included in the root package:
    • mobile.js
    • mobile.d.ts
    • core/providers/mobile.js
    • packages/mobile/**

Verify downloaded installers before running them.

Published SHA256 for v1.0.0:

183CF4BD94647F58CF64F0A482EA4734120DA559E131C9D1C7E5056E2A00ECD6

Verify with PowerShell:

Terminal window
Get-FileHash .\Orbittest-Studio-Setup-1.0.0.exe -Algorithm SHA256

The hash in the output must match exactly. If it does not match:

  1. Delete the installer.
  2. Download again from the official GitHub releases page.
  3. Verify the hash again before running the file.
CheckResult
Prettier format checkPassed
ESLintPassed
TypeScript typecheckPassed
Vitest unit tests7 files, 32 tests passed
npm high-severity security audit0 vulnerabilities
Third-party license auditPassed
Windows NSIS installer buildPassed
Bundled Companion APK verificationPassed

Run:

Terminal window
orbittest init
orbittest run

Confirm that test files match:

tests/**/*.test.js
tests/**/*.spec.js

If needed, update testMatch in orbittest.config.js.

Try:

Terminal window
npm install

Then confirm Chrome availability or set ORBITTEST_CHROME_PATH.

The element may not be visible yet, the text may not match, or the target may be inside a frame/shadow root.

Use a wait:

await orbit.waitFor(orbit.getByRole("button", "Continue"));
await orbit.click(orbit.getByRole("button", "Continue"));

Use a more specific locator:

await orbit.click(orbit.css("[data-testid='submit']"));

Check:

  • Node.js version.
  • Chrome availability.
  • Environment variables.
  • Network access.
  • Browser display/headless differences.
  • Report screenshots.
  • Trace artifacts.
  • Smart Report evidence.

Run:

Terminal window
adb devices

Confirm the device appears as device, not unauthorized.

If ADB is not on PATH, set the ADB binary path in Studio settings. Common path:

C:\Users\<you>\AppData\Local\Android\Sdk\platform-tools\adb.exe

Try a different USB cable or port. Many cables are charge-only.

Unlock the phone and approve the “Allow USB debugging?” prompt. If the prompt does not appear:

  1. Unplug the device.
  2. Replug it.
  3. Confirm USB debugging is still enabled.
  4. Revoke USB debugging authorizations and try again if needed.

Enable:

Developer Options > Install via USB

If a previous version is installed, uninstall it from the phone’s app settings, then retry.

OrbiStream uses Android MediaProjection. Approve the screen-capture permission prompt on the phone.

If the stream remains black:

  1. Tap Ping in the Devices panel.
  2. Confirm the companion bridge is alive.
  3. Reopen the Live panel.
  4. Restart ADB if the device session is stale.

Widen filters:

  • Severity: Info+ or All levels.
  • Time range: All buffered.
  • Clear package, tag, and text filters.

If package PID lookup failed, make sure the app is running and regenerate logs.

Terminal window
adb kill-server
adb start-server
adb devices
adb logcat -c

Use visible names and roles when possible:

await orbit.click("Login");
await orbit.type("Email", "qa@example.com");

Use implementation-specific selectors only when they are stable and intentional.

Prefer:

await orbit.waitForText("Dashboard");
await orbit.waitFor(orbit.css(".ready"));

Avoid:

await orbit.wait(2000);

Assert after meaningful steps. This makes failures easier to understand and improves report quality.

Smart Report helps explain whether a failure came from:

  • A browser-side JavaScript error.
  • A failed network request.
  • A slow backend response.
  • An unauthorized response.
  • A page that navigated unexpectedly.

Reports should help a teammate understand failure without rerunning the test immediately. Use screenshots, traces, logcat, and summaries deliberately.

For Android tests, store app package, activity, ADB path, and device serial in config or environment variables. Do not hardcode machine-specific paths in test files.

Remove obsolete tests, outdated workarounds, and helpers that no longer communicate product behavior.

Contributions should be focused and easy to review.

Recommended workflow:

  1. Fork the repository.
  2. Create a branch from master.
  3. Use branch names such as feat/<short-description>, fix/<short-description>, or chore/<short-description>.
  4. Make focused changes.
  5. Avoid new dependencies unless there is a clear reason.
  6. Run tests.
  7. Open a pull request against master.

Run tests:

Terminal window
node --test tests/

Code standards:

  • Plain JavaScript.
  • CommonJS modules.
  • Node.js 18 compatible.
  • No transpilation required.
  • No build step required for core package changes.
  • Modules should have a single, clearly named responsibility.
  • Prefer readable code over clever abstractions.
  • Add tests for behavior changes unless the change is purely cosmetic.

Security issues should not be reported in public issues. Use a private security advisory or contact the maintainer directly.

OrbitTest is distributed under the Apache License 2.0.

Orbittest Studio is distributed under the MIT License.

Forks and modified versions are permitted under the applicable license, but they must preserve copyright, license, and notice attribution. They must not imply affiliation with the official project.

The OrbitTest name, logo, and branding are governed by the project trademark guidelines.

Copyright 2026 Abhay.