Release 27 aug - #169
Merged
Merged
Conversation
Follow-up to #148. @wdio/mocha-framework binds beforeTest to the test function itself (wrapGlobalTestMethod), so onBeforeTest is the single per-test naming opportunity and it has already passed by the time the test body calls browser.reloadSession(). The replacement session is never registered in sessionMap, so flushSessionName() returns early on !sessionData and the onAfterExecute sweep -- which iterates the same map -- misses it too. service.onReload learns the new session id but only renames the outgoing one, leaving the live session on its creation-time sessionName capability. onAfterTest now re-resolves the live session id and adopts it into sessionMap before flushing the name, while that session is still open. The naming block is gated on skipSessionName rather than skipSessionStatus and sits above the status gate: gating a name repair behind a status flag would skip it for setSessionStatus:false users, and adopting unconditionally would pull setSessionName:false users into sessionMap and start issuing them a status PUT per session where they previously had none. Adoption also records the test result, so the post-reload session's status is marked at teardown instead of being dropped by the old `if (sessionData)` guard. Steady state costs nothing extra -- appliedName de-dupes the flush when the session did not change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ture aliased configs Three defects, each reproduced on an App Automate device run and verified against the session's own appAllyScan telemetry. App-A11y scanning is gated on a per-session entry that only onBeforeTest and onHookStart ever wrote, and that entry is keyed on the session id: - Commands issued from WDIO's config-level before()/beforeSession() ran before any entry existed, so a hook that launches the app and signs in produced no scans at all while still counting as expected coverage. The gate now opens at driver creation. - browser.reloadSession() leaves the entry stranded under the old session id, so nothing scanned for the rest of the reloading test. onReload now migrates it, on both the CLI and the classic handler paths. Config auto-capture followed only relative import specifiers, so a project that wires its split configs through tsconfig `paths` uploaded its entry config alone — the file carrying the hooks was never in the bundle. Aliases are now resolved from the nearest tsconfig/jsconfig (following `extends`, tolerating JSONC); the *.conf.* name filter still keeps application source out. SDK-7422
…replace CodeQL flagged the substitution as an incomplete replacement. The sharper problem is the other half of `String.replace` with a string pattern: `$&` / `$'` in the REPLACEMENT are interpreted, so a specifier carrying those characters resolved to a corrupted path and the imported config was silently dropped from the bundle. Slicing at the first `*` is immune to both, and TypeScript allows at most one `*` per target, so the first is the substitution point by definition. Test pins it — it fails on the previous implementation, which resolved `@confs/a$&b.conf` to `configs/a*b.conf.ts`. SDK-7422
Scans fired from WDIO's config-level before() had neither thTestRunUuid (no test exists yet) nor thHookRunUuid, so App-A11y's lookup of the scan's parent in BTCER had nothing to find — the scan reached the hub and then belonged to nothing. The config-level window is now reported as a BEFORE_ALL hook run through the existing framework path, so the framework mints the uuid, the binary emits HookRunStarted/Finished for it, and this module's own onHookStart observer stamps it onto the scans. The instance the tracked hook creates is the one the first test reuses, so the hook also lands parented to a real test_run_id rather than orphaned. Opened ON DEMAND, from the scan path only. WDIO gives a service no event for a user's config-level before() — ConfigParser.addService folds the user's config hooks and every service's hooks into one config.before array that the runner fires with Promise.all — so there is nothing to detect, and opening it from the lifecycle would report a hook that never ran on every build in the fleet. Opening it when a scan needs a parent is exactly the condition that matters, and it self-limits: verified against a control config with no config-level hook, which reports only its four real Mocha hooks. SDK-7422
…hook Wraps the handlers registered for each WDIO hook that runs with a live driver, so the window a driver command falls into is observable. WDIO tells a service nothing about the other handlers in a hook array — ConfigParser folds the user's config hooks and every service's hooks into one array the runner fires with Promise.all — so patching the array in place is the only way to see a non-ours handler start and finish. Logging only; no events, no behaviour change. Sync handlers stay sync so a sync throw is not converted into a rejection. Verified: 'before' appears only when the user declares one (control config with no hooks shows none), while the beforeTest/afterTest entries come from expect-webdriverio's snapshot and soft-assert services — so 'registered at construction' means the user's hooks plus any service constructed before ours, not user code alone. SDK-7422
… instead
Reverts the tsconfig `paths` alias resolution added earlier in this PR, restoring
configCapture to what it was: relative imports only, entry config plus package.json.
Following aliases widened what gets uploaded from a customer's project, and the actual
need — seeing the hook code behind a failure — does not require uploading anything.
Instead the launcher reads the hook sources out of the config file and logs them. The
parsed config cannot supply them: ConfigParser folds every hook into an array as
`hook.bind(service)`, and a bound function stringifies to `[native code]`, so the file
is the only place the bodies survive.
Also records which hooks call reloadSession and publishes the list on the environment as
BROWSERSTACK_HOOKS_WITH_RELOAD_SESSION — a reload inside a hook swaps the session under
the driver, which is the shape behind the scan-gate defect this PR fixes.
Parsing notes: brace matching is lexical, so a `}` inside a string, comment or template
literal does not truncate a body; and the parameter list is skipped by balancing its
parentheses before looking for the body brace, because a typed parameter carries its own
braces (`beforeTest: function (test: { title?: string })`) and taking the first one
captured the signature instead of the hook.
Known limit, unchanged from before: only hooks written in the entry config are found. A
split config that imports its hooks elsewhere yields nothing — the same blind spot the
alias work was aimed at, now without uploading files.
The config-level before() scan fix moves to SDK-7422-config-level-before-hook.
SDK-7422
Hook bodies are customer code and the debug log is uploaded, so the captured source now goes
through redactSensitiveContent — the same routine that guards an uploaded config file.
BStackLogger's own scrub is not enough on its own. Measured against a hook body carrying nine
sensitive shapes, it leaves six readable: authToken, password, clientSecret, a snake_case
AWS secret, URL userinfo and an inline `token:` value. It knows only user/key/userName/accessKey
in a key-value or query-string position.
The identifier scan moved ahead of redaction, which is not cosmetic: redaction replaces the
whole matching line, so `await browser.reloadSession({ userName, accessKey })` — reloading with
fresh credentials, a real pattern — collapses to [REDACTED] and the call would disappear from
the detection. Raw text is scanned for the tracked identifiers, then the stored copy is
redacted; extractUserHookSources returns both, and hooksUsing documents that it can only be as
complete as the text handed to it.
Test fixtures use neutral sentinels rather than realistic secrets — the redactor keys on the
field name, not the value shape, so nothing is lost by not committing token-shaped strings.
SDK-7422
Everything that writes to the uploaded logs leaves this branch, so the PR is the reloadSession scan-gate fix and nothing else. configCapture, constants and launcher are identical to main again. Parked on SDK-7422-hook-source-logging, complete with the redaction fix: reading a customer's hook bodies into an uploaded log needs its own review, on its own timeline, not as a rider on a scan-gate fix. SDK-7422
Review catch: the classic handler's migration ran in both flows, and under the CLI it is redundant. The handler is constructed either way, but `before(sessionId)` — which records _sessionId and populates the scan map — runs only in the else branch of the CLI check, so in the binary flow the object holds nothing to migrate. Worse than redundant, in fact: the old `_sessionId === null` fallback meant the CLI flow would adopt the new id onto an otherwise inert handler. Now an explicit either/or: the CLI flow migrates the AccessibilityModule gate, the classic flow migrates the handler. The handler also only migrates the session it was actually tracking, rather than treating null as "adopt this one". SDK-7422
Five review findings, all the same underlying shape: the scan gate is read per command from framework state, which a reload updates, while several writers captured the session id once and kept writing to it. Migrating the gate turned that latent disagreement into a live one. Before the migration both sides were stale, so they agreed and nothing scanned; after it, commandWrapper reads the new key while a captured writer sets the old one. Concretely: browser.stopA11yScanning() called AFTER a reload set false on the dead key while the live key stayed true, so scanning carried on against an explicit instruction, and startA11yScanning() was a silent no-op — in exactly the window this PR exists to fix. - accessibilityModule: the scanning toggles and the results getters resolve the session at call time via currentSessionId(). getAccessibilityResults/Summary previously reported the PRE-reload session's results, while the classic path reported the live one, so the two flows disagreed on the same operation. - accessibility-handler: the toggles write this._sessionId, which commandWrapper reads and this PR migrates, instead of the id captured in before(). - service: one _isCliAccessibilityFlow() predicate, used both where AccessibilityHandler.before() is gated and where the reload migration chooses a path. isRunning() alone is NOT equivalent — with the binary up against a non-BrowserStack provider, before() DID run, so the handler holds the live state and must still be migrated. - accessibilityMap / LOG_DISABLED_SHOWN are Map<string, boolean>: session ids are strings, the Map<number> declaration was a mis-declaration, and the casts it forced (including `as never` in the tests) are gone. Tests: the CLI branch's positive path is now asserted through the module registry rather than left to an optional chain; the non-BrowserStack-provider case is covered; and both toggle paths are pinned against the post-reload inversion. SDK-7422
The predicate added in cec234c landed between the decorator and the method, so the decoration moved to it: onReload stopped emitting its SDK_HOOK measurement, and the predicate started reporting one under hookType 'onReload' on every call — including from the before hook, so every session reported an onReload measurement whether or not it ever reloaded. Behaviour was unaffected (PerformanceTester.measure returns the raw value on the synchronous path), which is why nothing surfaced it: valid TypeScript, lint has no opinion, and the unit suites mock Measure into a pass-through so the binding has no observable behaviour to assert. Hence a structural test over the decorated files instead — verified to fail with the mistake reintroduced and pass without it. Doc comment corrected too: the two accessibility paths are NOT strictly exclusive. The module is registered on startBinResponse.accessibility?.success alone, independent of provider, so under a non-BrowserStack provider it can hold real state while the classic handler's before() has also run. What the predicate decides is which one owns the CLASSIC handler's state. SDK-7422
hamza-browserstack
previously approved these changes
Aug 27, 2026
hamza-browserstack
approved these changes
Aug 27, 2026
kamal-kaur04
approved these changes
Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this about?
Related Jira task/s
Release (mandatory for every PR — required for the
ready-for-reviewlabel)Version bump: (required — tick exactly one)
Release notes type: (optional)
Release notes (customer-facing): (optional but encouraged)
Release notes (internal): (required — engineer-facing; what actually changed / why)
Checklist
PR Validations
Run Tests: Comment RUN_TESTS to trigger sanity tests.