Skip to content
Closed
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;
}

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
21 changes: 11 additions & 10 deletions bin/helpers/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ exports.getGitMetaData = () => {
if(!info.commonGitDir) {
logger.debug(`Unable to find a Git directory`);
exports.debug(`Unable to find a Git directory`);
resolve({});
return resolve({});
}
if(!info.author && exports.findGitConfig(process.cwd())) {
/* commit objects are packed */
Expand Down Expand Up @@ -196,6 +196,16 @@ exports.getHostInfo = () => {
exports.getCiInfo = () => {
var env = process.env;

// GitHub Actions — checked before the presence-only providers (Jenkins,
// Bitbucket) so leftover env vars on self-hosted runners can't shadow it
if (env.GITHUB_ACTIONS === "true" || (env.CI === "true" && env.GITHUB_RUN_ID)) {
return {
name: "GitHub Actions",
build_url: `${env.GITHUB_SERVER_URL || 'https://github.com'}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
job_name: env.GITHUB_WORKFLOW || env.GITHUB_JOB,
build_number: env.GITHUB_RUN_ID
};
}
// Jenkins
if ((typeof env.JENKINS_URL === "string" && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === "string" && env.JENKINS_HOME.length > 0)) {
return {
Expand Down Expand Up @@ -268,15 +278,6 @@ exports.getCiInfo = () => {
build_number: env.CI_JOB_ID
};
}
// GitHub Actions
if (env.GITHUB_ACTIONS === "true" || env.CI === "true" && env.GITHUB_RUN_ID) {
return {
name: "GitHub Actions",
build_url: `${env.GITHUB_SERVER_URL || 'https://github.com'}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
job_name: env.GITHUB_WORKFLOW || env.GITHUB_JOB,
build_number: env.GITHUB_RUN_ID
};
}
// Buildkite
if (env.CI === "true" && env.BUILDKITE === "true") {
return {
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
59 changes: 59 additions & 0 deletions test/unit/bin/helpers/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,63 @@ describe('helper', () => {
expect(helper.getSizeOfJsonObjectInBytes(truncatedVCSInfo)).to.be.lessThanOrEqual(64 * 1024);
});
});

describe('getCiInfo', () => {
const CI_VARS = ['CI', 'GITHUB_ACTIONS', 'GITHUB_SERVER_URL', 'GITHUB_REPOSITORY', 'GITHUB_RUN_ID', 'GITHUB_RUN_NUMBER', 'GITHUB_WORKFLOW', 'GITHUB_JOB', 'JENKINS_URL', 'JENKINS_HOME', 'BUILD_URL', 'JOB_NAME', 'BUILD_NUMBER'];
let savedEnv = {};

beforeEach(() => {
CI_VARS.forEach((k) => {
savedEnv[k] = process.env[k];
delete process.env[k];
});
});

afterEach(() => {
CI_VARS.forEach((k) => {
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
});
savedEnv = {};
});

const setGithubActionsEnv = () => {
process.env.CI = 'true';
process.env.GITHUB_ACTIONS = 'true';
process.env.GITHUB_SERVER_URL = 'https://github.com';
process.env.GITHUB_REPOSITORY = 'org/repo';
process.env.GITHUB_RUN_ID = '27412345678';
process.env.GITHUB_WORKFLOW = 'CI Workflow';
};

it('detects GitHub Actions with build_url and run-id build_number', () => {
setGithubActionsEnv();
const ciInfo = helper.getCiInfo();
expect(ciInfo.name).to.eq('GitHub Actions');
expect(ciInfo.build_url).to.eq('https://github.com/org/repo/actions/runs/27412345678');
expect(ciInfo.build_number).to.eq('27412345678');
});

it('prefers GitHub Actions over leaked Jenkins env vars on self-hosted runners', () => {
setGithubActionsEnv();
process.env.JENKINS_HOME = '/var/lib/jenkins';
const ciInfo = helper.getCiInfo();
expect(ciInfo.name).to.eq('GitHub Actions');
expect(ciInfo.build_url).to.eq('https://github.com/org/repo/actions/runs/27412345678');
});

it('detects Jenkins when no explicit CI marker is set', () => {
process.env.JENKINS_URL = 'https://ci.internal/job/x';
process.env.BUILD_URL = 'https://ci.internal/job/x/42/';
process.env.JOB_NAME = 'x';
process.env.BUILD_NUMBER = '42';
const ciInfo = helper.getCiInfo();
expect(ciInfo.name).to.eq('Jenkins');
expect(ciInfo.build_url).to.eq('https://ci.internal/job/x/42/');
});

it('returns null when no CI is detected', () => {
expect(helper.getCiInfo()).to.be.null;
});
});
});
46 changes: 46 additions & 0 deletions test/unit/bin/testObservability/setEventListeners.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'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 baseHelper = require('../../../../bin/helpers/helper');

// Regression guard for SDK-7121: the TRA support-file injection 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('testObservability setEventListeners', () => {
let tmpDir, cwdStub, getSupportFilesStub;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk7121-'));
fs.mkdirSync(path.join(tmpDir, 'cypress', 'support'), { recursive: true });
fs.writeFileSync(path.join(tmpDir, 'cypress', 'support', 'e2e.js'), '// user original support file\n');

cwdStub = sinon.stub(process, 'cwd').returns(tmpDir);
getSupportFilesStub = sinon.stub(baseHelper, 'getSupportFiles').returns({
supportFile: '/cypress/support/e2e.js',
cleanupParams: {}
});
});

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

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(path.join(tmpDir, 'cypress', 'support', 'e2e.js'), 'utf-8');
expect(content).to.include("browserstack-cypress-cli/bin/testObservability/cypress");
});
});
Loading