Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 21 additions & 24 deletions bin/accessibility-automation/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,32 +378,29 @@ exports.setAccessibilityEventListeners = (bsConfig) => {
}

const globPattern = process.cwd() + supportFilesData.supportFile;
glob(globPattern, {}, (err, files) => {
if(err) {
logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
return;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are removing this, how do we know if exception caused issue in build start?


files.forEach(file => {
try {
const fileName = path.basename(file);
if(['e2e.js', 'e2e.ts', 'component.ts', 'component.js'].includes(fileName) && !file.includes('node_modules')) {

const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});
let cypressCommandEventListener = getAccessibilityCypressCommandEventListener(path.extname(file));
if(!defaultFileContent.includes(cypressCommandEventListener)) {
let newFileContent = defaultFileContent +
'\n' +
cypressCommandEventListener +
'\n';
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
}
// Synchronous for the same reason as testObservability setEventListeners
// (SDK-7121): the caller archives the suite right after this returns, so an
// async glob callback would race the archive and ship un-instrumented specs.
const files = glob.sync(globPattern, {});
files.forEach(file => {
try {
const fileName = path.basename(file);
if(['e2e.js', 'e2e.ts', 'component.ts', 'component.js'].includes(fileName) && !file.includes('node_modules')) {

const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});
let cypressCommandEventListener = getAccessibilityCypressCommandEventListener(path.extname(file));
if(!defaultFileContent.includes(cypressCommandEventListener)) {
let newFileContent = defaultFileContent +
'\n' +
cypressCommandEventListener +
'\n';
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
}
} catch(e) {
logger.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
}
});
} catch(e) {
logger.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
}
});
} catch(e) {
logger.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
Expand Down
40 changes: 21 additions & 19 deletions bin/testObservability/helper/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,27 +301,29 @@ exports.setEventListeners = (bsConfig) => {
try {
const supportFilesData = helper.getSupportFiles(bsConfig, false);
if(!supportFilesData.supportFile) return;
glob(process.cwd() + supportFilesData.supportFile, {}, (err, files) => {
if(err) return exports.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
files.forEach(file => {
try {
if (isE2ESupportFile(file) || !files.some(f => isE2ESupportFile(f))) {
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});

let cypressCommandEventListener = getCypressCommandEventListener(file.includes('js'));
if(!defaultFileContent.includes(cypressCommandEventListener)) {
let newFileContent = defaultFileContent +
'\n' +
cypressCommandEventListener +
'\n'
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
}
// Must be synchronous: runs.js proceeds to md5 hashing and zip archiving
// immediately after this returns. An async glob callback races the archive
// (SDK-7121) — a lost race ships an un-instrumented suite, and md5 caching
// makes it sticky, so TRA receives no test events.
const files = glob.sync(process.cwd() + supportFilesData.supportFile, {});
files.forEach(file => {
try {
if (isE2ESupportFile(file) || !files.some(f => isE2ESupportFile(f))) {
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});

let cypressCommandEventListener = getCypressCommandEventListener(file.includes('js'));
if(!defaultFileContent.includes(cypressCommandEventListener)) {
let newFileContent = defaultFileContent +
'\n' +
cypressCommandEventListener +
'\n'
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
}
} catch(e) {
exports.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
}
});
} catch(e) {
exports.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
}
});
} catch(e) {
exports.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
Expand Down
80 changes: 80 additions & 0 deletions test/unit/bin/testObservability/setEventListeners.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const fs = require('fs');
const os = require('os');
const path = require('path');

const o11yHelper = require('../../../../bin/testObservability/helper/helper');
const a11yHelper = require('../../../../bin/accessibility-automation/helper');
const baseHelper = require('../../../../bin/helpers/helper');

// Regression guard for SDK-7121: the support-file instrumentation MUST land
// synchronously. runs.js calls setEventListeners(bsConfig) and then proceeds
// immediately to md5 hashing + zip archiving. When the injection was deferred to
// an async glob callback, it raced the archive — a lost race shipped an
// un-instrumented suite, and md5 caching made it sticky, so the new Automate
// dashboard (TRA) received zero test events.
describe('SDK-7121 synchronous support-file instrumentation', () => {
let tmpDir, supportPath, cwdStub, getSupportFilesStub;

const setupTmpProject = () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk7121-'));
fs.mkdirSync(path.join(tmpDir, 'cypress', 'support'), { recursive: true });
supportPath = path.join(tmpDir, 'cypress', 'support', 'e2e.js');
fs.writeFileSync(supportPath, '// user original support file\n');
cwdStub = sinon.stub(process, 'cwd').returns(tmpDir);
};

afterEach(() => {
if (cwdStub) cwdStub.restore();
if (getSupportFilesStub) getSupportFilesStub.restore();
if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true });
cwdStub = getSupportFilesStub = tmpDir = undefined;
});

describe('testObservability setEventListeners', () => {
beforeEach(() => {
setupTmpProject();
process.env.BS_TESTOPS_BUILD_COMPLETED = 'true';
// non-magic path -> glob.sync resolves the exact file
getSupportFilesStub = sinon.stub(baseHelper, 'getSupportFiles').returns({
supportFile: '/cypress/support/e2e.js',
cleanupParams: {}
});
});

it('injects the observability require synchronously before returning', () => {
o11yHelper.setEventListeners({ run_settings: {} });
// Read exactly as md5/archive would — synchronously, right after the call.
const content = fs.readFileSync(supportPath, 'utf-8');
expect(content).to.include('browserstack-cypress-cli/bin/testObservability/cypress');
});

it('does not double-inject when called twice (idempotent)', () => {
o11yHelper.setEventListeners({ run_settings: {} });
o11yHelper.setEventListeners({ run_settings: {} });
const content = fs.readFileSync(supportPath, 'utf-8');
const occurrences = content.split('browserstack-cypress-cli/bin/testObservability/cypress').length - 1;
expect(occurrences).to.equal(1);
});
});

describe('accessibility setAccessibilityEventListeners (glob-pattern branch)', () => {
beforeEach(() => {
setupTmpProject();
// magic pattern -> exercises the glob.sync branch fixed for SDK-7121
getSupportFilesStub = sinon.stub(baseHelper, 'getSupportFiles').returns({
supportFile: '/cypress/support/**/*.js',
cleanupParams: {}
});
});

it('injects the accessibility require synchronously before returning', () => {
a11yHelper.setAccessibilityEventListeners({ run_settings: {} });
const content = fs.readFileSync(supportPath, 'utf-8');
expect(content).to.include('browserstack-cypress-cli/bin/accessibility-automation/cypress');
});
});
});
Loading