Skip to content

feat(app-a11y): scan the WDIO config-level hook window, with or without a parent uuid - #176

Open
kamal-kaur04 wants to merge 9 commits into
mainfrom
SDK-7422-scan-pre-test-window
Open

feat(app-a11y): scan the WDIO config-level hook window, with or without a parent uuid#176
kamal-kaur04 wants to merge 9 commits into
mainfrom
SDK-7422-scan-pre-test-window

Conversation

@kamal-kaur04

@kamal-kaur04 kamal-kaur04 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

What is this about?

A driver command issued from a WDIO config-level before() or beforeSuite was never scanned for App Accessibility. The scan gate opened at the first test — and WDIO runs config-level hooks before any test or framework hook exists — so screens visited during setup went uncovered.

The gate now opens as soon as the session is known a11y-capable, and the scan is sent whether or not a parent uuid exists: in that window there is no test and no framework hook, so the payload carries neither thTestRunUuid nor thHookRunUuid.

Every other validation still applies

Only the include/exclude tag filter is skipped, and only inside the window — it matches on suite and test titles, and neither exists yet. Everything else the per-test gate checks is unchanged: an a11y-capable session (caps-validated), autoScanning, a supported framework (mocha + cucumber; jasmine untouched, where App Accessibility is not supported), a real session id, and non-multiremote. onBeforeTest / beforeScenario re-computes the per-test gate — tags included — so the window governs nothing beyond itself.

No test run uuid for a window scan

TEST_ANALYTICS_ID in that window holds a uuid the framework minted at instance creation — a test that has not started. Sending it attributed the scan to a test it did not come from.

Verification

Bench: test-samples/app_automate-wdio_mocha-android/, phase boundaries annotated into the session log via browserstack_executor so each scan is attributed by position. Verdicts read from the App Automate session-logs API (work/SDK-7422-app-a11y-fixes/uuid-verdict.py).

flow conf baseline (main) this branch new window scans
mocha (CLI) repro.conf.ts chnsj2hcevtndlsq4q4fghp8b0zvnvo3ajx0zded · 119316d3e3689f4921b9256ed9bb9b5a948704ef — 6 scans, 0 in the window bfhusrbbjfyxlf4gbh0crdudvwsaup7wy5qi2gau · 4fda1f2fe573fd326b09ec560c148588fe62aa33 — 13 scans 7execute, executeScript, click ×3, execute, executeScript, each with no test uuid and no hook uuid
cucumber repro-cucumber.conf.ts zrlxhmjdu5z2diuvqeyqtizwvclttuc4uoefa8jt · c2a250be4c351e5ddc63b57ead8ba752e3a00fe4 — 3 scans edsvapa0evg18zt2ilbbjmd3t4l3s2fhuhhuccih · 86407fe669f17674685c03293866de9711cf6544 — 5 scans 2, likewise parentless
mocha (CLI) repro-inline-hooks.conf.tsframework hooks only 5xslabfrn225yzhdlaivg1z2fqubny7lcdsntrbj · ccd994cd941375698e449cc18398d73def6b66e8 — 6 scans, all carrying their test uuid bqez3voc1pj3kgqefduycmvjis1ksttcajmfojy7 · 4a1e06127c5d9c9d39610ac63d599dec1a8b0897 — 6 scans, all carrying their test uuid 0 — structurally identical to baseline. The control: this conf's before() touches no scannable command, so its only scans come from framework hooks

Per-phase on the mocha hook-command conf, this branch: 7 scans in config-level before() where the baseline had zero, then mocha before all 2, before each #1 1, test 1 body 3 — every one of those four framework-hook and test-body scans carrying test uuid db285069, exactly as the baseline does.

Framework hooks are deliberately untouched

The window closes the moment the framework signals a hook (onHookStart on the CLI path, beforeHook on the classic one), not at the first test. Closing at the first test would sweep in mocha's before all and before each #1 — they run inside that span — and strip their test run uuid, which is a behaviour change to framework hooks that this feature has no business making. The third row above is the control: a conf whose only scans come from framework hooks is structurally identical to baseline.

