Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@nightwatch/browserstack

Package Overview
Dependencies
Maintainers
6
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nightwatch/browserstack - npm Package Compare versions

Comparing version 0.1.3 to 0.2.0

src/testObservability.js

79

nightwatch/globals.js
const LocalTunnel = require('../src/local-tunnel');
const TestObservability = require('../src/testObservability');
const {CUSTOM_REPORTER_CALLBACK_TIMEOUT} = require('../src/utils/constants');
const CrashReporter = require('../src/utils/crashReporter');
const helper = require('../src/utils/helper');
const Logger = require('../src/utils/logger');
const localTunnel = new LocalTunnel();
const testObservability = new TestObservability();
const nightwatchRerun = process.env.NIGHTWATCH_RERUN_FAILED;
const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_REPORT_FILE;
module.exports = {
reporter: async function(results, done) {
if (!helper.isTestObservabilitySession()) {
done(results);
return;
}
try {
const modulesWithEnv = results['modulesWithEnv'];
const promises = [];
for (const testSetting in modulesWithEnv) {
for (const testFile in modulesWithEnv[testSetting]) {
const completedSections = modulesWithEnv[testSetting][testFile].completed;
for (const completedSection in completedSections) {
if (completedSections[completedSection]) {
delete completedSections[completedSection].steps;
delete completedSections[completedSection].testcases;
}
}
promises.push(testObservability.processTestReportFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile]))));
}
}
await Promise.all(promises);
done();
} catch (error) {
CrashReporter.uploadCrashReport(error.message, error.stack);
Logger.error(`Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`);
}
done(results);
},
onEvent({eventName, hook_type, ...args}) {
if (typeof browser !== 'undefined' && eventName === 'TestRunStarted') {
browser.execute(`browserstack_executor: {"action": "annotate", "arguments": {"type":"Annotation","data":"ObservabilitySync:${Date.now()}","level": "debug"}}`);
}
},
async before(settings) {

@@ -25,2 +72,19 @@ localTunnel.configure(settings);

}
try {
testObservability.configure(settings);
if (helper.isTestObservabilitySession()) {
settings.globals['customReporterCallbackTimeout'] = CUSTOM_REPORTER_CALLBACK_TIMEOUT;
if (testObservability._user && testObservability._key) {
await testObservability.launchTestSession();
}
if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS && process.env.BROWSERSTACK_RERUN_TESTS!=='null') {
const specs = process.env.BROWSERSTACK_RERUN_TESTS.split(',');
await helper.handleNightwatchRerun(specs);
}
}
} catch (error) {
Logger.error(`Could not configure or launch test observability - ${error}`);
}
},

@@ -30,2 +94,17 @@

