Skip to content

fix(cli): send the per-test accessibility verdict on test events - #359

Open
kamal-kaur04 wants to merge 4 commits into
mainfrom
fix/cli-a11y-test-scope
Open

kamal-kaur04 wants to merge 4 commits into
mainfrom
fix/cli-a11y-test-scope

Conversation

@kamal-kaur04

@kamal-kaur04 kamal-kaur04 commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What is this about?

On the CLI flow, every test in a build that uses accessibilityOptions.includeTagsInTestingScope / excludeTagsInTestingScope is reported as an accessibility test, including tests outside the tag scope. The binary sets each test's product_map.accessibility from the platform check alone (accessibility on + supported browser), so every test gets an accessibility test-execution record. The classic flow sends the per-test verdict (Listener.setTestRunAccessibilityVar), so only in-scope tests are recorded there.

This PR sends that verdict on the CLI flow:

  • AccessibilityModule.setTestAccessibility sets test_accessibility on the test instance: accessibility on and the test in the tag scope (same shouldScanTestForAccessibility call the scan gate makes, so cucumber uses the gherkin tags and mocha the title match). Auto-scanning is deliberately left out, so tests scanned manually with performScan() keep their accessibility row; it still gates the automatic scans.
  • Cucumber scenarios skipped because a BeforeAll failed are built straight into TEST/POST and never pass TEST/PRE, so they are created with test_accessibility: false (they never ran or scanned).
  • It is registered on TEST/PRE with a new prepend option (EventDispatcher.registerObserver / TestFramework.registerObserver), so it runs before TestHubModule serializes TestRunStarted. Without that, TestRunStarted would still carry the platform-only value.
  • The binary uses test_accessibility when it is a boolean and keeps the platform check otherwise (paired binary PR). Older binaries ignore the extra key.

Effect: in a tag-scoped build, out-of-scope tests no longer get accessibility test-execution records, matching the classic flow. Scanning behaviour is unchanged.

Backports: v9 and v8 (linked below).

Related Jira task/s

N/A (tracked internally)

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

Version bump: (required — tick exactly one)

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

Release notes type: (optional)

  • New Feature
  • Bug Fix
  • Other Improvement

Release notes (customer-facing): (optional but encouraged)

  • Fixed tests outside includeTagsInTestingScope / excludeTagsInTestingScope being reported as accessibility tests when using the CLI.

Release notes (internal): (required — engineer-facing; what actually changed / why)

  • CLI flow: the accessibility module now sends a per-test test_accessibility verdict (a11y on and test in tag scope; auto-scanning excluded so manual-scan tests keep their row) on test events, set by a TEST/PRE observer prepended ahead of TestHubModule so TestRunStarted carries it; cucumber cascade-skipped scenarios send false. Restores tag scoping for product_map.accessibility; requires the paired binary change to take effect.

Checklist

  • Ready to review
  • Has it been tested locally?

Unit tests: accessibilityModule.test.ts covers the verdict (in scope / out of scope / auto-scanning off keeps an in-scope test / platform unsupported / mocha title match) and cascade-skipped scenarios carrying false, plus the prepended registration. eventDispatcher.test.ts covers prepend ordering (main and v9 only). tsc --noEmit is clean.

End to end (WDIO 9 + cucumber, Chrome latest / Windows 11, includeTagsInTestingScope: ['@accessibility'], CLI flow; 1 tagged scenario + 2 untagged), product_map.accessibility on TestRunStarted / TestRunFinished:

Run Service Binary Tagged Untagged 1 Untagged 2
Before 9.44.0 (released) released true / true true / true true / true
After this branch (v9) paired fix (local) true / true false / false false / false
Compat this branch (v9) released true / true true / true true / true
  • After: the tagged scenario is still scanned and its results saved, with the same issue count on the accessibility dashboard as Before.
  • Compat: an older binary ignores the new key, so behaviour is unchanged and the run is green.

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.

kamal-kaur04 and others added 2 commits November 12, 2026 14:56
On the CLI flow the binary set product_map.accessibility from the platform
check alone, so every test in a tag-scoped build was reported as an
accessibility test. The accessibility module now sets test_accessibility
(accessibility on, auto-scanning on, test in the include/exclude tag scope),
matching the classic flow's setTestRunAccessibilityVar. Its TEST/PRE observer
is prepended so the value is on the instance before TestRunStarted is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto (fallback from Default branch)
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Central YAML (base), Organization UI (inherited), Workspace UI (inherited); Enforced overrides: Organization
  • Review profile: ASSERTIVE
  • Plan: Enterprise
  • Run ID: 5627e45c-7414-4471-910d-920a21f9a18d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