Unit: the params rule (window ⇒ no test uuid, hook uuid preserved; non-window ⇒ uuid sent), the CLI gate (opens at driver creation, respects autoScanning, closes at the first test), and the classic gate (mocha, cucumber, jasmine unchanged, multiremote skipped, closes at the first test). Full suite 1231 passed, 0 failed; npm run lint clean.

Two existing tests asserted the last argument of the scan helpers, so appending a parameter made them silently follow it. Both now assert by position.

Known limits

  • A command in the hook's first tick can still escape. Command wrapping completes a few ms after session creation, so a driver call in that gap is unscanned. Not closable from inside a service — no driver object exists to wrap earlier.
  • The window's scans have no parent on the dashboard — by design here. Reporting the window as a hook run is feat(app-a11y): scan the WDIO config-level before() window, and report it as a hook #168's scope, not this PR's.
  • Post-test scans carry the finished test's uuid. A scan in afterSuite/after is stamped with the last test that ran. Deliberate: it is what the classic flow has always done for cucumber's after-hooks, and inventing a third behaviour for mocha would be worse than matching it.
  • Web attribution is coarser than app. On web the scan payload carries no identity — attribution happens at test stop, when saveTestResults flushes with thTestRunUuid. Scans from config hooks before the first test are therefore flushed into the first test's results. isGlobalHook cannot reach that path; fixing it needs a uuid-less flush, which depends on an extension contract this SDK cannot see.

Related Jira task/s

https://browserstack.atlassian.net/browse/SDK-7422

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)

  • Accessibility scans now run for driver commands issued from your WDIO config's hooks — before() and beforeSuite before the run starts, and afterSuite/after during teardown — so screens visited outside your tests are covered.
  • browser.performScan() called from a config hook is no longer attributed to a test that has not started.

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

  • The scan gate now opens at driver creation instead of at the first test, so config-level WDIO hooks (which run before any test or framework hook exists) are covered on both the CLI and classic flows. Scoped to mocha + cucumber, non-multiremote; jasmine deliberately unchanged. Applies to web a11y as well as App Accessibility.
  • onAfterTest no longer deletes the session's entry from accessibilityMap. That teardown silenced every scan between tests and after the last one on the CLI flow — afterSuite and after never scanned there, while the classic flow (which never deleted) scanned them. Removing it rather than forcing the entry true keeps a tag-excluded test and a user's stopA11yScanning() in effect.
  • Scans that nothing can parent — no framework hook run and no test seen yet in the session — send no thTestRunUuid, because TEST_ANALYTICS_ID there holds a uuid minted at instance creation for a test that has not started. The rule is latched at the first framework hook or test, so everything after that point behaves exactly as before. It is applied at every scan site on both flows, including the user-facing browser.performScan().
  • Every validation except the include/exclude tag filter still applies; tags cannot be evaluated without a suite/test title.

Checklist

  • Ready to review
  • Has it been tested locally?

PR Validations

Run Tests: Comment RUN_TESTS to trigger sanity tests.

A driver command issued from a config-level before()/beforeSuite was never scanned. The scan gate
opened at the first test, and WDIO runs those hooks before any test or framework hook exists, so
setup screens went uncovered. The gate now opens as soon as the session is known a11y-capable.

Every validation the per-test gate applies still applies here — an a11y-capable session,
autoScanning, a supported framework (mocha, cucumber; jasmine untouched), a real session id, and
non-multiremote. The include/exclude tag filter is the one exception: it matches on suite and test
titles, and in this window neither exists yet. onBeforeTest/beforeScenario re-computes the per-test
gate, tags included, so the window governs nothing beyond itself.

Scans from the window carry no test run uuid. TEST_ANALYTICS_ID there holds a uuid the framework
minted at instance creation — a test that has not started — so sending it attributed the scan to a
test it did not come from.

Two existing tests pinned the exact argument list of the scan helpers, so appending a parameter made
them silently follow it; both now assert by position.

SDK-7422
@kamal-kaur04
kamal-kaur04 requested a review from a team as a code owner August 31, 2026 09:36
@coderabbitai

coderabbitai Bot commented Aug 31, 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

Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)

Review profile: ASSERTIVE

Plan: Team

Run ID: 952b7c49-b4b3-4372-9b11-81bee3ce2f88

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

Comment @coderabbitai help to get the list of available commands.

github-actions Bot and others added 2 commits August 31, 2026 09:36
…he first test

The window targets WDIO's own config hooks. Closing it at the first test swept in mocha's
before all / before each #1, which run inside that span, and stripped their test run uuid —
baseline carried it, the first cut showed none. That is a behaviour change to framework hooks,
which are not this feature's business.

It now closes the moment the framework signals a hook: onHookStart on the CLI path, beforeHook on
the classic one. The CLI clear sits ahead of that method's own early returns — if a framework hook
started at all, the config-level window is over, whether or not that hook goes on to scan. The
existing clears at the first test/scenario stay as a backstop for a spec with no framework hooks.

Four tests cover it, two per flow: a framework hook closes the window, and a framework-hook scan
still carries its test run uuid. Reverting either clear fails exactly those four.

Verified on device — repro-inline-hooks (framework hooks only) is now structurally identical to
baseline, every scan carrying its test uuid, while repro.conf.ts still gains its 7 parentless
config-level scans.

SDK-7422
@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Correction pushed (2a872db): framework hooks must not change

The first cut defined the window as driver creation → first test. Mocha's before all and before each #1 run inside that span, so they got swept in and lost their test run uuid — baseline carried 61047812, the first cut showed none. That is a behaviour change to framework hooks, which this feature has no business making.

The window now closes the moment the framework signals a hook — onHookStart on the CLI path, beforeHook on the classic one. The CLI clear sits ahead of that method's own early returns: if a framework hook started at all, the config-level window is over, whether or not that hook goes on to scan. The clears at the first test/scenario stay as a backstop for a spec with no framework hooks.

Re-verified on device, all three confs

conf baseline (main) corrected branch reading
repro.conf.ts chnsj2hc… — 6 scans, 0 in window bfhusrbb… · 4fda1f2fe573fd326b09ec560c148588fe62aa33 — 13 7 new parentless window scans; before all + before each #1 restored to test uuid db285069 + hook uuid
repro-cucumber.conf.ts zrlxhmjd… — 3 edsvapa0… · 86407fe669f17674685c03293866de9711cf6544 — 5 2 new parentless; steps keep 86bbd66b
repro-inline-hooks.conf.ts 5xslabfr… — 6, all with test uuid bqez3voc… · 4a1e06127c5d9c9d39610ac63d599dec1a8b0897 — 6, all with test uuid structurally identical to baseline — the control, since its only scans come from framework hooks

Tests

Four added, two per flow: a framework hook closes the window, and a framework-hook scan still carries its test run uuid. They are real regression tests — reverting src/ to the previous commit fails exactly those four (4 failed, 92 passed). Full suite 1231 passed, 0 failed; tsc and lint clean.

The PR description is updated: new build ids, and the "before each #1 loses its test uuid" known limit is deleted rather than reworded — it no longer happens.

@kamal-kaur04
kamal-kaur04 requested review from 07souravkunda and removed request for pri-gadhiya August 31, 2026 13:34
Comment thread packages/browserstack-service/src/accessibility-handler.ts
Comment thread packages/browserstack-service/src/accessibility-handler.ts Outdated
Comment thread packages/browserstack-service/src/cli/modules/accessibilityModule.ts Outdated
Comment thread packages/browserstack-service/tests/cli/modules/accessibilityModule.test.ts Outdated
Comment thread packages/browserstack-service/tests/cli/modules/accessibilityModule.test.ts Outdated

