Skip to content

v8: establish the standalone @wdio/browserstack-service v8 maintenance line - #42

Open
AakashHotchandani wants to merge 99 commits into
mainfrom
v8
Open

AakashHotchandani wants to merge 99 commits into
mainfrom
v8

Conversation

@AakashHotchandani

Copy link
Copy Markdown
Collaborator

Parallels the v9 conversion (#37) for the v8 line. The v8 branch is the long-lived v8 maintenance line, published to the npm v8 dist-tag via publishConfig.tag.

⚠️ Review/CI vehicle — not to be merged into main. v8 is a separate line from main (v9); it stays as the v8 branch. This PR mirrors how #37 tracks the v9 line. The mergeable release vehicle is the auto-opened "Version Packages" PR (→ 8.49.0).

Contents

Release

Manual/OIDC: push opens the "Version Packages" PR; Run workflow → publish does changeset publish --tag v8 with provenance. Gate the first v8 publish behind the v9 first-publish + freeze.

Regression

SDKWdioRegressionSharded: RUN_V8=true, WDIO_V8_BRANCH_NAME=v8, INSTALL_MODE=build-package.

🤖 Generated with Claude Code

AakashHotchandani and others added 15 commits June 13, 2026 16:10
…ide the gRPC core

Adds the WebdriverIO service (@wdio/browserstack-service) as packages/browserstack-service
and moves the existing gRPC/protobuf core (@browserstack/wdio-browserstack-service) into
packages/core (git history preserved), so both ship from this repo.

- npm workspace: root build builds core then service; tests scoped to the service.
- service: esbuild bundle (deps external) + tsc declarations; strict files allowlist
  (build + README + LICENSE + ambient d.ts); peerDeps for webdriverio/@wdio/* so the
  consumer keeps a single shared copy.
- release: Changesets + npm OIDC trusted publishing via .github/workflows/release.yml
  (main -> latest, v8 -> v8 dist-tag); the gRPC core is ignored by Changesets and stays
  on the SDK team's manual publish flow.
- fix(tests): the @wdio/reporter mock no longer imports the real module from within its
  own mock (a top-level vi.importActual + static import deadlocked vitest and hung
  reporter.test.ts); stub the stats classes instead. Full suite: 39 files / 980 tests
  pass and the run exits cleanly.
- repo metadata -> browserstack/wdio-browserstack-service; an added changeset bumps the
  first release to 9.29.0 (current npm latest is 9.28.0).

Verified locally: npm ci + build + test green; clean 80-file / ~590kB tarball; a real
BrowserStack session passed with webdriverio deduped to one copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The committed lockfile was stale (carried entries from the pre-conversion layout), so CI's `npm ci` failed the sync check (EUSAGE: lockfile's @types/node@12.20.55 vs @types/node@25.9.3, missing undici-types). Regenerated against the current npm-workspace package.json set (760 -> 592 packages). `npm ci --dry-run` now passes; toolchain (typescript, vitest, esbuild, ts-proto, @bufbuild/buf + 7 platform optionalDeps) and workspace links (core <-> service) preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous lockfile commit was produced by `npm install --package-lock-only` against a stale node_modules, so npm shortcut it ('up to date, audited ... in 2s') and only pruned the old tree instead of fully resolving. CI's `npm ci` then rebuilt the ideal tree from package.json and found mismatches (Invalid @types/node@12.20.55 vs 25.9.3, Missing esbuild@0.28.1, etc.).

Regenerated with both package-lock.json and node_modules absent so the resolution is computed purely from package.json (598 packages). Verified locally with npm@10.9.8 (CI's npm): `npm ci` installs cleanly, `npm run build` (core buf+tsc / service esbuild+tsc) succeeds, and `npm test` passes 980/980.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-install)

The service declares non-optional peerDependencies (@wdio/cli, @wdio/logger, @wdio/reporter, @wdio/types, webdriverio). npm 7+ auto-installs peers by default, so CI's `npm ci` requires their full closure in the lock (@wdio/cli@9.28.0 -> create-wdio, tsx, inquirer, ejs, execa, esbuild@0.28.1, @vitest/snapshot@2.1.9, ...).

Prior lock commits were generated with a local ~/.npmrc legacy-peer-deps=true (left over from unrelated work), which suppressed peer auto-install and produced a lock missing that closure -> CI EUSAGE 'Missing @wdio/cli@9.28.0 from lock file' etc.

Regenerated with --no-legacy-peer-deps to match CI's default (598 -> 720 packages). Verified CI-exact: `npm ci --no-legacy-peer-deps` installs cleanly, build succeeds, 980/980 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ake timers

CI's Node 18.20 job (supported per engines: node >=18.20.0; wdio v9 supports 18.20) surfaced three Node-18-only failures that Node 20/22 tolerate:

1. PerformanceTester.start/end/measure called performance.mark/measure directly. Under vitest's full fake timers (7 test files) performance.now() goes negative, and Node 18's perf_hooks rejects negative timestamps (ERR_PERFORMANCE_INVALID_TIMESTAMP), throwing out of callers' before() hooks. Routed all mark/measure through safeMark/safeMeasure wrappers — instrumentation must never throw into business logic (no-op on the happy path; transparent on Node 20+).

2. uploadLogs used fs.openAsBlob (Node >=20 only). Added a Node-18 fallback to new Blob([readFileSync]) for the small log archive.

3. util.test.ts faked  via bare useFakeTimers(); scoped to toFake:['Date'] (it never advances timers), matching reporter.test.ts.

Verified locally on Node 18.20.5 AND Node 22: npm ci + build + 980/980 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nternal docs

Addresses automated PR review findings on #37:

- [SECURITY] release.yml: pin changesets/action, actions/checkout, actions/setup-node to full commit SHAs (with version comments) since this workflow holds id-token:write + publish. ci.yml left on tags (contents:read only).
- [DOCS] packages/core/package.json: description now describes the gRPC/protobuf core (not the service); dropped the 'webdriverio' keyword — corrects the public npm listing.
- [SECURITY] removed the five docs/ strategy/migration files from the public repo (kept internally). Their only cross-references were among themselves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tadata

These two review fixes were edited but accidentally left unstaged when f935a52 (docs removal) was committed, so they never landed. Committing them now:

- release.yml: pin changesets/action, actions/checkout, actions/setup-node to full commit SHAs (with version comments).
- packages/core/package.json: description now describes the gRPC/protobuf core; dropped the 'webdriverio' keyword.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
setup-node's `registry-url` writes `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` into .npmrc; with no NODE_AUTH_TOKEN that empty token line can shadow npm OIDC Trusted Publishing at publish time. npm defaults to registry.npmjs.org and publishConfig.access=public already covers the scoped publish, so the line is unnecessary. Splitting the changesets publish into its own top-level step is noted as a further OIDC-hardening follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- v8 service (8.48.0) swapped into packages/browserstack-service; metadata repointed
  to browserstack/wdio-browserstack-service; core dep ^2.0.2; files += README/LICENSE-MIT.
- ported safeMark/safeMeasure (Node-18 perf_hooks) into performance-tester.
- changeset baseBranch=v8.
- regenerated package-lock.json with DEFAULT npm (no --legacy-peer-deps): npm ci is in
  sync, so v8 does NOT need the flag (resolves reviewer concern).
- build green (core buf+tsc; service tsc); per-file tests pass.

KNOWN-OUTSTANDING: full-suite 'vitest --run' hangs in teardown (open PerformanceObserver
handles + vitest 1.x not force-exiting). Toolchain decision pending (vitest 1.x->3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-file tests all pass and exit cleanly; whole-suite vitest --run still hangs in
teardown under vitest 1.x (open handle survives pool/teardown cleanup). Standalone
teardown remains the outstanding follow-up. setupFiles wires the per-file cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…flake, per-file runner

Root causes of the standalone 'teardown hang' (upstream only runs these inside the
full monorepo suite, never standalone):
- __mocks__/@wdio/reporter.ts imported the stats classes from '@wdio/reporter' — the
  module it mocks — deadlocking mock resolution at collection (file hung, no output).
  Stats are type-only in the service, so replaced with stub classes. THIS was what
  wedged the whole-suite run.
- util.test.ts logPatcher spied process.emit at describe scope without resetting it, so
  a stray emit made the count flaky (7 vs 6). Clear the spy in the test.
- reporter.test.ts: scope fake timers to ['Date'] (mirrors v9).
- vitest.setup.ts: restore real timers + disconnect the PerformanceTester observer per
  file (dynamic import to avoid a circular-decorator init failure).
- test script now runs each file in its own process (scripts/test-isolated.mjs), judging
  by test result not clean exit, so any residual post-test open-handle lingering can't
  wedge CI. Result: 41/41 files pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generate the gRPC/protobuf client inside @wdio/browserstack-service at
build time (buf generate -> src/grpc/generated) instead of depending on the
separately-published @browserstack/wdio-browserstack-service core.

- move buf.yaml / buf.gen.yaml + src/proto/** + the barrel (-> src/grpc/index.ts)
  from packages/core into packages/browserstack-service
- rewire all imports from '@browserstack/wdio-browserstack-service' to the
  local './grpc/index.js' barrel
- deps: drop the core, add @grpc/grpc-js + @bufbuild/protobuf;
  devDeps: add @bufbuild/buf + ts-proto
- build/clean/prepack run `buf generate` before tsc; src/proto ships in the
  tarball, src/grpc/generated stays gitignored
- delete packages/core; collapse root build script; changeset ignore: []

The published @browserstack/wdio-browserstack-service@2.0.x stays on npm
as-is (historical); nothing publishes a separate core going forward.

Verified on Node 22 + npm 10: build green (buf generate -> tsc), 41/41
test files pass, npm pack ships build/grpc + src/proto and no core dep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parity with the v9 fix (7c949da). CI runs plain `npm ci`
(legacy-peer-deps=false default), which requires the auto-installed
`@wdio/cli` peerDependency closure to be present in the lockfile. The
prior lockfile was generated with legacy-peer-deps=true and omitted that
closure, so npm ci failed "out of sync".

Regenerating with the peer tree exposed the same @types/node hoist
non-determinism as v9 (protobufjs/ts-proto pulls @types/node@25,
@manypkg/find-root pins ^12.7.1, and the v8 stack wants ^22), fixed with:

- overrides @types/node ^22.2.0 — collapses the 3-way spread to a single
  22.x (matches the service's declared ^22.2.0; types-only, no runtime
  impact), so npm install and npm ci agree.

Unlike v9, NO estree-walker workaround is needed: v8 uses vitest ^1.6.0,
which predates @vitest/mocker, so the deeply-nested-transitive drop that
affected v9 doesn't occur here.

Verified: `npm ci --legacy-peer-deps=false` passes, build passes, 0
runtime holes, full @wdio/cli peer tree present, cross-platform complete,
per-file tests green (service.test.ts 97/97). The aggregate
`npm test` (test-isolated.mjs) still hits the pre-existing all-at-once
teardown follow-up, unrelated to the lockfile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ow (upstream #15330)

Ports webdriverio/webdriverio#15330 (merged into monorepo v8 after the 2026-06-16 extract):
in the CLI/binary v8 flow, forward screenshot-on-failure to the binary over gRPC as a
TEST_SCREENSHOT log (uploaded via the binary's authorized testhub session) instead of the
direct-HTTP onScreenshot path (which 401s under the worker's binary-issued JWT); register the
command/result listeners in CLI mode so the screenshot command result is captured; and honor the
incoming log kind in the mocha CLI framework. Keeps the standalone v8 line at parity with the
monorepo. Src-only change (matches upstream — no test changes); builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elease.yml

- Move @wdio/logger|reporter|types from peerDependencies to dependencies (pinned 8.x),
  mirroring the v9 line so the published package carries them.
- Switch release.yml to the manual/hybrid model (parity with the v9 branch): push opens the
  "Version Packages" PR only; publishing is a manual workflow_dispatch (publish=true) →
  v8 dist-tag via publishConfig.tag, over OIDC. The existing take-over-publishing changeset
  (minor) computes the first standalone v8 release as 8.49.0 (> npm v8 8.48.3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread .github/workflows/ci.yml Fixed
Comment thread .github/workflows/ci.yml Fixed
Comment thread packages/browserstack-service/.github/workflows/ci.yml Fixed
Comment thread packages/browserstack-service/.github/workflows/ci.yml Fixed
Comment thread packages/browserstack-service/.github/workflows/release.yml Fixed
Comment thread packages/browserstack-service/.github/workflows/release.yml Fixed
Comment thread packages/browserstack-service/.github/workflows/release.yml Fixed
Comment thread packages/browserstack-service/src/insights-handler.ts Fixed
Comment thread packages/browserstack-service/src/util.ts Fixed
const sessionId = browser.sessionId

// a utility function to escape single and double quotes
const escapeString = (str: string) => str.replace(/'/g, "\\'").replace(/"/g, '\\"')
const sessionId = browser.sessionId

// a utility function to escape single and double quotes
const escapeString = (str: string) => str.replace(/'/g, "\\'").replace(/"/g, '\\"')
// a utility function to check if the hostname is browserstack

const isBrowserstack = (str: string ): boolean => {
return str.includes('browserstack.com')
…lour) + SHA-pin ci.yml actions

- test-isolated.mjs: under CI=true vitest forces ANSI colour even when piped, so the summary
  regexes (`Tests \d+ passed`) failed to match (colour codes between "Tests" and the count) and
  every passing file was mis-flagged "no test summary" → CI red. Strip ANSI in classify() and set
  NO_COLOR/FORCE_COLOR=0 on the spawned vitest. Verified: `CI=true npm test` → 41/41 files pass.
- ci.yml: pin actions/checkout + actions/setup-node to full commit SHAs (resolves the semgrep
  github-actions-mutable-action-tag findings, parity with the v9 line).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
function classify(output) {
// CI (CI=true) makes vitest force ANSI colour even when piped, which would break the
// summary regexes below (colour codes sit between "Tests" and the count). Strip them first.
const clean = output.replace(new RegExp(String.fromCharCode(27) + '\[[0-9;]*m', 'g'), '')
AakashHotchandani and others added 8 commits July 11, 2026 21:13
…ith/slice + drop vestigial nested workflows

- getHierarchy (insights-handler.ts + util.ts): replace `new RegExp(' ' + test.description + '$')`
  with an endsWith/slice suffix strip — avoids ReDoS and regex-metacharacter mismatches when the
  Jasmine description contains regex specials. Behaviour-preserving; mirrors the v9 fix (65d7c33).
- Remove packages/browserstack-service/.github/workflows/{ci,release}.yml — vestigial duplicates
  from the extraction (GitHub only runs root-level workflows; these never executed) that carried
  unpinned action tags. Root workflows are already SHA-pinned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… peer (v8)

Same parity fix as v9: the extraction moved webdriverio from dependencies into
peerDependencies. The v8 package published from the WebdriverIO monorepo
(@wdio/browserstack-service@8.48.3) keeps webdriverio in dependencies (8.46.0) and
peers only @wdio/cli. Move it back to dependencies to restore install parity and
avoid npm ERESOLVE against deps peering a non-overlapping webdriverio range.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(v8): declare webdriverio as a dependency, not a peer (install parity)
Port of webdriverio/webdriverio#15374 (v8 backport of #15373) to the
standalone repo.

With skipAppOverride: true the service classifies the session as App
Automate even when no app option is set, skips the app upload, and does
not inject an appium:app capability — the user supplies the app
reference themselves. Conflicting app option is warned about and
ignored; skipAppOverride: false with no app fails fast before any
session starts. The option is stripped from bstack:options and never
forwarded to the hub.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…exit-signal/log-redaction

Ports 4 upstream webdriverio/webdriverio v8 commits to the standalone v8 line:
- #15231 test management (testPlanId) support + CLI-only caps stripping + build-start errors
- #15217 raise gRPC send/receive message size limit to 20 MB
- #15146 terminate CLI process with SIGINT (not SIGKILL) on Unix
- #15200 redact credentials from CLI log output

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…LI flow

v8 port of the v9 fix, plus the hook-routing backport v8 never received:

- beforeHook/afterHook CLI branches: route hook lifecycle to the binary via
  the TestFramework tracker (gRPC). Without this the v8 CLI flow emits no
  HookRunStarted/HookRunFinished at all (the legacy Listener -> api/v1/batch
  path is not functional in the CLI pipeline).
- cli/skipReporter (new): reports tests that never reach beforeTest/afterTest
  (static it.skip, this.skip() in before/beforeEach hooks, suites aborted by a
  failed before hook) through the tracker using the same INIT_TEST -> TEST PRE
  -> LOG_REPORT POST -> TEST POST sequence afterTest uses, serialized (wdio
  does not await reporter hooks) and deduped against started tests.
- reporter.onTestSkip CLI branch + service.afterHook skip cascade (port of the
  legacy insights-handler skip propagation).
- Hook results: reading `testResult.status` (nonexistent on TestResult) left
  hook_result at 'pending', coerced downstream to 'passed' — failed
  before-hooks produced green builds while CI exited 1. Map from the actual
  passed/skipped fields.

Without these, such tests are invisible on the dashboard and their Automate
sessions are never linked to the build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kamal-kaur04 and others added 30 commits September 21, 2026 18:52
AccessibilityModule.onBeforeExecute wrapped every entry of the server-sent
commandsToWrap list in one unguarded loop. The list can name a command the
active driver never registered: appium sessions omit web-only commands, and
the list also carries Selenium-shaped entries (startA11yScanning,
stopA11yScanning, performScan with class HttpCommandExecutor, library
org.openqa.selenium) meant for other SDKs. WebdriverIO's overwriteCommand
throws on an unknown name, so the first such entry aborted the whole loop,
left every command after it unwrapped, and surfaced as
"Error in onBeforeExecute: overwriteCommand: no command to be overwritten:
startA11yScanning".

Guard each overwriteCommand call individually so an unknown name is skipped
(debug-logged) and the rest of the list still wraps. Ports the guard already
shipped on the v9 line in 8539e5f to v8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…9 line

The v8 guard body is byte-identical to the one on v9; the comment was not.
Drop the v8-specific narrative for v9's rationale, minus the isAppAccessibility
history that never applied to v8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
changeset-from-pr.yml generates .changeset/pr-<number>.md from the PR's Release
section on this branch, so a hand-written file only duplicates the CHANGELOG entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
getAppA11yResultsSummary and getA11yResultsSummary caught their failure with a bare
`catch`, logged a fixed "No accessibility summary was found." and returned {}. The
sibling getters (getAppA11yResults, getA11yResults) bind the error and debug-log it;
these two discarded it.

The message is the same whether the results API errored, returned nothing, or the
30s poll in getAppA11yResultResponse timed out, so an empty summary on a real run
cannot be told apart from an API failure. Bind the error and debug-log it, matching
the results getters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port of the main-line fix to the v8 branch; the reported build pinned
--wdio_versions v8.

WDIO Mocha and Jasmine never populated a test-level tags field, so every
Observability test arrived with tags == []. @tag tokens in the suite and
test titles are the source, matching the node SDK's Jest/Playwright
convention. The leading @ is kept so these match Cucumber pickle tags.

Both event paths fixed: wdioMochaTestFramework#getTestData now sets
KEY_TEST_TAGS (declared in the service's constants, never written, long
consumed by the binary), and insights-handler#getRunData now sets tags
on TestData.

Inert for untagged suites -- a title with no @ token yields [].

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit put the cause behind BStackLogger.debug, which does not print at
the default log level -- on O11Y build t4x7uep76ef2im4wwjv0rcm6jk6aohbkrdqhni6h the
run carried the change and still emitted a bare "No accessibility summary was found."
with no DEBUG line anywhere.

The message is already an error, so the cause belongs on that line. An empty summary
now reports whether the results API errored, returned no payload, or the 30s poll in
getAppA11yResultResponse timed out, without needing logLevel: debug.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pollApi throws a plain object, not an Error:

    throw { data: {}, headers: {}, message: ... }

so `${error}` produced a literal "[object Object]" — which is what O11Y build
eo2oxrhccxnhwxf5f3gcg4i8zqhx2ddsgagusp93 logged, still saying nothing about the cause.
The useful field is `message`, carrying the server's message from the response body.

Render `error?.message` with a util.inspect fallback, in all three getters. The results
getter also stops claiming "No accessibility summary was found" when it failed to fetch
results, and drops its debug duplicate.

Worth noting for whoever reads the next failure: of pollApi's paths only a non-404 error
response throws. A poll that exhausts the upper time limit, a 404 with a missing
next_poll_time header, and a request with no response all RETURN { data: {} } instead. So
this error line means a hard HTTP error from the results API, not a timeout.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A local App Automate run of the android start-a11y cell produced:

    ERROR @wdio/browserstack-service: No accessibility summary was found.
    Error: { data: {}, headers: {}, message: undefined }

message is undefined because the results API answered with a JSON body that has no
`message` field — the ternary only covers an EMPTY body, so a JSON error body of any
other shape yields undefined and the reader learns nothing. The status code and the
body itself were never carried at all.

Carry statusCode and body on the rejection, and fall back to `HTTP <code>: <body>` when
the body has no message, so an empty summary always names the HTTP failure behind it.
JSON.parse is also guarded: a non-JSON error body previously threw inside the catch,
replacing the real failure with a SyntaxError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A local run showed the results API answering HTTP 422 "test_run_uuid is invalid", but the
log did not say which uuid was sent, so the failure could not be attributed to the SDK or
to the service. Both App A11y getters now report testRunUuid and sessionId alongside the
error. Neither is a secret; the a11y JWT is still never logged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port of the main-line review fixes (#214).

- Tag pattern now requires the @ to start a token. /@[\w-]+/g matched any
  embedded @, so 'user@example.com' produced a bogus @example tag.
- reporter.ts#getRunData now sets tags. insights-handler#getRunData is
  reached only for mocha, so jasmine events and mocha skipped tests went
  through the reporter, which set scopes but no tags -- the Jasmine claim
  was false without this.
- Doc comment no longer claims node-agent parity; that SDK strips the @.
- Tests: embedded-@ cases, plus one per hierarchy shape with scopes
  omitted, pinned to the order the helper actually returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AccessibilityHandler.before() iterates the SAME server-sent
accessibilityScripts.commandsToWrap list as the CLI module and called
browser.overwriteCommand with no per-command boundary, so the Selenium-shaped entries
(startA11yScanning/stopA11yScanning/performScan, class HttpCommandExecutor) produce the
SDK-7452 symptom on that flow as well.

It is reachable for App Automate — the handler takes isAppAutomate and branches on
isAppAccessibilityAutomationSession — and before() has no try/catch of its own, so a throw
rejects the whole hook rather than being logged as it is on the CLI side.

v9 already guards this loop; this ports that guard, including its debug message. v9's
prevImpl/orig binding is a separate change needing a different commandWrapper signature
and is deliberately not ported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The regression test assigned to the module-scope accessibilityScripts singleton and reset
it as the last statement of the test body, so any earlier throw — a failed expect, or
onBeforeExecute rejecting — left a populated commandsToWrap behind for every later test in
the file, turning one real failure into a cascade. afterEach only calls vi.resetAllMocks(),
which does not restore a plain property written onto the mocked module.

Capture the original and restore it in a finally block, rather than resetting to a
hardcoded [] that would be wrong if the shared mock ever gains a non-empty default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DEF-12: the guard added to AccessibilityHandler.before() changed behaviour in a file the
repo tests densely (accessibility-handler.test.ts, 26 cases) with no test of its own.

Drives the real payload shape — an HttpCommandExecutor-class startA11yScanning between two
Element commands — and asserts all three wraps are attempted and the trailing command still
wraps. Verified to fail without the guard ("expected spy to be called 3 times, but got 2")
and pass with it.

The existing suite never reached this loop: the shared browser mock has no
overwriteCommand, so `'overwriteCommand' in browser` was false in every other case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, not only when it passes (v8)

Rolls the mandatory-to-run gate (SDK-7256) onto the v8 line. main already
has this via #202; v8-based PRs (e.g. #212) were still running the old
mandatory-to-pass gate on pull_request events, so a failing-but-run verdict
kept the gate red. CI/workflow only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry-run-v8

ci(sdk-pr-review-gate): green when the agent RAN on the latest commit, not only when it passes (v8)
…e (SDK-7358)

Two changes to the end-of-build summary rendering added in this PR.

Colour: a warn entry (an outdated SDK) is tinted yellow and an error entry
(a deprecated one) red, with the block's first line of text emphasised.
Lines are wrapped and reset individually rather than the block as a whole,
so a truncated or interleaved write cannot leave the customer's terminal
stuck in colour. Applied to the stream copy only — the archived copy stays
plain, because escape codes reach a log file as literal bytes and break
anchored searches over it.

Archiving: switch from the info/warn/error helpers to logToFile. Those
helpers also call @wdio/logger, which writes to the console, so the
customer saw the block twice — once raw from the stream write, once
prefixed by the logger. logToFile writes to the log file only. Unlike v9,
this branch's logToFile does not redact, but neither did the helpers it
replaces, so redaction behaviour is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-up on SDK-7358.

renderCustomerVisibleSummary wrapped the whole `for (const entry of
entries)` loop in one try/catch, so a stream that rejected entry N
aborted the loop: entries N+1.. were neither written nor archived. The
PR's own "still returns the response when rendering throws" test shows a
throwing write is a considered scenario. Only one entry exists today
(the version nudge), but the proto is explicitly built for more entry
types, so the gap widens as they are added.

Moves the catch inside the loop body, and writes the archived copy
before the stream write so archival no longer depends on the write
succeeding. The outer catch stays — stopBinSession rethrows, so a throw
escaping this method would cost the caller its response.

Covered by a new test that fails without the fix (only the throwing
entry reaches stderr; the two after it are dropped).

Package suite on Node 25: 46 files, 1056 tests, 0 failures.
tsc --noEmit exit 0. (No lint script exists on this branch.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s-v8

feat(browserstack-service): render end-of-build summary entries (SDK-7358) [v8]
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.