RUN_TESTS

@minionhelperappqa

Copy link
Copy Markdown

[SDK Wdio Test] TRA build state: failed | Stability 48% — verdict: failure. Passed: 28, Failed: 30, Aggregate: 58. TRA: https://observability.browserstack.com/builds/rm4nr97feknfkgrnjdrggkk6mwbxwsjy3oobhtvu

@kamal-kaur04 kamal-kaur04 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review (automated) — 4 inline finding(s). Full report in the PR comment below. Verdict: Passed.

Comment on lines +364 to +365
const inScope = Boolean(this.accessibility && this.autoScanning &&
shouldScanTestForAccessibility(suiteTitle, test.title || '', this.config.accessibilityOptions as Record<string, string> | undefined, world, Boolean(world)))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Low] Verdict expression duplicated with the onBeforeTest scan gate

This expression copies the scan gate in onBeforeTest. One drives the reported product_map.accessibility and the other drives actual scanning. If one is edited without the other, the reported a11y-test flag and the scanning behaviour will drift apart.

Suggestion: Extract a single private helper and call it from both, or have onBeforeTest read KEY_TEST_ACCESSIBILITY back from the instance (the prepended observer has already set it).

Reviewer: stack:code-review

shouldScanTestForAccessibility(suiteTitle, test.title || '', this.config.accessibilityOptions as Record<string, string> | undefined, world, Boolean(world)))
TestFramework.setState(testInstance, TestFrameworkConstants.KEY_TEST_ACCESSIBILITY, inScope)
} catch (error) {
this.logger.debug(`setTestAccessibility: ${error}`)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Low] Failure logged at debug and silently falls back to the platform check

If this throws, no key is written, so the binary falls back to its platform check and marks the test as an accessibility test (the behaviour this PR fixes). Nothing in the logs explains why.

Suggestion: Log at error, as onBeforeTest and onHookStart do. Optionally also write false here so a failure stays in scope.

Reviewer: stack:code-review

Comment on lines +512 to +519
it('should be prepended to TEST/PRE so TestRunStarted carries the verdict', () => {
expect(TestFramework.registerObserver).toHaveBeenCalledWith(
TestFrameworkState.TEST,
HookState.PRE,
expect.any(Function),
true
)
})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Low] Ordering is asserted only by the prepend flag

This test checks that prepend=true was passed. It does not check that test_accessibility is on the instance when an earlier-registered TEST/PRE observer (TestHub) reads it. The no-instance and exception paths are also untested.

Suggestion: Add a test against the real eventDispatcher: register a stub observer first that captures getAllData(), then register setTestAccessibility with prepend, notify, and assert the captured map contains test_accessibility. Also add tests for the missing-instance and throwing cases.

Reviewer: stack:code-review

const suiteTitle = (typeof args.suiteTitle === 'string' ? args.suiteTitle : '') || ''
const test = (args.test && typeof args.test === 'object' ? args.test as { title?: string } : {}) || {}
const world = args.world as { [key: string]: unknown } | undefined
const inScope = Boolean(this.accessibility && this.autoScanning &&

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] Auto-scanning is part of the verdict (behaviour change on CLI flow)

With autoScanning: false, every test now sends product_map.accessibility: false, App Accessibility sessions included. Before this change the binary marked them true by platform. This matches the classic handler (setTestRunAccessibilityVar(this._accessibility && shouldScanTest)), but it differs from the behave precedent (999131df5 has no auto-scanning term), and manual performScan() results may no longer attach to an accessibility test row.

Suggestion: Confirm with the A11y backend owners that manual-scan results still attach when product_map.accessibility is false. If they don't, drop autoScanning from the verdict and keep it only on the scan gate. Add an App A11y case with autoScanning: false plus a manual performScan.

Reviewer: stack:workspace-pr-review (cross-repo)

@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Claude Code PR Review

PR: #359 • Head: d83e8b1 • Reviewers: stack:code-review, stack:workspace-pr-review (cross-repo)

Summary