@07souravkunda 07souravkunda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The mechanism is minimal and well-reasoned, and the 2a872db correction — closing the window at the first framework signal rather than the first test, with the framework-hooks-only conf as a control — is the right call and properly covered. Blocking on scope rather than on design: neither gate is conditioned on app accessibility, so web (Automate) a11y sessions are silently in scope on a path where isGlobalHook is never forwarded and attribution happens at saveTestResults — window scans land in the first test's results. And the CLI gate carries neither of the classic gate's guards (framework allowlist, multiremote), while its only close signals are emitted by WdioMochaTestFramework, which is wired for webdriverio-mocha alone — so on CLI + cucumber the window appears to open at driver creation and never close. Two smaller items: beforeSuite is covered only for the first suite in a session, and the CLI test for the uuid-preservation rule hand-feeds the flag instead of going through commandWrapper, so the :308 wiring is unpinned. Details in the inline threads.

Review feedback: the command wrapper fires performScan for any wrapped command and never knows
which hook it is in, so a window state machine keyed on hook boundaries was the wrong shape for
the question being asked.

The question is whether anything can own the scan, which is a property of the moment:

    hasNoParent = no framework hook run uuid && no test context

Framework hook runs are reported to TRA and keep their test uuid, so they are untouched without
needing a hook boundary to protect them. This deletes preTestWindowActive, its three clears, and
the onHookStart/beforeHook plumbing added for it.

Two things fall out. A beforeSuite between suites is now parentless instead of inheriting the
finished test's uuid — the reviewer's second finding, fixed without reopening anything. And the
CLI "window never closes" concern stops being expressible, there being no window to leave open.

_testIdentifier was set at beforeTest/beforeScenario and never cleared, so on the classic path it
meant "has any test started" rather than "is one running". It is now cleared at afterTest and
afterScenario, which also makes startA11yScanning's "cannot be started from outside the test"
guard effective between tests instead of only before the first one.

The framework allowlist stays on the gate: it governs SCANNING, not attribution, and App
Accessibility is unsupported on jasmine, which must gain no scans it did not have before.

Tests: the CLI cases now drive commandWrapper rather than hand-feeding the flag, so they pin the
production call site — the reviewer's fifth finding. Added the between-tests case. Missing
trailing newline restored. All 12 fail against origin/main src.

SDK-7422
…k hook or test

The all-hooks E2E matrix showed the per-scan rule reaching further than intended. Scans in
cfg afterHook, the head of cfg beforeTest, and the trailing cfg afterScenario carried a test uuid
on main and lost it here, because at those moments no hook run is active and no test is
registered — true of every gap between tests, not just the one before the first.

The rule is now latched: _testContextSeen is set at the first framework hook or test of the
session and never reset, so only scans that precede anything the framework does are parentless.
Everything from the first framework hook onward is byte-for-byte main's behaviour.

That also makes the earlier _testIdentifier clear unnecessary, so it is reverted along with its
side effect on startA11yScanning's "cannot be started from outside the test" guard. The cost,
accepted deliberately: a beforeSuite between suites goes back to inheriting the finished test's
uuid, exactly as on main.

Verified across the full matrix — app and web, mocha and cucumber, every session-scoped config
hook, two describes so beforeSuite fires twice. App mocha: baseline 17 scans, branch 19 — the
17 identical, +2 parentless in cfg before / cfg beforeSuite.

SDK-7422
@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Full E2E matrix: App-A11y × Web-A11y × mocha × cucumber, every WDIO config hook

New bench examples/allhooks/ — all eight session-scoped config hooks annotate the session log and fire a scannable command, and the mocha spec has two top-level describes so beforeSuite fires twice. Web runs Chrome on Automate from the same bench, so app/web differ only in capabilities. 16 device runs: 4 combinations × baseline/branch, twice (the matrix caught a problem, see below).

combo baseline (main) branch delta
app · mocha hbsbpvzj… · 8c2b1298… — 17 scans t36teubh… · 9abf39c1… — 19 +2 parentless (cfg before, cfg beforeSuite); the other 17 identical
app · cucumber wox40lb8… · 21e12ec3… vmwbgdkt… · bedc7b6b… — 22 +2 parentless (cfg before, cfg beforeFeature)
web · mocha qcsbkhut… · ecc3dc2a… — 2 scans hazbjuh4… · 179ba6da… — 3 +1 (cfg before)
web · cucumber tcvmcs4b… · b3c5f278… — 2 scans mn7cew8l… · 31ca6e0d… — 3 +1 (cfg before)

What the matrix caught, and the fix (5d7d6ac)

The first version of the per-scan rule reached further than intended: cfg afterHook, the head of cfg beforeTest and the trailing cfg afterScenario carried a test uuid on main and lost it, because at those moments no hook run is active and no test is registered — true of every gap between tests, not just the one before the first.

The rule is now latched: _testContextSeen is set at the first framework hook or test of the session and never reset. App · mocha is the proof — baseline 17 scans, branch 19, and scans 3–19 are identical to baseline including afterHook (5, 8, 15) and beforeTest (9, 16) keeping their uuid. Only the two scans that precede anything the framework does are parentless.

This also made the earlier _testIdentifier clear unnecessary, so it is reverted along with its side effect on startA11yScanning. Accepted cost, deliberately: a beforeSuite between suites goes back to inheriting the finished test's uuid, exactly as on main — so @07souravkunda's second finding is now "same as main" rather than "fixed".

Web attribution (C1) — measured, and accepted for now

Web sends a real thTestRunUuid exactly twice per session, at save_results, one per test; the per-scan payload is { method } only. So the new cfg before scan is flushed into the first test's results. Magnitude is one scan per session in this bench. isGlobalHook cannot reach that path, and fixing it means flushing the pre-test buffer with no uuid — which depends on whether the extension accepts a uuid-less save. Shipping the coverage with the limit documented, and raising the contract question separately.

Suite 1232 passed / 0 failed; tsc and lint clean.

onAfterTest deleted the session's entry from accessibilityMap, and only the next test's
onBeforeTest re-created it. Nothing scanned in the gap: afterSuite and after went unscanned on
this flow for the life of the session, and a driver command in either was invisible.

The classic flow never deleted, so the same customer code scanned on cucumber and was silently
skipped on mocha. This removes the delete rather than forcing the entry true, so a tag-excluded
test and a user's stopA11yScanning() still decide what happens next.

Post-test scans carry the last test's uuid, which is what the classic flow has always done for
cucumber's after hooks. Verified on device: app mocha now scans cfg afterSuite and cfg after
(21 scans, up from 19), both stamped with the finished test's uuid. Web is unchanged at 3 scans
— back() is not a wrapped command there — so nothing is gained or lost on that path.

The test drives onAfterTest to completion, stubbing the stop-event internals that throw against
these mocks; without that the assertion passes with the delete restored and proves nothing.

SDK-7422
@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

afterSuite / after were never scanning on the CLI flow (027b140)

Raised in review of the matrix: the all-hooks bench fires a driver command in every config hook, yet the mocha census stopped at cfg afterTest.

Cause, and it predates this PR. onAfterTest ended with this.accessibilityMap.delete(sessionId), and only the next test's onBeforeTest re-created the entry. So on the CLI flow nothing scanned in the gap between tests or after the last one. The classic flow never deleted, which is why the same customer code scanned on cucumber (cfg afterScenario, cfg afterFeature, cfg after all present, carrying 997b4b07) and was silently skipped on mocha.

Fix: remove the delete. Not force the entry to true — leaving it as the test left it keeps a tag-excluded test and a user's stopA11yScanning() in effect.

app · mocha scans tail of the census
before this commit 19 ends at cfg afterTest
after (iufzakkr… · aa79b764…) 21 20 cfg afterSuite b9ceb250, 21 cfg after b9ceb250

Post-test scans carry the last test's uuid. That is a deliberate choice, and it matches what the classic flow has always done for cucumber's after hooks rather than inventing a third behaviour. Web (1njykirx…) is unchanged at 3 scans — back() is not a wrapped command there — so nothing is gained or lost on that path.

Two corrections to my earlier comments

  1. My beforeSuite claim was wrong. I said the two-describe spec makes beforeSuite fire twice. This census shows exactly one cfg beforeSuite and one cfg afterSuite for a two-describe spec — WDIO fires them per spec file, not per top-level describe. Reproducing @07souravkunda's C2 scenario needs two spec files. It does not change the outcome (with the latch, a later beforeSuite behaves exactly as on main), but the evidence I cited for it did not show what I said it showed.
  2. The first version of this commit's test was decorative — it passed with the delete restored, because onAfterTest throws inside its stop-event internals against these mocks and the outer catch swallowed it before reaching the line under test. Now stubbed so the method completes; verified it fails with the delete put back.

