fix(service): report screenshot logs on the binary flow (SDK-4177) - #190
fix(service): report screenshot logs on the binary flow (SDK-4177)#190anish353 wants to merge 6 commits into
Conversation
`InsightsHandler.browserCommand` is the only producer of TEST_SCREENSHOT logs, and its two call sites both sat behind `!BrowserstackCLI.isRunning()`. Since `CLISupportedFrameworks = ['mocha']`, every Mocha run took the binary flow and lost screenshots entirely — Observability received a test run with no TEST_LOG artifact, so no screenshots manifest. Jasmine and Cucumber, which stay on the Direct flow, were unaffected. Subscribe to the result event on the binary path as well. `command` (beforeCommand) is deliberately left unsubscribed: it only fills the map the HTTP-log half of `browserCommand` reads, and that half emits on the JS listener pipeline the binary owns here — so the screenshot upload, which rides its own JWT-authenticated endpoint, stays the single effect. Also honour an explicit denial: `allow_screenshots` is an optional *string* on the wire, so a denial arrives as `'false'`, which `Boolean()` read as permission granted. Same defect class `shouldProcessEventForTesthub` already guards against with `isTrue`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`.github/PULL_REQUEST_TEMPLATE.md` states the changeset is generated from the PR's Release section as `.changeset/pr-<number>.md`, so a hand-written one is redundant here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited) Review profile: ASSERTIVE Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Follow-up to the previous commit, which subscribed the result event on the
binary path but stopped one step short — verified end to end and the
screenshot still never reached Observability. Two further links were broken:
1. `browserCommand` uploaded via `listener.onScreenshot`, whose endpoint
answers 401 to the binary's JWT (`[screenshot_upload] Failed ... status:
401`). On this path the binary owns reporting, so the entry now rides the
same LOG rail `appendTestItemLog` uses: the CLI stamps the test uuid and
forwards it over gRPC.
2. `loadLogEntries` hardcoded `logRecord.kind = KIND_LOG`, destructuring only
`{level, message, timestamp}` and discarding the producer's kind — so the
screenshot arrived labelled as a console log and no screenshots manifest
was built. It now keeps an explicit kind and falls back to KIND_LOG, which
is what the previously-unreferenced KIND_SCREENSHOT constant was for.
Console logs are unchanged: `StdLog.kind` is already 'TEST_LOG'.
Verified on a real build: two `"kind":"TEST_SCREENSHOT"` entries on the gRPC
wire, `retries[].logs == ['TEST_LOG']` on both leaves, and BStackAutomation's
`validate_o11y_screenshot` returning True for both test ids (0/2 failures).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Change map (generated deterministically from the diff)graph LR
subgraph nwdio_service["wdio-service"]
npackages_browserstack_service_tests_cli_wdioMochaTestFramework_logKind_test_ts["⚠ wdioMochaTestFramework.logKind.test.ts<br/>~69 lines"]
npackages_browserstack_service_src_insights_handler_ts["insights-handler.ts<br/>~33 lines"]
npackages_browserstack_service_tests_insights_handler_test_ts["insights-handler.test.ts<br/>~33 lines"]
npackages_browserstack_service_src_service_ts["service.ts<br/>~23 lines"]
npackages_browserstack_service_tests_service_test_ts["service.test.ts<br/>~23 lines"]
npackages_browserstack_service_src_cli_frameworks_wdioMochaTestFramework_ts["wdioMochaTestFramework.ts<br/>~6 lines"]
n_changeset_pr_190_md["pr-190.md<br/>~5 lines"]
end
↻ 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 |
…ng it Review finding 1. `getTestFramework()` can be undefined while `isRunning()` is true — the dev-env short-circuit returns true before `setupTestFramework()` has run, and that only assigns for webdriverio-mocha — so the non-null assertion could throw. `o11yClassErrorHandler` wraps every InsightsHandler method and catches async rejections, so it could not break the customer's test, but the screenshot was thrown away silently. Resolve the framework and fall back to the direct upload when it is absent, so an untracked framework still gets its one chance at reporting the screenshot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review response — finding 1 fixed in
|
|
🔴 SDK PR Review gate is red. Pending:
It turns green once the latest SDK PR Review Agent run reports GTG on the current head commit. A native reviewer approval is separately required by branch protection before merge. |
What is this about?
Screenshots taken during a Mocha run never reach Test Reporting's consolidated logs. Mocha is the only framework that takes the binary flow (
CLISupportedFrameworks = ['mocha']), and three separate links in that path were broken. Jasmine and Cucumber stay on the Direct flow and were unaffected throughout.O11Y stores screenshots as a
TEST_LOGartifact on the test run — a gzipped JSONL manifest whose lines carrykind: "TEST_SCREENSHOT". On this path no manifest was ever built.1. The producer never ran.
InsightsHandler.browserCommandis the only producer ofTEST_SCREENSHOTentries, and both of its call sites inservice.tssat insideif (!BrowserstackCLI.getInstance().isRunning()), after the CLI branch's own earlyreturn. So the WebDriverresultevent it is built from was never subscribed to on the binary path.Fixed by subscribing
resultinside the CLI branch.command(beforeCommand) is deliberately left unsubscribed: it only fills the map thatbrowserCommand's HTTP-log half reads, and that half emits on the JS listener pipeline the binary owns here. Leaving it out makes the screenshot the single effect, with no second code path to maintain.2. The upload was unauthorized. With the producer running,
listener.onScreenshotPOSTs to the screenshot endpoint withAuthorization: Bearer $BROWSERSTACK_TESTHUB_JWT— and that endpoint answers 401 to the binary's JWT:On the binary path the binary owns reporting, so the entry now rides the same LOG rail
appendTestItemLogalready uses for console logs —trackEvent(TestFrameworkState.LOG, HookState.POST, { logEntry })— which stamps the test uuid and forwards over gRPC (LogEntrycarrieskind,message,timestamp).3. The rail relabelled it.
WdioMochaTestFramework.loadLogEntriesdestructured only{ level, message, timestamp }and then hardcodedlogRecord.kind = KIND_LOG, discarding the producer's kind. The screenshot therefore arrived at O11Y labelled as a console log, and still no manifest was built. It now keeps an explicit kind and falls back toKIND_LOG— which is what theKIND_SCREENSHOTconstant, defined in that same constants file and referenced nowhere in the repo until now, was for.Console logs are byte-identical:
StdLog.kindis already'TEST_LOG', the same valueKIND_LOGholds.Two things that were not wrong, checked so they don't get re-investigated: identity is already correct on this path (
setTestDataseeds_tests[id] = { uuid: KEY_TEST_UUID }, whichbrowserCommandreads), and the permission gate does open — the probe showedallow="true"in the worker. So this is not the node agent's SDK-4177 root cause, where logs were stamped with the CucumbertestCaseStartedIdinstead of the test-run uuid.One incidental hardening:
allow_screenshotsis anoptional stringon the wire, so a denial arrives as the string'false', whichBoolean()read as permission granted. Now guarded withisFalse— the same defect classshouldProcessEventForTesthubalready guards against ("RawBoolean('false')is truthy, which kept events flowing…").Related Jira task/s
SDK-4177
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)
service.ts: subscribe theresultevent on the binary path sobrowserCommand, the onlyTEST_SCREENSHOTproducer, actually runs there.commandleft unsubscribed on purpose so the HTTP-log half stays inert and the binary keeps ownership of the batch-event pipeline.insights-handler.ts: on the binary path send the screenshot throughtrackEvent(TestFrameworkState.LOG, …)instead oflistener.onScreenshot— the direct screenshot endpoint 401s on the binary's JWT.wdioMochaTestFramework.ts:loadLogEntriesno longer hardcodeskind = KIND_LOG; an entry that carries its own kind keeps it (falls back toKIND_LOG). This is what made the screenshot arrive labelled as a console log. Console logs unchanged —StdLog.kindis already'TEST_LOG'.insights-handler.ts:TESTOPS_SCREENSHOT_ENVread with an explicitisFalseguard, sinceallow_screenshotsis a protooptional stringand'false'passed a bareBoolean().Checklist
Unit — lint clean,
npm run buildsucceeds. Newtests/cli/wdioMochaTestFramework.logKind.test.ts(3/3) pins the kind behaviour in both directions.tests/insights-handler.test.ts79/79. Acrosstests/insights-handler.test.ts + tests/service.test.ts + tests/cli: 497 passed, 44 failed — all 44 pre-existing, verified by baselining the same files on cleanmain(identical failing-test sets: 40 inservice.test.ts, 4 incliUtils*). They are environmental here: a local proxy returns HTML forapi.browserstack.com, sores.json()throws in_printSessionURL.End to end, wdio_mocha automate sample on this branch, build
xkphdxm0lhatqvje9kc4dd7byqdu5pi6nxf0dxqe:kindon the gRPC wire"kind":"TEST_SCREENSHOT"×2 (one per platform)/ext/v1/builds/{uuid}/testRuns→retries[].logs[],[]['TEST_LOG'],['TEST_LOG']validate_o11y_screenshotFalsefor both test idsTruefor both —SCREENSHOT_FAILURES=0/2The matching BStackAutomation PR that re-enables the assertion is browserstack/BStackAutomation#83444.
PR Validations
Run Tests: Comment RUN_TESTS to trigger sanity tests.
🤖 Generated with Claude Code