CLI flow: AccessibilityModule now sets a per-test test_accessibility verdict (accessibility on, auto-scanning on, and the test in the include/exclude tag scope) on the test instance. It is registered as a prepended TEST/PRE observer so TestHubModule serializes it into TestRunStarted/TestRunFinished. The paired binary PR browserstack/browserstack-binary#2100 uses this value for product_map.accessibility, which restores parity with the classic flow.

Review Table

Priority Category Check Status Notes
High Security No hardcoded secrets or credentials Pass None introduced
High Security Authentication/authorization checks present N/A No auth surface touched
High Security Input validation and sanitization Pass args fields are type-guarded before use
High Security No IDOR — resource ownership validated N/A —
High Security No SQL injection (parameterized queries) N/A —
High Correctness Logic is correct, handles edge cases Pass See Correctness notes below the table
High Correctness Error handling is explicit, no swallowed exceptions Pass Caught and logged, as graceful degradation requires. Debug level noted (Low)
High Correctness No race conditions or concurrency issues Pass Synchronous observer; notifyObserver awaits observers in array order
Medium Testing New code has corresponding tests Pass Verdict cases plus prepend ordering in eventDispatcher.test.ts
Medium Testing Error paths and edge cases tested Fail No-instance early return and exception path untested; ordering only asserted by the flag (Low)
Medium Testing Existing tests still pass (no regressions) N/A Not run by the reviewer. PR reports unit tests and tsc --noEmit clean, plus an end-to-end before/after/compat table
Medium Performance No N+1 queries or unbounded data fetching N/A —
Medium Performance Long-running tasks use background jobs N/A —
Medium Quality Follows existing codebase patterns Pass Reuses shouldScanTestForAccessibility with the same argument handling as onBeforeTest; thin/thick boundary respected
Medium Quality Changes are focused (single concern) Pass —
Low Quality Meaningful names, no dead code Pass Verdict expression duplicated (Low)
Low Quality Comments explain why, not what Pass The prepend comment and the JSDoc give the reason
Low Quality No unnecessary dependencies added Pass None added

Correctness notes (Logic row):

  • TestHubModule is constructed before AccessibilityModule (cli/index.ts), so the prepend is required, and it works.
  • sendTestFrameworkEvent sends the instance's whole data map with no whitelist, so test_accessibility reaches TestRunStarted and TestRunFinished.
  • The argument shapes for mocha ({test, suiteTitle}) and cucumber ({world, test, suiteTitle}) match what setTestAccessibility reads, and args.instance is set by trackEvent.
  • The key name and boolean type match the paired binary's mocha and cucumber makeProductMap.
  • No regressions found in these cases:
    • Jasmine never reaches this code, because it is not a CLI-supported framework.
    • The classic flow is unchanged.
    • Builds with no tag scope get true for every test.
    • With auto-scanning off, every test gets false, which matches the classic setTestRunAccessibilityVar.