Suite 1233 passed / 0 failed; tsc and lint clean.

@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Correction: my web numbers were wrong — the bench used a command web does not wrap

@07souravkunda flagged that the web scans looked partial. They were, and the fault was in my bench, not the SDK. Correcting it publicly because the earlier matrix comment gave web figures that cannot be defended.

What happened. The web confs issued browser.back() in every hook. back is not in commandsToWrap on the web path. An ordered replay of session 179ba6daae394de87fec45b625d3dd7b19187423 shows it plainly — 18 POST /back, zero scans against any of them; the single command-driven scan fired immediately before the one POST /url:

[cfg before STARTED]  ASYNC -> scan script   CMD url      <- the only wrapped command
[cfg beforeSuite]     CMD back                            <- no scan
[cfg beforeHook]      CMD back                            <- no scan
... 18 x back, none scanned ...
[cfg afterTest]       ASYNC -> scan script                <- explicit end-of-test scan

So "baseline 2 → branch 3" measured the bench, not the coverage. url is wrapped, so the confs now alternate between two pages and every hook gets a real scan.

Corrected web matrix

combo baseline (main) branch delta
web · mocha (CLI flow) 333a9cf07617e1d2530f4af750bb800e2e48b293 — 12 scans 978fbbafbca3633ae3607267b7604dfab964126c16 +4: cfg before, cfg beforeSuite (the window) and cfg afterSuite, cfg after (the gate-teardown fix)
web · cucumber (classic) b03ea624ad8dacca843b3e75505754973fd22d76 — 16 scans 85b8ec71f14d02a216006e63adfd26886e5caf8518 +2: cfg before, cfg beforeFeature. Classic never tore the gate down, so its after-hooks already scanned

Web · mocha now shows scans in all eight config hooks. Both fixes in this PR are visible on it, and the earlier claim that web gains "one scan per session" was an understatement produced by the broken bench.

No code changed for this — the correction is entirely in examples/allhooks/web-*.conf.ts. The general lesson, which I have written into the bench: prove the chosen command is wrapped on the path under test before reading anything into a scan count, because a command that never scans is indistinguishable from a gate that never opens.

@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Correction 2: the spec and steps had the same flaw as the hooks

@07souravkunda pointed at the cucumber baseline b03ea624ad8dacca843b3e75505754973fd22d76. Fixing the hooks to use url left browser.back() in specs/two-suites.spec.js and steps/steps.js, so on web the framework-hook and test-body commands still scanned nothing — the exact half of the session where this PR claims to change nothing. The replay:

[cfg before]         CMD url                       <- no scan (baseline: window shut) OK
[cfg beforeFeature]  CMD url                       <- no scan (baseline) OK
[cfg beforeScenario] -> scan   CMD url             <- every url scans from here
[cfg beforeStep]     -> scan   CMD url
                               CMD back  <- STEP   <- no scan

Every url scans; all 4 back calls from steps scan zero. touch.cjs now picks the command by BENCH_KIND, set per conf — url on web, back on app — so hooks, specs and steps all issue a command the path under test actually wraps.

Web matrix, third and final measurement

combo baseline (main) branch delta
web · mocha (CLI) 3fbc9229f77f8e6f7ef141803270b368bf5b4a10 — 17 scans 2eed2a3dc89c0fa4c6ca78b246d604670ee8d98321 +4: cfg before, cfg beforeSuite, cfg afterSuite, cfg after
web · cucumber (classic) dc2d404fb9ed234a70ed0a8743dbd01298f7f504 — 20 scans a6111c517bf1378464766a6f9baff504e520c5bc22 +2: cfg before, cfg beforeFeature

The deltas are unchanged from correction 1 (+4 / +2) — the extra spec-level scans land on both sides, so only the absolute counts were understated. What this measurement adds is the part that was missing: everything between the new scans is now exercised and identical to baseline, so "nothing changes once the framework starts" is demonstrated on web, not just on app.

Full branch census for web · mocha shows scans in all eight config hooks plus both framework-hook and test-body positions: cfg before, cfg beforeSuite, beforeHook×2, afterHook, beforeTest×2, afterTest×2 per test, then cfg afterSuite and cfg after.

Still no product code change from either correction — both were bench defects. Three measurements in, the rule I have written into the bench is: verify the command is wrapped on the path under test, at every layer that issues one — hooks, specs and steps each needed it independently.

Comment thread packages/browserstack-service/src/accessibility-handler.ts Outdated

@07souravkunda 07souravkunda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed at 027b140. One of my four findings was wrong and I have withdrawn it: the CLI flow is not reachable for cucumber, jasmine or multiremote — CLISupportedFrameworks = ['mocha'] gates the binary bootstrap at both launcher.ts:343 and service.ts:175, which I had not traced. The web-scope decision is yours and I am not contesting it; the residual attribution question is genuinely open and correctly tracked. The test wiring and newline are fixed, and your correction on currentTestName was right. Replacing the window state machine with the latched per-scan rule is a better design than what I reviewed, and 5d7d6ac's all-hooks matrix is a stronger reason for it than my original finding was. Still holding the change request on two things. First, code: the new rule reaches the auto-scan path on both flows but only the CLI flow threads it into the manual browser.performScan() — the classic one still calls with four arguments, so a user-invoked scan from a config-level before() is stamped with the not-yet-started test's uuid on one flow and parentless on the other. Second, artefacts: three commits of design change have not reached the PR description, the ## Known limits section, or either release-note block — the description still documents the window that 5d7d6ac removed, still claims beforeSuite coverage that now holds only for the first suite, and nothing announces the between-test gate change from 027b140 that the commit measured at 19→21 scans. The commit messages are honest about all of this; what a releaser and the next reviewer read is not. Details in the threads.

kamal-kaur04 and others added 3 commits September 1, 2026 21:00
Review finding: the auto path got the rule on both flows, but the classic user-facing
browser.performScan() still called with four arguments, so hookRunUuid and isGlobalHook both
arrived undefined and the scan carried TEST_ANALYTICS_ID. The CLI equivalent was threaded. Same
customer code — performScan() from a config-level before() — was parentless on mocha and
misattributed on cucumber, and the manual path is the only route to a config-hook scan for a
setup that issues no wrapped command.

The rule is now a single getter per flow, used at every scan site: the auto path, the manual
performScan(), and the CLI's per-test re-patch, which passed the hook uuid but not the flag. That
last one was harmless — it is installed after the latch sets, so undefined and false agreed — but
it would have diverged silently the moment the rule changed.

Threading _currentHookRunUuid into the classic manual path also closes a pre-existing gap the
reviewer noted: a manual scan inside a framework hook used to land as a NULL hook row.

Two tests, both failing if the manual path is un-threaded: a manual scan from a config hook is
parentless, and one inside a framework hook carries that hook's uuid.

SDK-7422
Reviewer question on the gate-teardown change: with the map entry outliving the last test, does a
wrapped command in teardown turn a silent no-op into a logged failure? It does. Reproduced with a
driver command in afterSession, which runs after deleteSession:

  ERROR @wdio/browserstack-service/cli: Accessibility Scan could not be performed :
        Error: A sessionId is required for this command

The customer's own command fails identically a moment later, so nothing breaks — but main was
silent there and this is an error-level line in ordinary teardown, which is support-ticket
material.

Both wrappers now check the one thing that actually knows whether the session is alive — the
driver's sessionId — and skip the scan without touching the command. afterSuite and after still
scan, since those run before the session is deleted.

Verified on device: the error line is gone, the customer command still throws as it does on main,
and the all-hooks matrix is unchanged at 21 scans with cfg afterSuite and cfg after present.

The CLI mock browser gained a sessionId, since it was modelling a dead session and three tests
were passing for the wrong reason.

SDK-7422
@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

RUN_TESTS

