Skip to content

Commit d986604

Browse files
kamal-kaur04claude
andcommitted
SDK-7793: stop TestHub build for accessibility-only runs
The TestHub build is launched when observability or accessibility is enabled, but printBuildLink/stopBuildUpstream only ran for observability sessions. Accessibility-only builds were never stopped, leaving the A11y build stuck in processing. Stop the build whenever a TestHub build was launched, using the TestHub JWT/UUID when observability did not launch it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a4eaeb6 commit d986604

2 files changed

Lines changed: 104 additions & 5 deletions

File tree

‎bin/testObservability/helper/helper.js‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,16 @@ const supportFileCleanup = () => {
9292

9393
exports.buildStopped = false;
9494

95+
const isValidTestHubValue = (value) => !!value && value !== "null" && value !== "undefined";
96+
97+
// A build started at TestHub for any product (e.g. accessibility with observability off)
98+
// must also be stopped at TestHub, otherwise TestHub never finalises it downstream.
99+
exports.isTestHubBuildLaunched = () => {
100+
return isValidTestHubValue(process.env.BROWSERSTACK_TESTHUB_UUID) && isValidTestHubValue(process.env.BROWSERSTACK_TESTHUB_JWT);
101+
}
102+
95103
exports.printBuildLink = async (shouldStopSession, exitCode = null) => {
96-
if(!this.isTestObservabilitySession()) return;
104+
if(!this.isTestObservabilitySession() && !this.isTestHubBuildLaunched()) return;
97105
// SDK-6211: the build-stop may be sent early (runs.js fires it at poll-resolution, before the
98106
// post-test 5s wait + artifact download + report generation, so builds_th.finished_at — which
99107
// the collector stamps at stop-event receipt — reflects the test window rather than the full CLI
@@ -677,8 +685,11 @@ exports.shouldReRunObservabilityTests = () => {
677685
}
678686

679687
exports.stopBuildUpstream = async () => {
680-
if (process.env.BS_TESTOPS_BUILD_COMPLETED === "true") {
681-
if(process.env.BS_TESTOPS_JWT == "null" || process.env.BS_TESTOPS_BUILD_HASHED_ID == "null") {
688+
const observabilityBuildLaunched = process.env.BS_TESTOPS_BUILD_COMPLETED === "true";
689+
if (observabilityBuildLaunched || exports.isTestHubBuildLaunched()) {
690+
const jwt = observabilityBuildLaunched ? process.env.BS_TESTOPS_JWT : process.env.BROWSERSTACK_TESTHUB_JWT;
691+
const buildHashedId = observabilityBuildLaunched ? process.env.BS_TESTOPS_BUILD_HASHED_ID : process.env.BROWSERSTACK_TESTHUB_UUID;
692+
if(!isValidTestHubValue(jwt) || !isValidTestHubValue(buildHashedId)) {
682693
exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO ${TEST_REPORTING_ANALYTICS} : Missing authentication token`);
683694
return {
684695
status: 'error',
@@ -692,14 +703,14 @@ exports.stopBuildUpstream = async () => {
692703
};
693704
const config = {
694705
headers: {
695-
'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`,
706+
'Authorization': `Bearer ${jwt}`,
696707
'Content-Type': 'application/json',
697708
'X-BSTACK-TESTOPS': 'true'
698709
}
699710
};
700711

701712
try {
702-
const response = await exports.nodeRequest('PUT',`api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`,data,config);
713+
const response = await exports.nodeRequest('PUT',`api/v1/builds/${buildHashedId}/stop`,data,config);
703714
if(response.data && response.data.error) {
704715
throw({message: response.data.error});
705716
} else {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
'use strict';
2+
const chai = require('chai');
3+
const expect = chai.expect;
4+
const sinon = require('sinon');
5+
6+
const helper = require('../../../../bin/testObservability/helper/helper');
7+
8+
const ENV_KEYS = [
9+
'BROWSERSTACK_TEST_OBSERVABILITY',
10+
'BS_TESTOPS_BUILD_COMPLETED',
11+
'BS_TESTOPS_JWT',
12+
'BS_TESTOPS_BUILD_HASHED_ID',
13+
'BROWSERSTACK_TESTHUB_UUID',
14+
'BROWSERSTACK_TESTHUB_JWT',
15+
];
16+
17+
describe('TestHub build stop', () => {
18+
let savedEnv, nodeRequest;
19+
20+
beforeEach(() => {
21+
savedEnv = {};
22+
ENV_KEYS.forEach((k) => { savedEnv[k] = process.env[k]; delete process.env[k]; });
23+
helper.buildStopped = false;
24+
nodeRequest = sinon.stub(helper, 'nodeRequest').resolves({ data: {} });
25+
});
26+
27+
afterEach(() => {
28+
sinon.restore();
29+
ENV_KEYS.forEach((k) => {
30+
if (savedEnv[k] === undefined) delete process.env[k]; else process.env[k] = savedEnv[k];
31+
});
32+
helper.buildStopped = false;
33+
});
34+
35+
const stopCall = () => nodeRequest.getCalls().find((c) => c.args[0] === 'PUT');
36+
37+
it('stops an accessibility-only TestHub build when observability is off', async () => {
38+
process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'false';
39+
process.env.BS_TESTOPS_BUILD_COMPLETED = 'false';
40+
process.env.BS_TESTOPS_JWT = 'null';
41+
process.env.BS_TESTOPS_BUILD_HASHED_ID = 'null';
42+
process.env.BROWSERSTACK_TESTHUB_UUID = 'th-build-uuid';
43+
process.env.BROWSERSTACK_TESTHUB_JWT = 'th-jwt';
44+
45+
await helper.printBuildLink(true);
46+
47+
const call = stopCall();
48+
expect(call, 'PUT stop request').to.exist;
49+
expect(call.args[1]).to.equal('api/v1/builds/th-build-uuid/stop');
50+
expect(call.args[3].headers.Authorization).to.equal('Bearer th-jwt');
51+
});
52+
53+
it('keeps using the observability token and build id when observability launched the build', async () => {
54+
process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'true';
55+
process.env.BS_TESTOPS_BUILD_COMPLETED = 'true';
56+
process.env.BS_TESTOPS_JWT = 'o11y-jwt';
57+
process.env.BS_TESTOPS_BUILD_HASHED_ID = 'o11y-build';
58+
process.env.BROWSERSTACK_TESTHUB_UUID = 'o11y-build';
59+
process.env.BROWSERSTACK_TESTHUB_JWT = 'o11y-jwt';
60+
61+
await helper.printBuildLink(true);
62+
63+
const call = stopCall();
64+
expect(call.args[1]).to.equal('api/v1/builds/o11y-build/stop');
65+
expect(call.args[3].headers.Authorization).to.equal('Bearer o11y-jwt');
66+
});
67+
68+
it('sends no stop when no TestHub build was launched', async () => {
69+
process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'false';
70+
process.env.BROWSERSTACK_TESTHUB_UUID = 'null';
71+
process.env.BROWSERSTACK_TESTHUB_JWT = 'null';
72+
73+
await helper.printBuildLink(true);
74+
75+
expect(stopCall()).to.be.undefined;
76+
});
77+
78+
it('sends the stop only once across repeated calls', async () => {
79+
process.env.BROWSERSTACK_TEST_OBSERVABILITY = 'false';
80+
process.env.BROWSERSTACK_TESTHUB_UUID = 'th-build-uuid';
81+
process.env.BROWSERSTACK_TESTHUB_JWT = 'th-jwt';
82+
83+
await helper.printBuildLink(true);
84+
await helper.printBuildLink(true);
85+
86+
expect(nodeRequest.getCalls().filter((c) => c.args[0] === 'PUT')).to.have.length(1);
87+
});
88+
});

0 commit comments

Comments
 (0)