Findings

  • File: packages/browserstack-service/src/cli/modules/accessibilityModule.ts:364

  • Severity: Low

  • Reviewer: stack:code-review

  • Issue: The verdict this.accessibility && this.autoScanning && shouldScanTestForAccessibility(...) duplicates the scan-gate expression in onBeforeTest. One drives the reported product map and the other drives actual scanning. If one is edited without the other, the reported a11y-test flag and the scanning behaviour will drift apart.

  • Suggestion: Extract a single private helper and call it from both. Alternatively, have onBeforeTest read KEY_TEST_ACCESSIBILITY back from the instance, since the prepended observer has already set it.

  • File: packages/browserstack-service/src/cli/modules/accessibilityModule.ts:368

  • Severity: Low

  • Reviewer: stack:code-review

  • Issue: On failure the catch logs only at debug and writes no key. The binary then quietly falls back to its platform check, which marks the test as an accessibility test (the behaviour this PR fixes), and nothing in the logs explains why.

  • Suggestion: Log at error, as onBeforeTest and onHookStart do. Optionally also write false in the catch so a failure stays in scope.

  • File: packages/browserstack-service/tests/cli/modules/accessibilityModule.test.ts:512

  • Severity: Low

  • Reviewer: stack:code-review

  • Issue: The ordering test only asserts that registerObserver was called with prepend=true. Nothing proves the behaviour the fix depends on: that test_accessibility is present when a previously registered TestHub-style TEST/PRE observer reads the instance. The no-instance early return and the exception path are also untested.

  • Suggestion:

    • Add a test against the real eventDispatcher. Register a stub observer first that captures getAllData(), then register setTestAccessibility with prepend, notify, and assert the captured map contains test_accessibility.
    • Add tests for the missing-instance case and the throwing case.
  • File: wdio-browserstack-service packages/browserstack-service/src/cli/frameworks/wdioCucumberTestFramework.ts:489 (not in this diff; change-set)

  • Severity: Medium

  • Reviewer: stack:workspace-pr-review (cross-repo)

  • Issue: When a BeforeAll hook fails, buildSkippedScenarioInstance builds the abandoned scenarios directly in TEST/POST state, so they never pass through TEST/PRE and setTestAccessibility never runs. Their eventJson has no test_accessibility, so the binary's cucumber TestRunSkipped path falls back to isAccessibilitySupported and still sends product_map.accessibility: true for them. That is the symptom this change-set fixes.

  • Suggestion: Stamp test_accessibility on the cascade instance in buildSkippedScenarioInstance: false, since these scenarios never ran or scanned, or the tag-scope result from scenario.tags. Add a binary test that a test_skipped_cascade event with test_accessibility: false gives product_map.accessibility: false on TestRunSkipped.

  • File: packages/browserstack-service/src/cli/modules/accessibilityModule.ts:364

  • Severity: Medium

  • Reviewer: stack:workspace-pr-review (cross-repo)

  • Issue: With autoScanning: false, every test now sends product_map.accessibility: false, App Accessibility sessions included. Before this change the binary marked them true by platform. This matches the classic handler (setTestRunAccessibilityVar(this._accessibility && shouldScanTest)), but it differs from the behave precedent (999131df5 has no auto-scanning term), and manual performScan() results may no longer attach to an accessibility test row.

  • Suggestion: Confirm with the A11y backend owners that manual-scan results still attach when product_map.accessibility is false. If they don't, drop autoScanning from the verdict and keep it only on the scan gate. Add an App A11y case with autoScanning: false plus a manual performScan.

  • File: change-set

  • Severity: Low

  • Reviewer: stack:workspace-pr-review (cross-repo)

  • Issue: Mixed versions are safe both ways: an old service sends no key and the binary's boolean guard falls back to the platform check, and an old binary ignores the extra key. But the service changeset says the fix is shipped, and that is only true once binary #2100 is released.

  • Suggestion: Release the binary before or together with the service PRs, or word the release note to say the fix needs the updated binary.

  • File: packages/browserstack-service/src/cli/modules/accessibilityModule.ts:364

  • Severity: Low

  • Reviewer: stack:workspace-pr-review (cross-repo)

  • Issue: setTestAccessibility repeats the in-scope expression that onBeforeTest uses for the scan gate. If one changes without the other, the reported flag and actual scanning disagree. (Same as the per-repo finding above.)

  • Suggestion: Compute it once: a shared helper, or have onBeforeTest read KEY_TEST_ACCESSIBILITY from the instance.

  • File: packages/browserstack-service/src/cli/modules/accessibilityModule.ts:364

  • Severity: Low

  • Reviewer: stack:workspace-pr-review (cross-repo)

  • Issue: The service verdict now overrides the binary's platform check, and they disagree on mobile web: validateCapsWithA11y accepts supported mobile device/browser pairs, while isAccessibilitySupported accepts only desktop Chrome/Edge. In-scope mobile-web tests (e.g. iOS Safari) now send true. That is probably the correct value, but it isn't announced.

  • Suggestion: Confirm it's intended and mention it in the PR description / release note; optionally add a mobile-web test.


Verdict: PASS

kamal-kaur04 and others added 2 commits November 12, 2026 16:20
Scenarios abandoned by a failed BeforeAll are built straight into TEST/POST,
so the TEST/PRE verdict never ran for them and the binary fell back to its
platform check. They never ran or scanned, so they now carry
test_accessibility: false. The in-scope rule is computed by one helper for
both the reported verdict and the scan gate, and a failure to set the verdict
is logged at error level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With autoScanning off, tests scanned manually with performScan() still need
their accessibility test row, so test_accessibility is now accessibility on and
test in the include/exclude tag scope. Auto-scanning still gates the automatic
scans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kamal-kaur04

kamal-kaur04 commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

✅ Good to go

File Status
cli/test/modules/observability/languages/webdriverio/test-frameworks/cucumber/index.test.js (Binary #2100) ✅ All Clear
cli/test/modules/observability/languages/webdriverio/test-frameworks/mocha/index.test.js (Binary #2100) ✅ All Clear
packages/@browserstack/observability/languages/webdriverio/test-frameworks/cucumber/index.js (Binary #2100) ✅ All Clear
packages/@browserstack/observability/languages/webdriverio/test-frameworks/mocha/index.js (Binary #2100) ✅ All Clear
.changeset/pr-359.md (WDIO service #359) ✅ All Clear
packages/browserstack-service/src/cli/eventDispatcher.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/src/cli/frameworks/constants/testFrameworkConstants.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/src/cli/frameworks/testFramework.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/src/cli/frameworks/wdioCucumberTestFramework.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/src/cli/modules/accessibilityModule.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/tests/cli/eventDispatcher.test.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/tests/cli/modules/accessibilityModule.test.ts (WDIO service #359) ✅ All Clear
packages/browserstack-service/tests/service.test.ts (WDIO service #359) ✅ All Clear

Change map (generated deterministically from the diff)

graph LR
  subgraph nbinary["binary"]
    ncli_test_modules_observability_languages_webdriverio_test_frameworks_cucumber_index_test_js["index.test.js<br/>~30 lines"]
    ncli_test_modules_observability_languages_webdriverio_test_frameworks_mocha_index_test_js["index.test.js<br/>~13 lines"]
    npackages__browserstack_observability_languages_webdriverio_test_frameworks_cucumber_index_js["index.js<br/>~9 lines"]
    npackages__browserstack_observability_languages_webdriverio_test_frameworks_mocha_index_js["index.js<br/>~9 lines"]
  end
  subgraph nwdio_service["wdio-service"]
    nwdio_browserstack_service_packages_browserstack_service_src_cli_modules_accessibilityModule_ts["accessibilityModule.ts<br/>~49 lines"]
    nwdio_browserstack_service_packages_browserstack_service_tests_cli_modules_accessibilityModule_test_ts["accessibilityModule.test.ts<br/>~49 lines"]
    nwdio_browserstack_service_packages_browserstack_service_src_cli_eventDispatcher_ts["⚠ eventDispatcher.ts<br/>~10 lines"]
    nwdio_browserstack_service_packages_browserstack_service_tests_cli_eventDispatcher_test_ts["⚠ eventDispatcher.test.ts<br/>~10 lines"]
    nwdio_browserstack_service__changeset_pr_359_md["pr-359.md<br/>~5 lines"]
    nwdio_browserstack_service_packages_browserstack_service_src_cli_frameworks_testFramework_ts["testFramework.ts<br/>~5 lines"]
    nwdio_browserstack_service_packages_browserstack_service_src_cli_frameworks_wdioCucumberTestFramework_ts["wdioCucumberTestFramework.ts<br/>~2 lines"]
    nwdio_browserstack_service_packages_browserstack_service_src_cli_frameworks_constants_testFrameworkConstants_ts["testFrameworkConstants.ts<br/>~1 lines"]
    nwdio_browserstack_service_packages_browserstack_service_tests_service_test_ts["service.test.ts<br/>~1 lines"]
  end
  nsvc_Observability_TRA(["Observability/TRA"])
  nsvc_collector_observability_browserstack_com(["collector-observability.browserstack.com"])
  nsvc_upload_observability_browserstack_com(["upload-observability.browserstack.com"])
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_cucumber_index_js --> nsvc_Observability_TRA
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_cucumber_index_js --> nsvc_collector_observability_browserstack_com
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_cucumber_index_js --> nsvc_upload_observability_browserstack_com
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_mocha_index_js --> nsvc_Observability_TRA
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_mocha_index_js --> nsvc_collector_observability_browserstack_com
  npackages__browserstack_observability_languages_webdriverio_test_frameworks_mocha_index_js --> nsvc_upload_observability_browserstack_com
Loading

↻ This verdict comment is the review anchor — it's updated in place on each run (the gate posts its status separately).

— SDK PR Review Agent

@github-actions

Copy link
Copy Markdown
Contributor

🟢 SDK PR Review gate is green — the SDK PR Review Agent has run on the current head commit (verdict: success).

This gate confirms a review ran on the latest commit. The verdict itself is advisory — read the findings and use your judgement; it does not block merge. A native GitHub reviewer approval is still separately required by branch protection before this PR can merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant