Skip to content

Commit ed3a58a

Browse files
kamal-kaur04claude
andcommitted
feat(o11y): stamp testhubBuildUuid + buildProductMap on cypress build caps [SDK-7285]
Cypress is the only SDK line that stamps no TestHub capability at all: 0 of 16,629 sessions carry one across all 32 CLI versions. That leaves it with a single route into Test Reporting & Analytics -- the BTCER session_hashed_id fallback -- and that misses ~32.5% of its sessions, which are then counted against ingestion stability with no build to attach to (2,917 sessions / 0.5882pp in the measured cohort, across 76 accounts). caps() already runs after build start, so the uuid and the product map are both in hand; getProductMap() is reused rather than reimplemented, and evaluating it here reports the products still enabled after the accessibility/observability gates rather than the ones requested. Both keys are written unconditionally. The metric treats an absent key and an empty string identically, so the empty string costs nothing and preserves the distinction between a cap writer that ran with no build to name and no writer running at all -- which for cypress is currently unobservable. Verified end-to-end on production: the build-create POST carries testhubBuildUuid=xcsouiygyettrxqdxyxmi18qnkyb6alfxqnj8x4z, matching the TestHub build the run created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 8731a23 commit ed3a58a

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

bin/helpers/capabilityHelper.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const { readCypressConfigFile } = require('./readCypressConfigUtil');
44

55
const logger = require("./logger").winstonLogger,
66
Constants = require("./constants"),
7-
Utils = require("./utils");
7+
Utils = require("./utils"),
8+
testhubUtils = require("../testhub/utils");
89

910
const caps = (bsConfig, zip) => {
1011
return new Promise(function (resolve, reject) {
@@ -131,6 +132,14 @@ const caps = (bsConfig, zip) => {
131132
obj.run_settings = JSON.stringify(bsConfig.run_settings);
132133
}
133134

135+
// The only route by which a cypress session can name its TestHub build: every session
136+
// this build spawns inherits these caps. Written unconditionally so an empty uuid records
137+
// that build start ran and had nothing to name, which an absent key cannot express.
138+
obj.testhubBuildUuid = process.env.BROWSERSTACK_TESTHUB_UUID || "";
139+
obj.buildProductMap = testhubUtils.getProductMap(bsConfig);
140+
141+
logger.debug(`TestHub build uuid stamped on caps: ${obj.testhubBuildUuid || "<empty>"}`);
142+
134143
obj.cypress_cli_user_agent = Utils.getUserAgent();
135144

136145
logger.info(`Cypress CLI User Agent: ${obj.cypress_cli_user_agent}`);

test/unit/bin/helpers/capabilityHelper.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const chai = require("chai"),
55

66
const capabilityHelper = require("../../../../bin/helpers/capabilityHelper"),
77
Constants = require("../../../../bin/helpers/constants"),
8+
testhubUtils = require("../../../../bin/testhub/utils"),
89
logger = require("../../../../bin/helpers/logger").winstonLogger;
910

1011
chai.use(chaiAsPromised);
@@ -562,6 +563,70 @@ describe("capabilityHelper.js", () => {
562563
});
563564
});
564565
});
566+
567+
context("testhub build attribution", () => {
568+
const bsConfig = {
569+
auth: {
570+
username: "random",
571+
access_key: "random",
572+
},
573+
browsers: [
574+
{
575+
browser: "chrome",
576+
os: "Windows 10",
577+
versions: ["78"],
578+
},
579+
],
580+
run_settings: {},
581+
};
582+
const productMap = {
583+
observability: true,
584+
accessibility: false,
585+
percy: false,
586+
automate: true,
587+
app_automate: false,
588+
};
589+
let productMapStub;
590+
let originalTesthubUuid;
591+
592+
beforeEach(() => {
593+
originalTesthubUuid = process.env.BROWSERSTACK_TESTHUB_UUID;
594+
productMapStub = sinon.stub(testhubUtils, "getProductMap").returns(productMap);
595+
});
596+
597+
afterEach(() => {
598+
productMapStub.restore();
599+
if (originalTesthubUuid === undefined) {
600+
delete process.env.BROWSERSTACK_TESTHUB_UUID;
601+
} else {
602+
process.env.BROWSERSTACK_TESTHUB_UUID = originalTesthubUuid;
603+
}
604+
});
605+
606+
it("stamps the testhub build uuid and the product map on the caps", () => {
607+
process.env.BROWSERSTACK_TESTHUB_UUID = "some-testhub-build-uuid";
608+
return capabilityHelper
609+
.caps(bsConfig, { zip_url: "bs://<random>" })
610+
.then(function (data) {
611+
let parsed_data = JSON.parse(data);
612+
chai.assert.equal(parsed_data.testhubBuildUuid, "some-testhub-build-uuid");
613+
chai.assert.deepEqual(parsed_data.buildProductMap, productMap);
614+
sinon.assert.calledWith(productMapStub, bsConfig);
615+
});
616+
});
617+
618+
it("stamps an empty testhub build uuid when build start produced none", () => {
619+
delete process.env.BROWSERSTACK_TESTHUB_UUID;
620+
return capabilityHelper
621+
.caps(bsConfig, { zip_url: "bs://<random>" })
622+
.then(function (data) {
623+
let parsed_data = JSON.parse(data);
624+
chai.assert.equal(parsed_data.testhubBuildUuid, "");
625+
chai.assert.isTrue(Object.prototype.hasOwnProperty.call(parsed_data, "testhubBuildUuid"));
626+
chai.assert.deepEqual(parsed_data.buildProductMap, productMap);
627+
});
628+
});
629+
});
565630
});
566631

567632
describe("addCypressZipStartLocation", () => {

0 commit comments

Comments
 (0)