Skip to content

feat(SDK-7063): report bail-skipped tests as skipped - #122

Draft
harshit-browserstack wants to merge 6 commits into
mainfrom
feat/sdk-7063-report-skipped-tests
Draft

feat(SDK-7063): report bail-skipped tests as skipped#122
harshit-browserstack wants to merge 6 commits into
mainfrom
feat/sdk-7063-report-skipped-tests

Conversation

@harshit-browserstack

@harshit-browserstack harshit-browserstack commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What is this about?

With mochaOpts: { bail: true }, Mocha halts a spec on its first failure and the remaining tests in that file never run. Today those tests are not reported in any state — they simply disappear from the Test Run, so the report silently under-counts. This PR reports them as skipped.

Tests in sibling describe blocks in the same file are covered too, since Mocha's bail aborts the whole spec rather than just the failing block.

How it works

afterTest already fires for the failing test. From there we reach the live Mocha suite via test.ctx.test.parent, walk up to the spec's root suite, and hand it to the existing reportSuiteSkipped cascade — the same code path already used when a before hook fails and Mocha silently drops the rest of the suite. That is the identical shape of problem, so this is a second trigger on a proven path rather than new machinery.

Mocha builds its full suite tree at file-load time, before running anything, so tests that never execute are already known and carry state === undefined.

Deliberately gated on mochaOpts.bail only

WebdriverIO's top-level bail is a different setting: it counts failed spec files and stops scheduling further ones, and is never forwarded to Mocha. Under it, every test in the running spec still executes — so triggering on it would report tests that are about to run. Reporting is also skipped while a retry is still queued, for the same reason.

Not covered here

Tests in spec files that never started (WebdriverIO's bail) are not reported. That is a separate, launcher-side change and is kept out of this PR to keep the blast radius small; the work exists on feat/sdk-7063-unlaunched-specs and can land on its own review.

Also scoped to the CLI/gRPC reporting path — the legacy direct-HTTP path and multi-remote runs are unchanged.

Related Jira task/s

Release (mandatory for every PR — required for the ready-for-review label)

Version bump:

  • minor (backwards-compatible feature)
  • patch (bug fix or other small change)

Release notes type:

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing):

  • Tests that never run because Mocha's bail halted a spec are now reported as skipped, instead of being left out of the report entirely — so a Test Run accounts for every test in that spec.

Release notes (internal):

  • service.afterTest: when mochaOpts.bail is set and a test fails, resolve the spec's root suite from test.ctx.test.parent and run the existing reportSuiteSkipped cascade over it. Root-walk (rather than starting at the failing test's own suite) is what covers sibling describe blocks.
  • Detection reads mochaOpts.bail with Mocha's own truthiness rather than isTrue(), which is a strict 'true' string compare and would miss a numeric bail: 1.
  • Not gated on wdio-level config.bail: in-spec tests still run under it, so reporting them would double-count. Also skipped while results.retries.attempts < limit.
  • Wrapped in an error boundary — a failure here logs at debug and never affects the user's run.

Checklist

  • Ready to review
  • Has it been tested locally?

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.


Testing evidence

Verified against real BrowserStack builds with Observability assertions, not just console output. Each scenario was run twice — once on the published SDK, once with the fix.

Scenario Published SDK With fix
5 tests in one spec, 2nd fails 1 passed, 1 failed, 3 missing 1 passed, 1 failed, 3 skipped
Failure in one describe, tests in a sibling describe 1 passed, 1 failed, 3 missing 1 passed, 1 failed, 3 skipped
All tests pass (control) no skipped entries no skipped entries

The sibling-describe run also confirmed the reported scopes stay per-suite (['Suite B']) rather than being flattened, and that skipped tests carry the same Automate session id as the tests that ran — so they attribute to the session instead of orphaning.

Identity fields match what an executed test emits (name/identifier = bare title, scope = parent - title), so a test reported skipped and the same test on a later successful run are recognised as one test.

4 unit tests added; service.test.ts failure count unchanged from main (those are pre-existing and environmental — vi.mock('fetch') is a no-op, so they make real network calls with dummy credentials).

harshit-browserstack and others added 6 commits August 5, 2026 22:28
Tests that never run because of `bail` were absent from the Test Run
entirely. Report them as `skipped` so the report accounts for every
declared test.

WebdriverIO has two unrelated bail mechanisms, handled separately:

- `mochaOpts.bail` halts the failing spec. `service.afterTest` walks
  `test.ctx.test.parent` to the spec's root suite and reuses the
  existing `reportSuiteSkipped` cascade, so sibling describes are
  covered. Gated strictly on mocha's own option -- under wdio's `bail`
  every in-spec test still runs, so triggering on it would report tests
  that are about to execute.

- wdio's `bail: <n>` counts failed spec files and stops scheduling more.
  `launcher.onComplete` diffs dispatched specs against the run's spec
  list and enumerates the rest via mocha's declaration pass, without
  executing any test body.

Launcher-side reporting only happens when bail demonstrably fired
(`failedRunners >= bail`, counted the way wdio counts it -- a failed
spec with retries left is requeued, not counted). It is skipped
entirely under sharding, when capabilities define their own
specs/exclude, and for excluded specs. A spec can fail to run for many
reasons unrelated to bail, and reporting one of those as skipped would
corrupt the report; under-reporting is recoverable, phantom skips are
not.

Events are relayed through the binary's EnqueueTestEvent RPC. The
launcher's TESTHUB_JWT is the binary's account token on the CLI path
and carries no build claim, so posting to the collector directly
returns 401; the binary holds the build-scoped credential. The RPC
already existed in the proto -- only the client wrapper was missing.

Reported identity matches `getUniqueIdentifier` so a test reported
skipped and the same test on a later run share one identity.

Covers the CLI/gRPC path only. Enumerating never-started specs
re-executes their top-level code.
Wire evidence showed a test that runs emits `name` and `identifier` as
the bare title, with only `scope` carrying the parent:

  identifier: 'MA-TC1 passes'
  scope:      'Multi Spec A - MA-TC1 passes'

Skipped tests reported for never-launched specs were setting
`identifier` to the parent-qualified form, so the same test reported
skipped and later run would not have matched on that field.

Verified on build bpt0jvulwfonuoucepdu892xhzborork2hy7tgqb: executed and
skipped tests now emit identical conventions for all three fields.
The customer's config is `bail: 0` with `mochaOpts.bail: true`, so only
Mocha's in-spec bail applies to them. WebdriverIO's spec-file-level bail
is disabled in their setup and the launcher-side reporting never runs.

Drop the never-launched-spec work from this PR so the change that
unblocks them is small and built on the already-proven
`reportSuiteSkipped` path. That work is preserved on
`feat/sdk-7063-unlaunched-specs` and can land separately on its own
review.

Removes: specEnumerator, unlaunchedSpecReporter, the launcher hooks,
the EnqueueTestEvent client wrapper, and the mocha devDependency they
needed. README and changeset narrowed to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant