Intent-first testing with OrbitTest for readable QA automation
Intent-first testing keeps automation focused on user-visible behavior instead of fragile implementation details.

There's a familiar frustration in QA: you write a perfectly good test suite, the product ships, a designer tweaks a button's class name from btn-primary to btn-cta, and suddenly half your tests are red. Nothing actually broke. The user experience is identical. But your selectors are dead.

This is the core problem with how most browser automation has been done for the last decade. We've been writing tests that describe the implementation, not the intent.

OrbitTest is built around a different idea: test what users see, not what developers write.

The Selector Tax

Every test written against a CSS selector or XPath carries a hidden cost. When a developer refactors a component, changes an ID for semantic reasons, or migrates from one UI library to another, your tests break even if the behavior is unchanged. As a QA engineer, you end up spending a significant chunk of your time not finding bugs, but maintaining selector strings that have drifted from the UI.

XPath is worse. Expressions like //div[@class='container']//button[2] are brittle by design. They encode positional assumptions and implementation details that will inevitably change. They're also nearly unreadable to anyone who didn't write them.

The result is a test suite that is expensive to maintain, slow to update, and that the development team stops trusting because the noise-to-signal ratio is too high.

Intent-First: A Different Mental Model

Intent-first testing flips the framing. Instead of asking "what is the DOM structure of this page?", you ask "what would a user do here?"

A user doesn't look at the page source. A user reads visible text, finds buttons by their labels, and fills in fields by their placeholder or associated label. That's exactly what OrbitTest's locator engine does:

await orbit.click('Login');
await orbit.type('Email', 'user@example.com');
await orbit.type('Password', 'secret');
expect(await orbit.hasText('Dashboard')).toBe(true);

There is no CSS selector here. There's no XPath. You're expressing what a user would do: click the thing labelled "Login", type into the "Email" field, and let the framework resolve the DOM target using accessible names, visible labels, and roles.

If a developer renames a class or restructures the DOM but keeps the label "Login" on the button, your test continues to work. Because nothing the user would perceive has changed.

Why This Matters for QA Engineers Specifically

Developers often treat test maintenance as a secondary concern. For QA engineers, it's the daily reality. When you own hundreds or thousands of end-to-end tests, the selector tax compounds:

  • Onboarding slows down. A new team member has to understand the DOM structure of the app just to write or read a test.
  • Refactors become risky. Every UI change triggers a round of test fixes before anyone knows if the actual feature is working.
  • Coverage drops. Engineers start skipping or commenting out tests that are too brittle to maintain, creating blind spots.

Intent-first tests sidestep most of this. A test that says orbit.click('Add to Cart') is readable by anyone on the team: product managers, developers, and other QA engineers. It's also far less likely to break from a UI refactor than orbit.click(orbit.css('.product-card:nth-child(3) .btn-primary')).

When You Still Need Explicit Locators

Intent-first doesn't mean "no selectors, ever." OrbitTest is pragmatic about this. When visible text is ambiguous, say there are three "Edit" buttons on a page, you can fall back to explicit locators:

// Role-based
await orbit.click(orbit.getByRole('button', 'Edit'));

// Attribute-based
await orbit.click(orbit.getByAttribute('data-testid', 'edit-profile-btn'));

// CSS or XPath when truly needed
await orbit.click(orbit.css('#modal-submit'));

The intent-first approach is the default, not a constraint. You reach for explicit selectors when the context demands it, not as a starting point for every interaction.

Readable Tests Are Maintainable Tests

There's a compound benefit to intent-first testing that's easy to overlook: when tests read like user stories, they function as living documentation.

test('User can reset their password', async (orbit) => {
  await orbit.open('https://app.example.com/login');
  await orbit.click('Forgot password?');
  await orbit.type('Email address', 'user@example.com');
  await orbit.click('Send reset link');
  expect(await orbit.hasText('Check your inbox')).toBe(true);
});

A product manager can read that test and immediately understand what it covers. A developer can read it when debugging a regression. When the test fails, the failure message points to a user-visible action, like "couldn't find element matching 'Send reset link'", not an opaque CSS class that requires cross-referencing the component tree.

The Reporting Side

Good intent is useless if failures are hard to diagnose. OrbitTest generates HTML, JSON, and JUnit reports out of the box, with inline screenshots and trace timelines. The --smart-report flag goes further: it captures console errors, failed network requests, JS exceptions, and navigation events, so you can see not just what failed but why, without manually attaching debugging sessions.

For CI pipelines, sharding, retries, fail-fast mode, and GitHub Actions annotations are all built in. The framework is designed to slot into a real QA workflow, not just a demo.

Getting Started

If you want to try OrbitTest on an existing project:

npm install orbittest
npx orbittest init
npx orbittest run

The init command scaffolds a config and an example test. The forge command opens a browser recorder that watches your interactions and generates a ready-to-run test script, which is useful for quickly covering a flow you've been testing manually.

The Bigger Point

The tools we use shape the tests we write. If your framework defaults to CSS selectors, you default to thinking about DOM structure. If your framework defaults to visible labels and user intent, you default to thinking about user behavior, which is ultimately what QA is for.

Intent-first testing isn't a silver bullet. It doesn't replace the need for careful test design, good coverage strategy, or strong feedback loops with development. But it removes a significant source of maintenance burden and makes your test suite more readable, more resilient, and more aligned with what you're actually trying to verify: that the product works for real users.

OrbitTest is one framework taking this seriously. It's worth a look.

Try OrbitTest

OrbitTest is open source under the Apache 2.0 license. Read the docs, install the package, or explore the project on GitHub.