Skip to content

Commit 304f9db

Browse files
anish353claude
andcommitted
fix(rerun): send project-relative spec paths to remote on re-run [SDK-7124]
BROWSERSTACK_RERUN_TESTS arrives as bare filenames ("a.ts,b.ts"). run_settings is forwarded to the BrowserStack machines verbatim, where a bare filename does not resolve against the project root, so Cypress exits having run no spec at all ("Cypress could not run any of the specs"). getNumberOfSpecFiles already resolves those entries against cypressProjectDir via matchBase globbing, but only persisted the resolved list back to run_settings.specs under turboScaleSession. Persist it for re-run sessions too, so the remote receives paths it can resolve. Scoped to re-runs via a new shared predicate isReRunSpecsSession(), which setUserSpecs now uses as well so the two call sites cannot drift. A plain --spec glob is deliberately left untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 75e23e6 commit 304f9db

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

bin/helpers/utils.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,10 +491,20 @@ exports.setNodeVersion = (bsConfig, args) => {
491491
}
492492

493493
// specs can be passed from bstack configuration file
494+
// True when the spec list for this run comes from BROWSERSTACK_RERUN_TESTS rather than
495+
// the user's config/CLI args. Shared by setUserSpecs and getNumberOfSpecFiles so the two
496+
// cannot drift apart — getNumberOfSpecFiles rewrites specs only for runs setUserSpecs
497+
// actually sourced from the re-run env.
498+
exports.isReRunSpecsSession = () => {
499+
return o11yHelpers.isBrowserstackInfra()
500+
&& o11yHelpers.isTestObservabilitySession()
501+
&& o11yHelpers.shouldReRunObservabilityTests();
502+
}
503+
494504
// specs can be passed via command line args as a string
495505
// command line args takes precedence over config
496506
exports.setUserSpecs = (bsConfig, args) => {
497-
if(o11yHelpers.isBrowserstackInfra() && o11yHelpers.isTestObservabilitySession() && o11yHelpers.shouldReRunObservabilityTests()) {
507+
if(this.isReRunSpecsSession()) {
498508
// BROWSERSTACK_RERUN_TESTS arrives comma+space separated (e.g. "a.ts, b.ts"); normalise
499509
// like the other spec sources below, else sanitizeSpecsPattern builds "{a.ts, b.ts}" whose
500510
// space-prefixed brace alternatives never match and the failed-spec filter collapses.
@@ -1232,6 +1242,16 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig, turboScaleSession
12321242
files = files.map((x) => { return x.replaceAll("\\", "/") })
12331243
// setting specs for turboScale as we don't have patched API for turboscale so we will rely on info from CLI
12341244
bsConfig.run_settings.specs = files;
1245+
} else if (this.isReRunSpecsSession() && files.length) {
1246+
// BROWSERSTACK_RERUN_TESTS arrives as bare filenames ("a.ts,b.ts"). run_settings is
1247+
// forwarded to the remote machines verbatim, and a bare filename does not resolve
1248+
// against the project root there, so Cypress exits having run no spec at all. The
1249+
// glob above already resolved them against cypressProjectDir — persist those
1250+
// project-relative paths so the remote receives something it can resolve.
1251+
bsConfig.run_settings.specs = files
1252+
.map((file) => path.relative(bsConfig.run_settings.cypressProjectDir, file).replaceAll("\\", "/"))
1253+
.join(",");
1254+
logger.debug(`Re-run specs resolved to project-relative paths: ${bsConfig.run_settings.specs}`);
12351255
}
12361256
return files;
12371257
} catch (err) {

test/unit/bin/helpers/utils.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,6 +2327,76 @@ describe('utils', () => {
23272327
});
23282328

23292329
describe('getNumberOfSpecFiles', () => {
2330+
context('when re-running observability failed tests (SDK-7124)', () => {
2331+
let rerunStubs = [];
2332+
const specPattern = `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`;
2333+
2334+
const stubGlob = () => {
2335+
const globStub = sinon.stub(glob, 'sync');
2336+
globStub.withArgs(specPattern).returns([
2337+
'cypress/e2e/FlightOffer/FO-E2E-02.ts',
2338+
'cypress/e2e/FlightOffer/FO-E2E-05.ts',
2339+
'cypress/e2e/FlightOffer/FO-E2E-09.ts',
2340+
]);
2341+
globStub.withArgs('{FO-E2E-02.ts,FO-E2E-05.ts}').returns([
2342+
'cypress/e2e/FlightOffer/FO-E2E-02.ts',
2343+
'cypress/e2e/FlightOffer/FO-E2E-05.ts',
2344+
]);
2345+
return globStub;
2346+
};
2347+
2348+
const buildConfig = (specs) => ({
2349+
run_settings: {
2350+
cypressTestSuiteType: CYPRESS_V10_AND_ABOVE_TYPE,
2351+
specs: specs,
2352+
cypressProjectDir: '/repo',
2353+
},
2354+
});
2355+
2356+
beforeEach(() => {
2357+
rerunStubs.push(sinon.stub(o11yHelpers, 'isBrowserstackInfra').returns(true));
2358+
rerunStubs.push(sinon.stub(o11yHelpers, 'isTestObservabilitySession').returns(true));
2359+
rerunStubs.push(sinon.stub(o11yHelpers, 'shouldReRunObservabilityTests').returns(true));
2360+
});
2361+
2362+
afterEach(() => {
2363+
rerunStubs.forEach((s) => s.restore());
2364+
rerunStubs = [];
2365+
if (glob.sync.restore) glob.sync.restore();
2366+
});
2367+
2368+
it('rewrites the bare rerun filenames to project-relative paths for the remote machines', () => {
2369+
stubGlob();
2370+
// shape setUserSpecs leaves behind for a rerun: bare filenames, comma separated
2371+
let bsConfig = buildConfig('FO-E2E-02.ts,FO-E2E-05.ts');
2372+
2373+
const result = utils.getNumberOfSpecFiles(bsConfig, {}, {});
2374+
2375+
expect(result.length).to.eql(2);
2376+
// run_settings is forwarded to the remote verbatim, so it must carry resolvable paths
2377+
expect(bsConfig.run_settings.specs).to.be.eq(
2378+
'cypress/e2e/FlightOffer/FO-E2E-02.ts,cypress/e2e/FlightOffer/FO-E2E-05.ts'
2379+
);
2380+
});
2381+
2382+
it('leaves specs untouched when this is not a rerun session', () => {
2383+
rerunStubs.forEach((s) => s.restore());
2384+
rerunStubs = [];
2385+
sinon.stub(o11yHelpers, 'isBrowserstackInfra').returns(false);
2386+
sinon.stub(o11yHelpers, 'isTestObservabilitySession').returns(false);
2387+
sinon.stub(o11yHelpers, 'shouldReRunObservabilityTests').returns(false);
2388+
rerunStubs.push(o11yHelpers.isBrowserstackInfra);
2389+
rerunStubs.push(o11yHelpers.isTestObservabilitySession);
2390+
rerunStubs.push(o11yHelpers.shouldReRunObservabilityTests);
2391+
stubGlob();
2392+
let bsConfig = buildConfig('FO-E2E-02.ts,FO-E2E-05.ts');
2393+
2394+
utils.getNumberOfSpecFiles(bsConfig, {}, {});
2395+
2396+
expect(bsConfig.run_settings.specs).to.be.eq('FO-E2E-02.ts,FO-E2E-05.ts');
2397+
});
2398+
});
2399+
23302400
it('should return files matching with run_settings.specs and under default folder if cypress v <= 9 and no integration/testFiles patterm provided', () => {
23312401
let globStub = sinon.stub(glob, 'sync')
23322402
globStub.withArgs('cypress/integration/foo*.js')

0 commit comments

Comments
 (0)