@07souravkunda 07souravkunda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Lifting the change request at 64e8682 — every code finding is resolved, and the two rounds of self-correction (5d7d6ac bounding the rule to a latch, 64e8682 guarding the dead session) landed in better shape than what I asked for. Notably you found two things I had not: the CLI's per-test re-patch of performScan at :383, and the three CLI tests that were passing because their mock browser modelled a dead session. Reproducing the afterSession case with the actual error line rather than reasoning about it is the right instinct. Verified myself: the guard's early return in the CLI wrapper matches the normal tail so nothing is skipped or double-invoked, the classic one falls through correctly, and multiremote is unaffected since service.ts:277 seeds the gate from the same sessionId. Two non-blocking items left, both in threads. The classic commandWrapper guard has no test — the CLI side has one, and the shared classic mock carries sessionId: 'session123', so a revert there would go uncaught. And the description body plus verification table are still the pre-2b81d10 ones: they describe a window that no longer exists, claim the gate governs nothing beyond itself (which 027b140 deliberately reversed), and cite three superseded commits — while ## Known limits and both release blocks are now correct. Not blocking either; the second one only costs the next reader, and the numbers from 5d7d6ac and 027b140 are stronger evidence than the table currently carries. Approving on the code.

@07souravkunda 07souravkunda left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving to actually clear the earlier change request — my previous comment said I was lifting it, but a comment-level review does not dismiss CHANGES_REQUESTED, so the PR was still showing blocked. Assessment is unchanged from that comment: all code findings resolved at 64e8682; the two remaining items (no test on the classic dead-session guard, stale description body and verification table) are non-blocking and live in the threads.

@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

@kamal-kaur04
kamal-kaur04 requested review from AakashHotchandani and removed request for vivianludrick September 1, 2026 16:01
@kamal-kaur04

Copy link
Copy Markdown
Collaborator Author

Full E2E re-run on the final build (64e8682)

Eight fresh runs — App-A11y and Web-A11y × mocha and cucumber, baseline and branch — against the build that now carries all four changes: the latched window, the between-test gate, the manual-performScan() fix and the session-liveness guard. Bench issues a command every layer actually wraps (back on app, url on web) in all eight config hooks, both framework hooks and both test bodies.

combo baseline (main) branch delta scans lost
app · mocha (CLI) dtehwakw… · c6e142e3… — 17 rpzi0bbp… · aa5af9b4…21 +4 cfg before, cfg beforeSuite, cfg afterSuite, cfg after none
app · cucumber (classic) 7cbxbazs… · dee5ef98… — 20 we7sj8hf… · 102d47cb…22 +2 cfg before, cfg beforeFeature none
web · mocha (CLI) sje6k34f… · b7542d61… — 17 cxafmlvp… · 6a23fa4f…21 +4 cfg before, cfg beforeSuite, cfg afterSuite, cfg after none
web · cucumber (classic) 2bewlu4o… · 8cecbde9… — 20 4dcjtuhu… · dfe3a3ac…22 +2 cfg before, cfg beforeFeature none

Deltas computed as a multiset difference over scan phases, so "scans lost: none" is a measured result, not an eyeball: no scan present on main is missing on the branch, on any of the four combinations.

What the shape tells you

  • +4 on both mocha rows, +2 on both cucumber rows — the gain tracks the flow, not the product. Mocha runs the CLI flow, which gains the two pre-test hooks and the two teardown hooks (the between-test gate was deleted there). Cucumber runs the classic flow, which never deleted the gate, so its teardown hooks already scanned and it gains only the two pre-test ones.
  • App and web are now identical in shape, which is the part the earlier bench defects hid.
  • Attribution unchanged after the first framework hook. On app · mocha the 17 baseline scans keep their uuids exactly; the 4 additions are parentless before the first test, and the two teardown scans carry the finished test's uuid as documented.

Teardown, re-confirmed

The afterSession probe still fires a driver command against a deleted session, and with the liveness guard the run is clean — no Accessibility Scan could not be performed line. The customer's own command throws exactly as on main.

Suite 1236 passed / 0 failed; tsc and lint clean.

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.

2 participants