From afd55c48e5ad7e396dd68e31922afff6c0379050 Mon Sep 17 00:00:00 2001 From: Anish Sinha Date: Tue, 18 Aug 2026 18:37:58 +0530 Subject: [PATCH] feat(local): forward connection_settings.binaryPath to browserstack-local [SDK-7284] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `browserstack-local` accepts a `binarypath` option that skips the binary download entirely, but the CLI never forwarded one, so a customer whose network blocks the download had no way to point the run at a binary they had already placed on disk. `setLocalArgs` now passes it through. The key it forwards is deliberately all-lowercase: `browserstack-local` only special-cases `binarypath`, and any other casing falls through its `addArgs` default branch and is handed to the binary as an unknown CLI flag while the download happens anyway. Both `binaryPath` and `binarypath` are accepted in `browserstack.json` for the same reason — a silently ignored config key is the failure mode this change exists to remove. Also reorders two requires in the test file. `usageReporting` reassigns `module.exports` after its own circular `require('./utils')`, so requiring it before `utils` left `utils` holding a stale, empty exports object: nothing stubbed in the test was visible to the code under test, and `setLocalArgs` threw. That is why the existing `setLocalArgs` spec was already failing on master. Co-Authored-By: Claude Opus 5 --- bin/helpers/utils.js | 6 ++++ test/unit/bin/helpers/utils.js | 56 +++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 2cc7f133..e1a6273c 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -1001,6 +1001,12 @@ exports.setLocalArgs = (bsConfig, args) => { if (bsConfig["connection_settings"]["useCaCertificate"]) local_args['useCaCertificate'] = bsConfig["connection_settings"]["useCaCertificate"]; + // browserstack-local only recognises the all-lowercase binarypath key; any other casing is + // forwarded to the binary as an unknown CLI flag and the binary gets downloaded anyway + const localBinaryPath = bsConfig["connection_settings"]["binaryPath"] || bsConfig["connection_settings"]["binarypath"]; + if (localBinaryPath) + local_args['binarypath'] = localBinaryPath; + local_args['daemon'] = true; local_args['enable-logging-for-api'] = true local_args['source'] = `cypress:${usageReporting.cli_version_and_path(bsConfig).version}`; diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 480cb5f4..d444f16d 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -13,7 +13,6 @@ const chai = require('chai'), crypto = require('crypto'), fs = require('fs'); const getmac = require('getmac').default; -const usageReporting = require('../../../../bin/helpers/usageReporting'); const utils = require('../../../../bin/helpers/utils'), constant = require('../../../../bin/helpers/constants'), logger = require('../../../../bin/helpers/logger').winstonLogger, @@ -23,6 +22,10 @@ const utils = require('../../../../bin/helpers/utils'), syncLogger = require('../../../../bin/helpers/logger').syncCliLogger, Contants = require('../../../../bin/helpers/constants'), o11yHelpers = require('../../../../bin/testObservability/helper/helper'); +// utils must be required first: usageReporting reassigns module.exports after its own circular +// require('./utils'), so loading it first leaves utils holding a stale, empty exports object and +// nothing stubbed here is visible to the code under test. +const usageReporting = require('../../../../bin/helpers/usageReporting'); const browserstack = require('browserstack-local'); const { CYPRESS_V10_AND_ABOVE_TYPE, CYPRESS_V9_AND_OLDER_TYPE } = require('../../../../bin/helpers/constants'); const { winstonLogger, syncCliLogger } = require('../../../../bin/helpers/logger'); @@ -1440,6 +1443,57 @@ describe('utils', () => { expect(local_args['config-file']).to.be.eq(path.resolve('./local.yml')); sinon.restore(); }); + + it('forwards a configured binaryPath as the lowercase binarypath key', () => { + let bsConfig = { + auth: { access_key: 'xyz' }, + connection_settings: { + local: true, + local_identifier: 'on-demand', + binaryPath: '/tmp/BrowserStackLocal', + }, + }; + let cliVersionPathStub = sinon + .stub(usageReporting, 'cli_version_and_path') + .withArgs(bsConfig); + cliVersionPathStub.returns('abc'); + let local_args = utils.setLocalArgs(bsConfig, {}); + expect(local_args['binarypath']).to.be.eq('/tmp/BrowserStackLocal'); + expect(local_args['binaryPath']).to.be.eq(undefined); + sinon.restore(); + }); + + it('also accepts the lowercase binarypath config key', () => { + let bsConfig = { + auth: { access_key: 'xyz' }, + connection_settings: { + local: true, + local_identifier: 'on-demand', + binarypath: '/tmp/BrowserStackLocal', + }, + }; + let cliVersionPathStub = sinon + .stub(usageReporting, 'cli_version_and_path') + .withArgs(bsConfig); + cliVersionPathStub.returns('abc'); + let local_args = utils.setLocalArgs(bsConfig, {}); + expect(local_args['binarypath']).to.be.eq('/tmp/BrowserStackLocal'); + sinon.restore(); + }); + + it('omits binarypath when none is configured', () => { + let bsConfig = { + auth: { access_key: 'xyz' }, + connection_settings: { local: true, local_identifier: 'on-demand' }, + }; + let cliVersionPathStub = sinon + .stub(usageReporting, 'cli_version_and_path') + .withArgs(bsConfig); + cliVersionPathStub.returns('abc'); + let local_args = utils.setLocalArgs(bsConfig, {}); + expect(Object.keys(local_args)).to.not.include('binarypath'); + sinon.restore(); + }); }); describe('stopLocalBinary', () => {