localTunnel.stop();
if (helper.isTestObservabilitySession()) {
try {
await testObservability.stopBuildUpstream();
if (process.env.BS_TESTOPS_BUILD_HASHED_ID) {
Logger.info(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`);
}
} catch (error) {
Logger.error(`Something went wrong in stopping build session for test observability - ${error}`);
}
process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun;
process.env.NIGHTWATCH_RERUN_REPORT_FILE = nightwatchRerunFile;
if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) {
await helper.deleteRerunFile();
}
}
},

@@ -32,0 +111,0 @@

9

package.json
{
"name": "@nightwatch/browserstack",
"version": "0.1.3",
"version": "0.2.0",
"description": "Nightwatch plugin for integration with browserstack.",

@@ -41,3 +41,8 @@ "main": "index.js",

"dependencies": {
"browserstack-local": "^1.5.1"
"browserstack-local": "^1.5.1",
"git-last-commit": "^1.0.1",
"git-repo-info": "^2.1.1",
"gitconfiglocal": "^2.1.0",
"request": "^2.88.2",
"strip-ansi": "^6.0.1"
},

@@ -44,0 +49,0 @@ "devDependencies": {

@@ -9,3 +9,3 @@ # @nightwatch/browserstack

Official Nightwatch plugin for integration with the BrowserStack Local tunnel.
Official Nightwatch plugin for integration with the BrowserStack.

@@ -29,2 +29,9 @@ ```

// other browserstack local options
},
test_observability: {
enabled: true, // set true for enabling browserstack test observability
user: '${BROWSERSTACK_USERNAME}',
key: '${BROWSERSTACK_ACCESS_KEY}',
projectName: "BrowserStack Samples",
buildName: "browserstack build"
}

@@ -31,0 +38,0 @@ },

const os = require('os');
const fs = require('fs');
const fsPromises = fs.promises;
const path = require('path');
const {promisify} = require('util');
const gitRepoInfo = require('git-repo-info');
const gitconfig = require('gitconfiglocal');
const pGitconfig = promisify(gitconfig);
const gitLastCommit = require('git-last-commit');
const {makeRequest} = require('./requestHelper');
const {RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS} = require('./constants');
const requestQueueHandler = require('./requestQueueHandler');
const Logger = require('./logger');
exports.generateLocalIdentifier = () => {

@@ -23,2 +36,357 @@ const formattedDate = new Intl.DateTimeFormat('en-GB', {

exports.isTestObservabilitySession = () => {
return process.env.BROWSERSTACK_TEST_OBSERVABILITY === 'true';
};
exports.getObservabilityUser = (config, bstackOptions={}) => {
return process.env.BROWSERSTACK_USERNAME || config.user || bstackOptions.userName;
};
exports.getObservabilityKey = (config, bstackOptions={}) => {
return process.env.BROWSERSTACK_ACCESS_KEY || config.key || bstackOptions.accessKey;
};
exports.getObservabilityProject = (options, bstackOptions={}) => {
if (options.test_observability && options.test_observability.projectName) {
return options.test_observability.projectName;
} else if (bstackOptions.projectName) {
return bstackOptions.projectName;
}
return '';
};
exports.getObservabilityBuild = (options, bstackOptions={}) => {
if (options.test_observability && options.test_observability.buildName) {
return options.test_observability.buildName;
} else if (bstackOptions.buildName) {
return bstackOptions.buildName;
}
return path.basename(path.resolve(process.cwd()));
};
exports.getObservabilityBuildTags = (options, bstackOptions={}) => {
if (options.test_observability && options.test_observability.buildTag) {
return options.test_observability.buildTag;
} else if (bstackOptions.buildTag) {
return bstackOptions.buildTag;
}
return [];
};
exports.getFrameworkName = (testRunner) => {
if (testRunner && testRunner.type) {
return `nightwatch-${testRunner.type}`;
}
return 'nightwatch-default';
};
exports.getCIVendor = () => {
var env = process.env;
// Jenkins
if ((typeof env.JENKINS_URL === 'string' && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === 'string' && env.JENKINS_HOME.length > 0)) {
return 'Jenkins';
}
// CircleCI
if (env.CI === 'true' && env.CIRCLECI === 'true') {
return 'CircleCI';
}
// Travis CI
if (env.CI === 'true' && env.TRAVIS === 'true') {
return 'TravisCI';
}
// Codeship
if (env.CI === 'true' && env.CI_NAME === 'codeship') {
return 'Codeship';
}
// Bitbucket
if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) {
return 'Bitbucket';
}
// Drone
if (env.CI === 'true' && env.DRONE === 'true') {
return 'Drone';
}
// Semaphore
if (env.CI === 'true' && env.SEMAPHORE === 'true') {
return 'Semaphore';
}
// GitLab
if (env.CI === 'true' && env.GITLAB_CI === 'true') {
return 'GitLab';
}
// Buildkite
if (env.CI === 'true' && env.BUILDKITE === 'true') {
return 'Buildkite';
}
// Visual Studio Team Services
if (env.TF_BUILD === 'True') {
return 'Visual Studio Team Services';
}
};
exports.getCiInfo = () => {
var env = process.env;
const ciVendor = this.getCIVendor();
switch (ciVendor) {
case 'Jenkins':
return {
name: 'Jenkins',
build_url: env.BUILD_URL,
job_name: env.JOB_NAME,
build_number: env.BUILD_NUMBER
};
case 'CircleCI':
return {
name: 'CircleCI',
build_url: env.CIRCLE_BUILD_URL,
job_name: env.CIRCLE_JOB,
build_number: env.CIRCLE_BUILD_NUM
};
case 'TravisCI':
return {
name: 'Travis CI',
build_url: env.TRAVIS_BUILD_WEB_URL,
job_name: env.TRAVIS_JOB_NAME,
build_number: env.TRAVIS_BUILD_NUMBER
};
case 'Codeship':
return {
name: 'Codeship',
build_url: null,
job_name: null,
build_number: null
};
case 'Bitbucket':
return {
name: 'Bitbucket',
build_url: env.BITBUCKET_GIT_HTTP_ORIGIN,
job_name: null,
build_number: env.BITBUCKET_BUILD_NUMBER
};
case 'Drone':
return {
name: 'Drone',
build_url: env.DRONE_BUILD_LINK,
job_name: null,
build_number: env.DRONE_BUILD_NUMBER
};
case 'Semaphore':
return {
name: 'Semaphore',
build_url: env.SEMAPHORE_ORGANIZATION_URL,
job_name: env.SEMAPHORE_JOB_NAME,
build_number: env.SEMAPHORE_JOB_ID
};
case 'GitLab':
return {
name: 'GitLab',
build_url: env.CI_JOB_URL,
job_name: env.CI_JOB_NAME,
build_number: env.CI_JOB_ID
};
case 'Buildkite':
return {
name: 'Buildkite',
build_url: env.BUILDKITE_BUILD_URL,
job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME,
build_number: env.BUILDKITE_BUILD_NUMBER
};
case 'Visual Studio Team Services':
return {
name: 'Visual Studio Team Services',
build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`,
job_name: env.SYSTEM_DEFINITIONID,
build_number: env.BUILD_BUILDID
};
default:
return null;
}
};
const findGitConfig = async (filePath) => {
if (filePath == null || filePath === '' || filePath === '/') {
return null;
}
try {
await fsPromises.stat(filePath + '/.git/config');
return filePath;
} catch (e) {
const parentFilePath = filePath.split('/');
parentFilePath.pop();
return await findGitConfig(parentFilePath.join('/'));
}
};
exports.getGitMetaData = () => {
return new Promise((resolve, reject) => {
(async () => {
try {
var info = gitRepoInfo();
if (!info.commonGitDir) {
Logger.info('Unable to find a Git directory');
resolve({});
}
if (!info.author && await findGitConfig(process.cwd())) {
/* commit objects are packed */
gitLastCommit.getLastCommit(async (err, commit) => {
info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`;
info['authorDate'] = info['authorDate'] || commit['authoredOn'];
info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`;
info['committerDate'] = info['committerDate'] || commit['committedOn'];
info['commitMessage'] = info['commitMessage'] || commit['subject'];
const {remote} = await pGitconfig(info.commonGitDir);
const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']}));
resolve({
'name': 'git',
'sha': info['sha'],
'short_sha': info['abbreviatedSha'],
'branch': info['branch'],
'tag': info['tag'],
'committer': info['committer'],
'committer_date': info['committerDate'],
'author': info['author'],
'author_date': info['authorDate'],
'commit_message': info['commitMessage'],
'root': info['root'],
'common_git_dir': info['commonGitDir'],
'worktree_git_dir': info['worktreeGitDir'],
'last_tag': info['lastTag'],
'commits_since_last_tag': info['commitsSinceLastTag'],
'remotes': remotes
});
}, {dst: await findGitConfig(process.cwd())});
} else {
const {remote} = await pGitconfig(info.commonGitDir);
const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']}));
resolve({
'name': 'git',
'sha': info['sha'],
'short_sha': info['abbreviatedSha'],
'branch': info['branch'],
'tag': info['tag'],
'committer': info['committer'],
'committer_date': info['committerDate'],
'author': info['author'],
'author_date': info['authorDate'],
'commit_message': info['commitMessage'],
'root': info['root'],
'common_git_dir': info['commonGitDir'],
'worktree_git_dir': info['worktreeGitDir'],
'last_tag': info['lastTag'],
'commits_since_last_tag': info['commitsSinceLastTag'],
'remotes': remotes
});
}
} catch (err) {
Logger.error(`Exception in populating Git metadata with error : ${err}`);
resolve({});
}
})();
});
};
exports.requireModule = (module) => {
Logger.info(`Getting ${module} from ${process.cwd()}`);
const local_path = path.join(process.cwd(), 'node_modules', module);
return require(local_path);
};
exports.getAgentVersion = () => {
const _path = path.join(__dirname, '../../package.json');
if (fs.existsSync(_path)) {return require(_path).version}
};
const packages = {};
exports.getPackageVersion = (package_) => {
if (packages[package_]) {return packages[package_]}
return packages[package_] = this.requireModule(`${package_}/package.json`).version;
};
exports.uploadEventData = async (eventData) => {
const log_tag = {
['TestRunStarted']: 'Test_Start_Upload',
['TestRunFinished']: 'Test_End_Upload',
['TestRunSkipped']: 'Test_Skipped_Upload',
['LogCreated']: 'Log_Upload',
['HookRunStarted']: 'Hook_Start_Upload',
['HookRunFinished']: 'Hook_End_Upload'
}[eventData.event_type];
if (process.env.BS_TESTOPS_JWT && process.env.BS_TESTOPS_JWT !== 'null') {
requestQueueHandler.pending_test_uploads += 1;
}
if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') {
if (process.env.BS_TESTOPS_JWT === 'null') {
Logger.info(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`);
requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads-1);
return {
status: 'error',
message: 'Token/buildID is undefined, build creation might have failed'
};
}
let data = eventData;
let event_api_url = 'api/v1/event';
requestQueueHandler.start();
const {
shouldProceed,
proceedWithData,
proceedWithUrl
} = requestQueueHandler.add(eventData);
if (!shouldProceed) {
return;
} else if (proceedWithData) {
data = proceedWithData;
event_api_url = proceedWithUrl;
}
const config = {
headers: {
'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`,
'Content-Type': 'application/json',
'X-BSTACK-TESTOPS': 'true'
}
};
try {
const response = await makeRequest('POST', event_api_url, data, config);
if (response.data.error) {
throw ({message: response.data.error});
} else {
requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads - (event_api_url === 'api/v1/event' ? 1 : data.length));
return {
status: 'success',
message: ''
};
}
} catch (error) {
if (error.response) {
Logger.error(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`);
} else {
Logger.error(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`);
}
requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads - (event_api_url === 'api/v1/event' ? 1 : data.length));
return {
status: 'error',
message: error.message || (error.response ? `${error.response.status}:${error.response.statusText}` : error)
};
}
}
};
exports.getAccessKey = (settings) => {

@@ -40,1 +408,66 @@ let accessKey = null;

};
exports.getCloudProvider = (hostname) => {
if (hostname.includes('browserstack')) {
return 'browserstack';
}
return 'unknown_grid';
};
exports.getIntegrationsObject = (capabilities, sessionId) => {
return {
capabilities: capabilities,
session_id: sessionId,
browser: capabilities.browserName,
browser_version: capabilities.browserVersion,
platform: capabilities.platformName
};
};
exports.handleNightwatchRerun = async (specs) => {
const modules = {};
specs.forEach(spec => {
modules[spec] = {
modulePath: spec,
status: 'fail'
};
});
const data = {
modules: modules
};
try {
await fsPromises.writeFile(RERUN_FILE, JSON.stringify(data));
process.env.NIGHTWATCH_RERUN_FAILED = true;
process.env.NIGHTWATCH_RERUN_REPORT_FILE = path.resolve(RERUN_FILE);
} catch (error) {
Logger.error(error);
}
};
exports.deleteRerunFile = async () => {
try {
await fsPromises.unlink(path.resolve(RERUN_FILE));
} catch (err) {
Logger.error(err);
}
};
const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
exports.uploadPending = async (
waitTimeout = DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS,
waitInterval = DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS
) => {
if (requestQueueHandler.pending_test_uploads <= 0 || waitTimeout <= 0) {
return;
}
await sleep(waitInterval);
return this.uploadPending(waitTimeout - waitInterval);
};
exports.shutDownRequestHandler = async () => {
await requestQueueHandler.shutdown();
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc