diff --git a/bin/accessibility-automation/constants.js b/bin/accessibility-automation/constants.js deleted file mode 100644 index 496667a9..00000000 --- a/bin/accessibility-automation/constants.js +++ /dev/null @@ -1 +0,0 @@ -exports.API_URL = 'https://accessibility.browserstack.com/api'; diff --git a/bin/accessibility-automation/cypress/index.js b/bin/accessibility-automation/cypress/index.js deleted file mode 100644 index 6bc8aa3b..00000000 --- a/bin/accessibility-automation/cypress/index.js +++ /dev/null @@ -1,383 +0,0 @@ -/* Event listeners + custom commands for Cypress */ - -const browserStackLog = (message) => { - if (!Cypress.env('BROWSERSTACK_LOGS')) return; - cy.task('browserstack_log', message); -} - -const commandsToWrap = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin']; - -const performScan = (win, payloadToSend) => - new Promise(async (resolve, reject) => { - const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); - if (!isHttpOrHttps) { - resolve(); - } - - function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); - } - - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise(async (resolve, reject) => { - let count = 0; - const intervalID = setInterval(async () => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); - } - - function startScan() { - function onScanComplete() { - win.removeEventListener("A11Y_SCAN_FINISHED", onScanComplete); - resolve(); - } - - win.addEventListener("A11Y_SCAN_FINISHED", onScanComplete); - const e = new CustomEvent("A11Y_SCAN", { detail: payloadToSend }); - win.dispatchEvent(e); - } - - if (findAccessibilityAutomationElement()) { - startScan(); - } else { - waitForScannerReadiness() - .then(startScan) - .catch(async (err) => { - resolve("Scanner is not ready on the page after multiple retries. performscan"); - }); - } - }) - -const getAccessibilityResultsSummary = (win) => - new Promise((resolve) => { - const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); - if (!isHttpOrHttps) { - resolve(); - } - - function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); - } - - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(() => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); - } - - function getSummary() { - function onReceiveSummary(event) { - - win.removeEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); - resolve(event.detail); - } - - win.addEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); - const e = new CustomEvent("A11Y_GET_RESULTS_SUMMARY"); - win.dispatchEvent(e); - } - - if (findAccessibilityAutomationElement()) { - getSummary(); - } else { - waitForScannerReadiness() - .then(getSummary) - .catch((err) => { - resolve(); - }); - } - }) - -const getAccessibilityResults = (win) => - new Promise((resolve) => { - const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); - if (!isHttpOrHttps) { - resolve(); - } - - function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); - } - - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(() => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); - } - - function getResults() { - function onReceivedResult(event) { - win.removeEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); - resolve(event.detail); - } - - win.addEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); - const e = new CustomEvent("A11Y_GET_RESULTS"); - win.dispatchEvent(e); - } - - if (findAccessibilityAutomationElement()) { - getResults(); - } else { - waitForScannerReadiness() - .then(getResults) - .catch((err) => { - resolve(); - }); - } - }); - -const saveTestResults = (win, payloadToSend) => - new Promise( (resolve, reject) => { - try { - const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); - if (!isHttpOrHttps) { - resolve("Unable to save accessibility results, Invalid URL."); - } - - function findAccessibilityAutomationElement() { - return win.document.querySelector("#accessibility-automation-element"); - } - - function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { - return new Promise((resolve, reject) => { - let count = 0; - const intervalID = setInterval(async () => { - if (count > retryCount) { - clearInterval(intervalID); - reject( - new Error( - "Accessibility Automation Scanner is not ready on the page." - ) - ); - } else if (findAccessibilityAutomationElement()) { - clearInterval(intervalID); - resolve("Scanner set"); - } else { - count += 1; - } - }, retryInterval); - }); - } - - function saveResults() { - function onResultsSaved(event) { - resolve(); - } - win.addEventListener("A11Y_RESULTS_SAVED", onResultsSaved); - const e = new CustomEvent("A11Y_SAVE_RESULTS", { - detail: payloadToSend, - }); - win.dispatchEvent(e); - } - - if (findAccessibilityAutomationElement()) { - saveResults(); - } else { - waitForScannerReadiness() - .then(saveResults) - .catch(async (err) => { - resolve("Scanner is not ready on the page after multiple retries. after run"); - }); - } - - } catch(er) { - resolve() - } - - }) - -const shouldScanForAccessibility = (attributes) => { - if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return false; - - const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH"); - const isHeaded = Cypress.browser.isHeaded; - - if (!isHeaded || (extensionPath === undefined)) return false; - - let shouldScanTestForAccessibility = true; - - if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY") || Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { - try { - let includeTagArray = []; - let excludeTagArray = []; - if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY")) { - includeTagArray = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") - } - if (Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) { - excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";") - } - - const fullTestName = attributes.title; - const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude)); - const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include)); - shouldScanTestForAccessibility = !excluded && included; - } catch (error) { - browserStackLog("Error while validating test case for accessibility before scanning. Error : ", error); - } - } - - return shouldScanTestForAccessibility; -} - -Cypress.on('command:start', async (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'window' || command.attributes.name == 'then' || command.attributes.name == 'wrap') { - return; - } - - if (!commandsToWrap.includes(command.attributes.name)) return; - - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - - let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) return; - - cy.window().then((win) => { - browserStackLog('Performing scan form command ' + command.attributes.name); - cy.wrap(performScan(win, {method: command.attributes.name}), {timeout: 30000}); - }) -}) - -afterEach(() => { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest; - cy.window().then(async (win) => { - let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) return cy.wrap({}); - - cy.wrap(performScan(win), {timeout: 30000}).then(() => { - try { - let os_data; - if (Cypress.env("OS")) { - os_data = Cypress.env("OS"); - } else { - os_data = Cypress.platform === 'linux' ? 'mac' : "win" - } - let filePath = ''; - if (attributes.invocationDetails !== undefined && attributes.invocationDetails.relativeFile !== undefined) { - filePath = attributes.invocationDetails.relativeFile; - } - const payloadToSend = { - "saveResults": shouldScanTestForAccessibility, - "testDetails": { - "name": attributes.title, - "testRunId": '5058', // variable not consumed, shouldn't matter what we send - "filePath": filePath, - "scopeList": [ - filePath, - attributes.title - ] - }, - "platform": { - "os_name": os_data, - "os_version": Cypress.env("OS_VERSION"), - "browser_name": Cypress.browser.name, - "browser_version": Cypress.browser.version - } - }; - browserStackLog(`Saving accessibility test results`); - cy.wrap(saveTestResults(win, payloadToSend), {timeout: 30000}).then(() => { - browserStackLog(`Saved accessibility test results`); - }) - - } catch (er) { - } - }) - }); -}) - -Cypress.Commands.add('performScan', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot perform scan.`); - return cy.wrap({}); - } - cy.window().then(async (win) => { - browserStackLog(`Performing accessibility scan`); - await performScan(win); - }); - } catch {} -}) - -Cypress.Commands.add('getAccessibilityResultsSummary', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results summary.`); - return cy.wrap({}); - } - cy.window().then(async (win) => { - await performScan(win); - browserStackLog('Getting accessibility results summary'); - return await getAccessibilityResultsSummary(win); - }); - } catch {} - -}); - -Cypress.Commands.add('getAccessibilityResults', () => { - try { - const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; - const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); - if (!shouldScanTestForAccessibility) { - browserStackLog(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`); - return cy.wrap({}); - } - -/* browserstack_accessibility_automation_script */ - - cy.window().then(async (win) => { - await performScan(win); - browserStackLog('Getting accessibility results'); - return await getAccessibilityResults(win); - }); - - } catch {} - -}); diff --git a/bin/accessibility-automation/helper.js b/bin/accessibility-automation/helper.js deleted file mode 100644 index 8a08b674..00000000 --- a/bin/accessibility-automation/helper.js +++ /dev/null @@ -1,233 +0,0 @@ -const logger = require("../helpers/logger").winstonLogger; -const { API_URL } = require('./constants'); -const utils = require('../helpers/utils'); -const fs = require('fs'); -const path = require('path'); -const request = require('request'); -const os = require('os'); -const glob = require('glob'); -const helper = require('../helpers/helper'); -const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../helpers/constants'); -const supportFileContentMap = {} - -exports.checkAccessibilityPlatform = (user_config) => { - let accessibility = false; - try { - user_config.browsers.forEach(browser => { - if (browser.accessibility) { - accessibility = true; - } - }) - } catch {} - - return accessibility; -} - -exports.setAccessibilityCypressCapabilities = async (user_config, accessibilityResponse) => { - if (utils.isUndefined(user_config.run_settings.accessibilityOptions)) { - user_config.run_settings.accessibilityOptions = {} - } - user_config.run_settings.accessibilityOptions["authToken"] = accessibilityResponse.data.accessibilityToken; - user_config.run_settings.accessibilityOptions["auth"] = accessibilityResponse.data.accessibilityToken; - user_config.run_settings.accessibilityOptions["scannerVersion"] = accessibilityResponse.data.scannerVersion; - user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_AUTH=${accessibilityResponse.data.accessibilityToken}`) - user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_SCANNERVERSION=${accessibilityResponse.data.scannerVersion}`) -} - -exports.isAccessibilitySupportedCypressVersion = (cypress_config_filename) => { - const extension = cypress_config_filename.split('.').pop(); - return CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension); -} - -exports.createAccessibilityTestRun = async (user_config, framework) => { - - try { - if (!this.isAccessibilitySupportedCypressVersion(user_config.run_settings.cypress_config_file) ){ - logger.warn(`Accessibility Testing is not supported on Cypress version 9 and below.`) - process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false'; - user_config.run_settings.accessibility = false; - return; - } - const userName = user_config["auth"]["username"]; - const accessKey = user_config["auth"]["access_key"]; - let settings = utils.isUndefined(user_config.run_settings.accessibilityOptions) ? {} : user_config.run_settings.accessibilityOptions - - const { - buildName, - projectName, - buildDescription - } = helper.getBuildDetails(user_config); - - const data = { - 'projectName': projectName, - 'buildName': buildName, - 'startTime': (new Date()).toISOString(), - 'description': buildDescription, - 'source': { - frameworkName: "Cypress", - frameworkVersion: helper.getPackageVersion('cypress', user_config), - sdkVersion: helper.getAgentVersion(), - language: 'javascript', - testFramework: 'cypress', - testFrameworkVersion: helper.getPackageVersion('cypress', user_config) - }, - 'settings': settings, - 'versionControl': await helper.getGitMetaData(), - 'ciInfo': helper.getCiInfo(), - 'hostInfo': { - hostname: os.hostname(), - platform: os.platform(), - type: os.type(), - version: os.version(), - arch: os.arch() - }, - 'browserstackAutomation': process.env.BROWSERSTACK_AUTOMATION === 'true' - }; - - const config = { - auth: { - user: userName, - pass: accessKey - }, - headers: { - 'Content-Type': 'application/json' - } - }; - - const response = await nodeRequest( - 'POST', 'v2/test_runs', data, config, API_URL - ); - if(!utils.isUndefined(response.data)) { - process.env.BS_A11Y_JWT = response.data.data.accessibilityToken; - process.env.BS_A11Y_TEST_RUN_ID = response.data.data.id; - } - if (process.env.BS_A11Y_JWT) { - process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'true'; - } - logger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.data.id}`); - - this.setAccessibilityCypressCapabilities(user_config, response.data); - helper.setBrowserstackCypressCliDependency(user_config); - - } catch (error) { - if (error.response) { - logger.error( - `Exception while creating test run for BrowserStack Accessibility Automation: ${ - error.response.status - } ${error.response.statusText} ${JSON.stringify(error.response.data)}` - ); - } else { - if(error.message === 'Invalid configuration passed.') { - logger.error( - `Exception while creating test run for BrowserStack Accessibility Automation: ${ - error.message || error.stack - }` - ); - for(const errorkey of error.errors){ - logger.error(errorkey.message); - } - - } else { - logger.error( - `Exception while creating test run for BrowserStack Accessibility Automation: ${ - error.message || error.stack - }` - ); - } - // since create accessibility session failed - process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false'; - user_config.run_settings.accessibility = false; - } - } -} - -const nodeRequest = (type, url, data, config) => { - return new Promise(async (resolve, reject) => { - const options = {...config,...{ - method: type, - url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - }}; - - request(options, function callback(error, response, body) { - if(error) { - logger.info("error in nodeRequest", error); - reject(error); - } else if(!(response.statusCode == 201 || response.statusCode == 200)) { - logger.info("response.statusCode in nodeRequest", response.statusCode); - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); - } else { - try { - if(typeof(body) !== 'object') body = JSON.parse(body); - } catch(e) { - if(!url.includes('/stop')) { - reject('Not a JSON response from BrowserStack Server'); - } - } - resolve({ - data: body - }); - } - }); - }); -} - -exports.supportFileCleanup = () => { - logger.debug("Cleaning up support file changes added for accessibility.") - Object.keys(supportFileContentMap).forEach(file => { - try { - if(typeof supportFileContentMap[file] === 'object') { - let fileOrDirpath = file; - if(supportFileContentMap[file].deleteSupportDir) { - fileOrDirpath = path.join(process.cwd(), 'cypress', 'support'); - } - helper.deleteSupportFileOrDir(fileOrDirpath); - } else { - fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'}); - } - } catch(e) { - logger.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e); - } - }); -} - -const getAccessibilityCypressCommandEventListener = (extName) => { - return extName == 'js' ? ( - `require('browserstack-cypress-cli/bin/accessibility-automation/cypress');` - ) : ( - `import 'browserstack-cypress-cli/bin/accessibility-automation/cypress'` - ) -} - -exports.setAccessibilityEventListeners = (bsConfig) => { - try { - // Searching form command.js recursively - const supportFilesData = helper.getSupportFiles(bsConfig, true); - if(!supportFilesData.supportFile) return; - glob(process.cwd() + supportFilesData.supportFile, {}, (err, files) => { - if(err) return logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files'); - files.forEach(file => { - try { - if(!file.includes('commands.js') && !file.includes('commands.ts')) { - 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 parse support files to set event listeners with error ${e}`, true, e); - } -} diff --git a/bin/accessibility-automation/plugin/index.js b/bin/accessibility-automation/plugin/index.js deleted file mode 100644 index dd47f208..00000000 --- a/bin/accessibility-automation/plugin/index.js +++ /dev/null @@ -1,53 +0,0 @@ - -const path = require("node:path"); - -const browserstackAccessibility = (on, config) => { - let browser_validation = true; - if (process.env.BROWSERSTACK_ACCESSIBILITY_DEBUG === 'true') { - config.env.BROWSERSTACK_LOGS = 'true'; - process.env.BROWSERSTACK_LOGS = 'true'; - } - on('task', { - browserstack_log(message) { - console.log(message) - - return null - }, - }) - on('before:browser:launch', (browser = {}, launchOptions) => { - try { - if (process.env.ACCESSIBILITY_EXTENSION_PATH !== undefined) { - if (browser.name !== 'chrome') { - console.log(`Accessibility Automation will run only on Chrome browsers.`); - browser_validation = false; - } - if (browser.name === 'chrome' && browser.majorVersion <= 94) { - console.log(`Accessibility Automation will run only on Chrome browser version greater than 94.`); - browser_validation = false; - } - if (browser.isHeadless === true) { - console.log(`Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.`); - browser_validation = false; - } - if (browser_validation) { - const ally_path = path.dirname(process.env.ACCESSIBILITY_EXTENSION_PATH) - launchOptions.extensions.push(ally_path); - return launchOptions - } - } - } catch(err) {} - - }) - config.env.ACCESSIBILITY_EXTENSION_PATH = process.env.ACCESSIBILITY_EXTENSION_PATH - config.env.OS_VERSION = process.env.OS_VERSION - config.env.OS = process.env.OS - - config.env.IS_ACCESSIBILITY_EXTENSION_LOADED = browser_validation.toString() - - config.env.INCLUDE_TAGS_FOR_ACCESSIBILITY = process.env.ACCESSIBILITY_INCLUDETAGSINTESTINGSCOPE - config.env.EXCLUDE_TAGS_FOR_ACCESSIBILITY = process.env.ACCESSIBILITY_EXCLUDETAGSINTESTINGSCOPE - - return config; -} - -module.exports = browserstackAccessibility; diff --git a/bin/commands/generateReport.js b/bin/commands/generateReport.js index 81c6fab8..7bb11cb7 100644 --- a/bin/commands/generateReport.js +++ b/bin/commands/generateReport.js @@ -31,7 +31,6 @@ module.exports = function generateReport(args, rawArgs) { let messageType = Constants.messageTypes.INFO; let errorCode = null; let buildId = args._[1]; - reportGenerator(bsConfig, buildId, args, rawArgs, buildReportData); utils.sendUsageReport(bsConfig, args, 'generate-report called', messageType, errorCode, buildReportData, rawArgs); }).catch((err) => { diff --git a/bin/commands/info.js b/bin/commands/info.js index 715b9647..e8e9344a 100644 --- a/bin/commands/info.js +++ b/bin/commands/info.js @@ -1,12 +1,13 @@ 'use strict'; -const request = require('request'); - +const axios = require('axios').default; const config = require("../helpers/config"), logger = require("../helpers/logger").winstonLogger, Constants = require("../helpers/constants"), utils = require("../helpers/utils"), getInitialDetails = require('../helpers/getInitialDetails').getInitialDetails; +const { setAxiosProxy } = require('../helpers/helper'); + module.exports = function info(args, rawArgs) { let bsConfigPath = utils.getConfigPath(args.cf); @@ -19,7 +20,7 @@ module.exports = function info(args, rawArgs) { // accept the access key from command line if provided utils.setAccessKey(bsConfig, args); - getInitialDetails(bsConfig, args, rawArgs).then((buildReportData) => { + getInitialDetails(bsConfig, args, rawArgs).then(async (buildReportData) => { utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); @@ -43,53 +44,46 @@ module.exports = function info(args, rawArgs) { options.url = `${config.turboScaleBuildsUrl}/${buildId}`; } - request.get(options, function (err, resp, body) { - let message = null; - let messageType = null; - let errorCode = null; - - if (err) { - message = Constants.userMessages.BUILD_INFO_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_info'; - - logger.info(message); - logger.error(utils.formatRequest(err, resp, body)); - } else { - let build = null; + let message = null; + let messageType = null; + let errorCode = null; + + options.config = { + auth: { + username: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + headers: options.headers + }; + setAxiosProxy(options.config); + + try { + const response = await axios.get(options.url, options.config); + let build = null; try { - build = JSON.parse(body); + build = response.data; } catch (error) { build = null; } - - if (resp.statusCode == 299) { + if (response.status == 299) { messageType = Constants.messageTypes.INFO; errorCode = 'api_deprecated'; - - if (build) { - message = build.message; - logger.info(message); - } else { - message = Constants.userMessages.API_DEPRECATED; - logger.info(message); - } - logger.info(utils.formatRequest(err, resp, body)); - } else if (resp.statusCode != 200) { + message = build ? build.message : Constants.userMessages.API_DEPRECATED; + logger.info(utils.formatRequest(response.statusText, response, response.data)); + } else if (response.status != 200) { + message = Constants.userMessages.BUILD_INFO_FAILED; messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_info'; - if (build) { message = `${ Constants.userMessages.BUILD_INFO_FAILED } with error: \n${JSON.stringify(build, null, 2)}`; - logger.error(message); if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; } else { message = Constants.userMessages.BUILD_INFO_FAILED; - logger.error(message); } - logger.error(utils.formatRequest(err, resp, body)); + logger.error(message); + logger.error(utils.formatRequest(response.statusText, response, response.data)); } else { messageType = Constants.messageTypes.SUCCESS; message = `Build info for build id: \n ${JSON.stringify( @@ -97,11 +91,18 @@ module.exports = function info(args, rawArgs) { null, 2 )}`; - logger.info(message); } - } - utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - }); + logger.info(message); + } catch (error) { + message = `${ + Constants.userMessages.BUILD_INFO_FAILED + } with error: \n${error.response.data.message}`; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_info'; + logger.info(message); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + } + utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); }).catch((err) => { logger.warn(err); }); diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 57a6d0f9..84440b50 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -22,23 +22,6 @@ const archiver = require("../helpers/archiver"), packageDiff = require('../helpers/package-diff'); const { getStackTraceUrl } = require('../helpers/sync/syncSpecsLogs'); -const { - launchTestSession, - setEventListeners, - setTestObservabilityFlags, - runCypressTestsLocally, - printBuildLink -} = require('../testObservability/helper/helper'); - - -const { - createAccessibilityTestRun, - setAccessibilityEventListeners, - checkAccessibilityPlatform, - supportFileCleanup -} = require('../accessibility-automation/helper'); -const { isTurboScaleSession, getTurboScaleGridDetails, patchCypressConfigFileContent, atsFileCleanup } = require('../helpers/atsHelper'); - module.exports = function run(args, rawArgs) { markBlockStart('preBuild'); @@ -65,14 +48,7 @@ module.exports = function run(args, rawArgs) { // set cypress config filename utils.setCypressConfigFilename(bsConfig, args); - /* - Set testObservability & browserstackAutomation flags - */ - const [isTestObservabilitySession, isBrowserstackInfra] = setTestObservabilityFlags(bsConfig); - const checkAccessibility = checkAccessibilityPlatform(bsConfig); - const isAccessibilitySession = bsConfig.run_settings.accessibility || checkAccessibility; - const turboScaleSession = isTurboScaleSession(bsConfig); - Constants.turboScaleObj.enabled = turboScaleSession; + const turboScaleSession = false; utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting); @@ -84,7 +60,8 @@ module.exports = function run(args, rawArgs) { // accept the access key from command line or env variable if provided utils.setAccessKey(bsConfig, args); - let buildReportData = (turboScaleSession || !isBrowserstackInfra) ? null : await getInitialDetails(bsConfig, args, rawArgs); + const isBrowserstackInfra = true + let buildReportData = await getInitialDetails(bsConfig, args, rawArgs); // accept the build name from command line if provided utils.setBuildName(bsConfig, args); @@ -102,6 +79,7 @@ module.exports = function run(args, rawArgs) { // set spec timeout utils.setSpecTimeout(bsConfig, args); } + // accept the specs list from command line if provided utils.setUserSpecs(bsConfig, args); @@ -111,14 +89,6 @@ module.exports = function run(args, rawArgs) { // set build tag caps utils.setBuildTags(bsConfig, args); - - /* - Send build start to Observability - */ - if(isTestObservabilitySession) { - await launchTestSession(bsConfig, bsConfigPath); - utils.setO11yProcessHooks(null, bsConfig, args, null, buildReportData); - } // accept the system env list from bsconf and set it utils.setSystemEnvs(bsConfig); @@ -147,36 +117,9 @@ module.exports = function run(args, rawArgs) { // add cypress dependency if missing utils.setCypressNpmDependency(bsConfig); - - if (isAccessibilitySession && isBrowserstackInfra) { - await createAccessibilityTestRun(bsConfig); - } - - if (turboScaleSession) { - // Local is only required in case user is running on trial grid and wants to access private website. - // Even then, it will be spawned separately via browserstack-cli ats connect-grid command and not via browserstack-cypress-cli - // Hence whenever running on ATS, need to make local as false - bsConfig.connection_settings.local = false; - - const gridDetails = await getTurboScaleGridDetails(bsConfig, args, rawArgs); - - if (gridDetails && Object.keys(gridDetails).length > 0) { - Constants.turboScaleObj.gridDetails = gridDetails; - Constants.turboScaleObj.gridUrl = gridDetails.cypressUrl; - Constants.turboScaleObj.uploadUrl = gridDetails.cypressUrl + '/upload'; - Constants.turboScaleObj.buildUrl = gridDetails.cypressUrl + '/build'; - - logger.debug(`Automate TurboScale Grid URL set to ${gridDetails.url}`); - - patchCypressConfigFileContent(bsConfig); - } else { - process.exitCode = Constants.ERROR_EXIT_CODE; - return; - } - } } - const { packagesInstalled } = !isBrowserstackInfra ? false : await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); + const { packagesInstalled } = await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); if(isBrowserstackInfra) { // set node version @@ -198,20 +141,10 @@ module.exports = function run(args, rawArgs) { markBlockEnd('setConfig'); logger.debug("Completed setting the configs"); - if(!isBrowserstackInfra) { - return runCypressTestsLocally(bsConfig, args, rawArgs); - } - // Validate browserstack.json values and parallels specified via arguments markBlockStart('validateConfig'); logger.debug("Started configs validation"); return capabilityHelper.validate(bsConfig, args).then(function (cypressConfigFile) { - if(process.env.BROWSERSTACK_TEST_ACCESSIBILITY) { - setAccessibilityEventListeners(bsConfig); - } - if(process.env.BS_TESTOPS_BUILD_COMPLETED) { - // setEventListeners(bsConfig); - } markBlockEnd('validateConfig'); logger.debug("Completed configs validation"); markBlockStart('preArchiveSteps'); @@ -289,14 +222,6 @@ module.exports = function run(args, rawArgs) { markBlockEnd('zip.zipUpload'); markBlockEnd('zip'); - if (process.env.BROWSERSTACK_TEST_ACCESSIBILITY === 'true') { - supportFileCleanup(); - } - - if (turboScaleSession) { - atsFileCleanup(bsConfig); - } - // Set config args for enforce_settings if ( !utils.isUndefinedOrFalse(bsConfig.run_settings.enforce_settings) ) { markBlockStart('setEnforceSettingsConfig'); @@ -321,9 +246,6 @@ module.exports = function run(args, rawArgs) { markBlockEnd('createBuild'); markBlockEnd('total'); utils.setProcessHooks(data.build_id, bsConfig, bs_local, args, buildReportData); - if(isTestObservabilitySession) { - utils.setO11yProcessHooks(data.build_id, bsConfig, bs_local, args, buildReportData); - } let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`; let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${data.dashboard_url}`; if (turboScaleSession) { @@ -379,7 +301,7 @@ module.exports = function run(args, rawArgs) { }); } else if(!turboScaleSession){ let stacktraceUrl = getStackTraceUrl(); - downloadBuildStacktrace(stacktraceUrl).then((message) => { + downloadBuildStacktrace(stacktraceUrl, bsConfig).then((message) => { utils.sendUsageReport(bsConfig, args, message, Constants.messageTypes.SUCCESS, null, buildReportData, rawArgs); }).catch((err) => { let message = `Downloading build stacktrace failed with statuscode: ${err}. Please visit ${data.dashboard_url} for additional details.`; @@ -403,7 +325,6 @@ module.exports = function run(args, rawArgs) { logger.info(dashboardLink); if(!args.sync) { logger.info(Constants.userMessages.EXIT_SYNC_CLI_MESSAGE.replace("",data.build_id)); - printBuildLink(false); } let dataToSend = { time_components: getTimeComponents(), diff --git a/bin/commands/stop.js b/bin/commands/stop.js index e62b516e..c37f7443 100644 --- a/bin/commands/stop.js +++ b/bin/commands/stop.js @@ -1,5 +1,4 @@ 'use strict'; -const request = require('request'); const config = require("../helpers/config"), logger = require("../helpers/logger").winstonLogger, diff --git a/bin/helpers/atsHelper.js b/bin/helpers/atsHelper.js deleted file mode 100644 index 01e874ef..00000000 --- a/bin/helpers/atsHelper.js +++ /dev/null @@ -1,119 +0,0 @@ -const path = require('path'); -const fs = require('fs') - -const request = require('request'), - logger = require('./logger').winstonLogger, - utils = require('./utils'), - config = require('./config'); - Constants = require('./constants'); - -exports.isTurboScaleSession = (bsConfig) => { - // env var will override config - if (process.env.BROWSERSTACK_TURBOSCALE && process.env.BROWSERSTACK_TURBOSCALE === 'true') { - return true; - } - - if (utils.isNotUndefined(bsConfig) && bsConfig.run_settings && bsConfig.run_settings.turboScale) { - return true; - } - - return false; -}; - -exports.getTurboScaleOptions = (bsConfig) => { - if (bsConfig.run_settings && bsConfig.run_settings.turboScaleOptions) { - return bsConfig.run_settings.turboScaleOptions; - } - - return {}; -}; - -exports.getTurboScaleGridName = (bsConfig) => { - // env var will override config - if (process.env.BROWSERSTACK_TURBOSCALE_GRID_NAME) { - return process.env.BROWSERSTACK_TURBOSCALE_GRID_NAME; - } - - if (bsConfig.run_settings && bsConfig.run_settings.turboScaleOptions && bsConfig.run_settings.turboScaleOptions.gridName) { - return bsConfig.run_settings.turboScaleOptions.gridName; - } - - return 'NO_GRID_NAME_PASSED'; -}; - -exports.getTurboScaleGridDetails = async (bsConfig, args, rawArgs) => { - try { - const gridName = this.getTurboScaleGridName(bsConfig); - - return new Promise((resolve, reject) => { - let options = { - url: `${config.turboScaleAPIUrl}/grids/${gridName}`, - auth: { - username: bsConfig.auth.username, - password: bsConfig.auth.access_key, - }, - headers: { - 'User-Agent': utils.getUserAgent(), - } - }; - let responseData = {}; - request.get(options, function (err, resp, data) { - if(err) { - logger.warn(utils.formatRequest(err, resp, data)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); - resolve({}); - } else { - try { - responseData = JSON.parse(data); - } catch (e) { - responseData = {}; - } - if(resp.statusCode != 200) { - logger.warn(`Warn: Get Automate TurboScale Details Request failed with status code ${resp.statusCode}`); - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs); - resolve({}); - } - resolve(responseData); - } - }); - }); - } catch (err) { - logger.error(`Failed to find TurboScale Grid: ${err}: ${err.stack}`); - } -}; - -exports.patchCypressConfigFileContent = (bsConfig) => { - try { - let cypressConfigFileData = fs.readFileSync(path.resolve(bsConfig.run_settings.cypress_config_file)).toString(); - const patchedConfigFileData = cypressConfigFileData + '\n\n' + ` - let originalFunction = module.exports.e2e.setupNodeEvents; - - module.exports.e2e.setupNodeEvents = (on, config) => { - const bstackOn = require("./cypressPatch.js")(on); - if (originalFunction !== null && originalFunction !== undefined) { - originalFunction(bstackOn, config); - } - return config; - } - ` - - let confPath = bsConfig.run_settings.cypress_config_file; - let patchedConfPathList = confPath.split(path.sep); - patchedConfPathList[patchedConfPathList.length - 1] = 'patched_ats_config_file.js' - const patchedConfPath = patchedConfPathList.join(path.sep); - - bsConfig.run_settings.patched_cypress_config_file = patchedConfPath; - - fs.writeFileSync(path.resolve(bsConfig.run_settings.patched_cypress_config_file), patchedConfigFileData); - } catch(e) { - logger.error(`Encountered an error when trying to patch ATS Cypress Config File ${e}`); - return {}; - } -} - -exports.atsFileCleanup = (bsConfig) => { - const filePath = path.resolve(bsConfig.run_settings.patched_cypress_config_file); - if(fs.existsSync(filePath)){ - fs.unlinkSync(filePath); - } -} diff --git a/bin/helpers/build.js b/bin/helpers/build.js index 9d97a57a..eca5ef89 100644 --- a/bin/helpers/build.js +++ b/bin/helpers/build.js @@ -1,5 +1,5 @@ 'use strict'; -const request = require('request'); +const axios = require('axios').default; const config = require('./config'), capabilityHelper = require("../helpers/capabilityHelper"), @@ -7,9 +7,11 @@ const config = require('./config'), utils = require('../helpers/utils'), logger = require('../helpers/logger').winstonLogger; +const { setAxiosProxy } = require('./helper'); + const createBuild = (bsConfig, zip) => { return new Promise(function (resolve, reject) { - capabilityHelper.caps(bsConfig, zip).then(function(data){ + capabilityHelper.caps(bsConfig, zip).then(async function(data){ let options = { url: config.buildUrl, auth: { @@ -23,42 +25,45 @@ const createBuild = (bsConfig, zip) => { body: data } - if (Constants.turboScaleObj.enabled) { - options.url = Constants.turboScaleObj.buildUrl; + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers } + setAxiosProxy(axiosConfig); - request.post(options, function (err, resp, body) { - if (err) { - logger.error(utils.formatRequest(err, resp, body)); - reject(err); - } else { - let build = null; - try { - build = JSON.parse(body); - } catch (error) { - build = null; + try { + const response = await axios.post(options.url, data, axiosConfig); + let build = null; + try { + build = response.data; + } catch (error) { + build = null; + } + if (response.status == 299) { + if (build) { + resolve(build.message); + } else { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + reject(Constants.userMessages.API_DEPRECATED); } - - if (resp.statusCode == 299) { - if (build) { - resolve(build.message); - } else { - logger.error(utils.formatRequest(err, resp, body)); - reject(Constants.userMessages.API_DEPRECATED); - } - } else if (resp.statusCode != 201) { - logger.error(utils.formatRequest(err, resp, body)); - if (build) { - reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); - } else { - reject(Constants.userMessages.BUILD_FAILED); - } + } else if (response.status != 201) { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + if (build) { + reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); } else { - resolve(build); + reject(Constants.userMessages.BUILD_FAILED); } - resolve(build); } - }) + resolve(build); + } catch (error) { + if(error.response) { + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + reject(`${Constants.userMessages.BUILD_FAILED} Error: ${error.response.data.message}`); + } + } }).catch(function(err){ reject(err); }); diff --git a/bin/helpers/buildArtifacts.js b/bin/helpers/buildArtifacts.js index 844ad3b6..5f74c29a 100644 --- a/bin/helpers/buildArtifacts.js +++ b/bin/helpers/buildArtifacts.js @@ -8,9 +8,12 @@ const logger = require('./logger').winstonLogger, Constants = require("./constants"), config = require("./config"); -const request = require('request'); +const { default: axios } = require('axios'); +const HttpsProxyAgent = require('https-proxy-agent'); +const FormData = require('form-data'); const decompress = require('decompress'); const unzipper = require("unzipper"); +const { setAxiosProxy } = require('./helper'); let BUILD_ARTIFACTS_TOTAL_COUNT = 0; let BUILD_ARTIFACTS_FAIL_COUNT = 0; @@ -107,18 +110,23 @@ const downloadAndUnzip = async (filePath, fileName, url) => { logger.debug(`Downloading build artifact for: ${filePath}`) return new Promise(async (resolve, reject) => { - request.get(url).on('response', function(response) { - - if(response.statusCode != 200) { - if (response.statusCode === 404) { + try { + const axiosConfig = { + responseType: 'stream', + validateStatus: status => (status >= 200 && status < 300) || status === 404 + }; + setAxiosProxy(axiosConfig); + const response = await axios.get(url, axiosConfig); + if(response.status != 200) { + if (response.status === 404) { reject(Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_NOT_FOUND); } - const errorMsg = `Non 200 status code, got status code: ${response.statusCode}`; + const errorMsg = `Non 200 status code, got status code: ${response.status}`; reject(errorMsg); } else { //ensure that the user can call `then()` only when the file has //been downloaded entirely. - response.pipe(writer); + response.data.pipe(writer); let error = null; writer.on('error', err => { error = err; @@ -137,7 +145,9 @@ const downloadAndUnzip = async (filePath, fileName, url) => { resolve(true); }); } - }); + } catch (error) { + reject(error); + } }); } @@ -188,28 +198,38 @@ const sendUpdatesToBstack = async (bsConfig, buildId, args, options, rawArgs, bu } options.formData = data.toString(); + const axiosConfig = { + auth: { + username: options.auth.username, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + let responseData = null; return new Promise (async (resolve, reject) => { - request.post(options, function (err, resp, data) { - if(err) { - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); - logger.error(utils.formatRequest(err, resp, data)); - reject(err); - } else { - try { - responseData = JSON.parse(data); - } catch(e) { - responseData = {}; - } - if (resp.statusCode != 200) { - if (responseData && responseData["error"]) { - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); - reject(responseData["error"]) - } + try { + const response = await axios.post(options.url, data, axiosConfig); + try { + responseData = response.data; + } catch(e) { + responseData = {}; + } + if (response.status != 200) { + if (responseData && responseData["error"]) { + utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); + reject(responseData["error"]) } } - resolve() - }); + resolve(); + } catch (error) { + if(error.response) { + utils.sendUsageReport(bsConfig, args, error.response, Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update', buildReportData, rawArgs); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + reject(errror.response.data.message); + } + } }); } @@ -233,53 +253,51 @@ exports.downloadBuildArtifacts = async (bsConfig, buildId, args, rawArgs, buildR let messageType = null; let errorCode = null; let buildDetails = null; - request.get(options, async function (err, resp, body) { - if(err) { - logger.error(utils.formatRequest(err, resp, body)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); + options.config = { + auth: options.auth, + headers: options.headers + } + setAxiosProxy(options.config); + let response; + try { + response = await axios.get(options.url, options.config); + buildDetails = response.data; + await createDirectories(buildId, buildDetails); + await parseAndDownloadArtifacts(buildId, buildDetails, bsConfig, args, rawArgs, buildReportData); + if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { + messageType = Constants.messageTypes.ERROR; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); + logger.error(message); process.exitCode = Constants.ERROR_EXIT_CODE; } else { - try { - buildDetails = JSON.parse(body); - if(resp.statusCode != 200) { - logger.error('Downloading the build artifacts failed.'); - logger.error(`Error: Request failed with status code ${resp.statusCode}`) - logger.error(utils.formatRequest(err, resp, body)); - utils.sendUsageReport(bsConfig, args, buildDetails, Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); - process.exitCode = Constants.ERROR_EXIT_CODE; - } else { - await createDirectories(buildId, buildDetails); - await parseAndDownloadArtifacts(buildId, buildDetails, bsConfig, args, rawArgs, buildReportData); - if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { - messageType = Constants.messageTypes.ERROR; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); - logger.error(message); - process.exitCode = Constants.ERROR_EXIT_CODE; - } else { - messageType = Constants.messageTypes.SUCCESS; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_SUCCESS.replace('', buildId).replace('', process.cwd()); - logger.info(message); - } - await sendUpdatesToBstack(bsConfig, buildId, args, options, rawArgs, buildReportData) - utils.sendUsageReport(bsConfig, args, message, messageType, null, buildReportData, rawArgs); - } - } catch (err) { + messageType = Constants.messageTypes.SUCCESS; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_SUCCESS.replace('', buildId).replace('', process.cwd()); + logger.info(message); + } + await sendUpdatesToBstack(bsConfig, buildId, args, options, rawArgs, buildReportData) + utils.sendUsageReport(bsConfig, args, message, messageType, null, buildReportData, rawArgs); + } catch (err) { + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_artifacts'; + if(err.response && err.response.status !== 200) { + logger.error('Downloading the build artifacts failed.'); + logger.error(`Error: Request failed with status code ${err.response.status}`) + logger.error(utils.formatRequest(err.response.statusText, err.response, err.response.data)); + utils.sendUsageReport(bsConfig, args, JSON.stringify(buildDetails), Constants.messageTypes.ERROR, 'api_failed_build_artifacts', buildReportData, rawArgs); + } else { + if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_artifacts'; - if (BUILD_ARTIFACTS_FAIL_COUNT > 0) { - messageType = Constants.messageTypes.ERROR; - message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); - logger.error(message); - } else { - logger.error('Downloading the build artifacts failed.'); - } - utils.sendUsageReport(bsConfig, args, err, messageType, errorCode, buildReportData, rawArgs); - logger.error(`Error: Request failed with status code ${resp.statusCode}`) - logger.error(utils.formatRequest(err, resp, body)); - process.exitCode = Constants.ERROR_EXIT_CODE; + message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('', buildId).replace('', BUILD_ARTIFACTS_FAIL_COUNT); + logger.error(message); + } else { + logger.error('Downloading the build artifacts failed.'); } + utils.sendUsageReport(bsConfig, args, err, messageType, errorCode, buildReportData, rawArgs); + logger.error(`Error: Request failed with status code ${resp.status}`) + logger.error(utils.formatRequest(err, resp, body)); } - resolve(); - }); + process.exitCode = Constants.ERROR_EXIT_CODE; + } + resolve(); }); }; diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index ae09e260..ba26045a 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -262,6 +262,7 @@ const validate = (bsConfig, args) => { if (!Utils.isUndefined(bsConfig.run_settings.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,bsConfig.run_settings.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE); } } catch(error){ + logger.debug(error) reject(Constants.validationMessages.INVALID_CYPRESS_JSON) } diff --git a/bin/helpers/checkUploaded.js b/bin/helpers/checkUploaded.js index 2acb5cba..dfc29241 100644 --- a/bin/helpers/checkUploaded.js +++ b/bin/helpers/checkUploaded.js @@ -1,5 +1,5 @@ 'use strict'; -const request = require('request'); +const { default: axios } = require('axios'); const { combineMacWinNpmDependencies } = require('./helper'); const crypto = require('crypto'), @@ -11,6 +11,7 @@ const crypto = require('crypto'), utils = require('./utils'), logger = require('./logger').winstonLogger; +const { setAxiosProxy } = require('./helper'); const checkSpecsMd5 = (runSettings, args, instrumentBlocks) => { return new Promise(function (resolve, reject) { @@ -91,7 +92,7 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { } instrumentBlocks.markBlockStart("checkAlreadyUploaded.md5Total"); - checkSpecsMd5(bsConfig.run_settings, args, instrumentBlocks).then(function (zip_md5sum) { + checkSpecsMd5(bsConfig.run_settings, args, instrumentBlocks).then(async function (zip_md5sum) { instrumentBlocks.markBlockStart("checkAlreadyUploaded.md5Package"); let npm_package_md5sum = checkPackageMd5(bsConfig.run_settings); instrumentBlocks.markBlockEnd("checkAlreadyUploaded.md5Package"); @@ -116,7 +117,7 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { 'Content-Type': 'application/json', "User-Agent": utils.getUserAgent(), }, - body: JSON.stringify(data) + body: data }; if (Constants.turboScaleObj.enabled) { @@ -124,39 +125,48 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => { } instrumentBlocks.markBlockStart("checkAlreadyUploaded.railsCheck"); - request.post(options, function (err, resp, body) { - if (err) { - instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); - resolve(obj); - } else { - let zipData = null; - try { - zipData = JSON.parse(body); - } catch (error) { - zipData = {}; - } - if (resp.statusCode === 200) { - if (!utils.isUndefined(zipData.zipUrl)) { - Object.assign(obj, zipData, {zipUrlPresent: true}); - } - if (!utils.isUndefined(zipData.npmPackageUrl)) { - Object.assign(obj, zipData, {packageUrlPresent: true}); - } - } - if (utils.isTrueString(zipData.disableNpmSuiteCache)) { - bsConfig.run_settings.cache_dependencies = false; - Object.assign(obj, {packageUrlPresent: false}); - delete obj.npm_package_md5sum; + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + + try { + const response = await axios.post(options.url, options.body, axiosConfig); + let zipData = null; + try { + zipData = response.data; + } catch (error) { + zipData = {}; + } + if (response.status === 200) { + if (!utils.isUndefined(zipData.zipUrl)) { + Object.assign(obj, zipData, {zipUrlPresent: true}); } - if (utils.isTrueString(zipData.disableTestSuiteCache)) { - args["force-upload"] = true; - Object.assign(obj, {zipUrlPresent: false}); - delete obj.zip_md5sum; + if (!utils.isUndefined(zipData.npmPackageUrl)) { + Object.assign(obj, zipData, {packageUrlPresent: true}); } - instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); - resolve(obj); } - }); + if (utils.isTrueString(zipData.disableNpmSuiteCache)) { + bsConfig.run_settings.cache_dependencies = false; + Object.assign(obj, {packageUrlPresent: false}); + delete obj.npm_package_md5sum; + } + if (utils.isTrueString(zipData.disableTestSuiteCache)) { + args["force-upload"] = true; + Object.assign(obj, {zipUrlPresent: false}); + delete obj.zip_md5sum; + } + instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); + resolve(obj); + } catch (error) { + instrumentBlocks.markBlockEnd("checkAlreadyUploaded.railsCheck"); + resolve(obj); + } }).catch((err) => { let errString = err.stack ? err.stack.toString().substring(0,100) : err.toString().substring(0,100); resolve({zipUrlPresent: false, packageUrlPresent: false, error: errString}); diff --git a/bin/helpers/downloadBuildStacktrace.js b/bin/helpers/downloadBuildStacktrace.js index a7471038..50d55f7c 100644 --- a/bin/helpers/downloadBuildStacktrace.js +++ b/bin/helpers/downloadBuildStacktrace.js @@ -1,28 +1,37 @@ -'use strict' -const request = require('request'); +'use strict'; +const { default: axios } = require('axios'); +const { setAxiosProxy } = require('./helper'); + +const downloadBuildStacktrace = async (url, bsConfig) => { + const axiosConfig = { + auth: { + username: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + responseType: 'stream', + }; + setAxiosProxy(axiosConfig); -const downloadBuildStacktrace = async (url) => { return new Promise(async (resolve, reject) => { - request.get(url).on('response', function (response) { - if(response.statusCode == 200) { - response.pipe(process.stdout); + try { + const response = await axios.get(url, axiosConfig); + if (response.status === 200) { + response.data.pipe(process.stdout); let error = null; process.stdout.on('error', (err) => { error = err; process.stdout.close(); - reject(response.statusCode); + reject(response.status); }); process.stdout.on('close', async () => { - if(!error) { - resolve("Build stacktrace downloaded successfully"); + if (!error) { + resolve('Build stacktrace downloaded successfully'); } }); - } else { - reject(response.statusCode); } - }).on('end', () => { - resolve("Build stacktrace downloaded successfully"); - }); + } catch (error) { + reject(error.response.status); + } }); }; diff --git a/bin/helpers/getInitialDetails.js b/bin/helpers/getInitialDetails.js index 30d3114b..61b1539d 100644 --- a/bin/helpers/getInitialDetails.js +++ b/bin/helpers/getInitialDetails.js @@ -1,11 +1,14 @@ -const request = require('request'), - logger = require('./logger').winstonLogger, +const { default: axios } = require('axios'); + +const logger = require('./logger').winstonLogger, utils = require('./utils'), config = require("./config"), Constants = require('./constants'); +const { setAxiosProxy } = require('./helper'); + exports.getInitialDetails = (bsConfig, args, rawArgs) => { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let options = { url: config.getInitialDetails, auth: { @@ -17,28 +20,36 @@ exports.getInitialDetails = (bsConfig, args, rawArgs) => { } }; let responseData = {}; - request.get(options, function (err, resp, data) { - if(err) { - logger.warn(utils.formatRequest(err, resp, data)); - utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); + + const axiosConfig = { + auth: options.auth, + headers: options.headers, + } + setAxiosProxy(axiosConfig); + + try { + const response = await axios.get(options.url, axiosConfig); + try { + responseData = response.data; + } catch (e) { + responseData = {}; + } + if(response.status != 200) { + logger.warn(`Warn: Get Initial Details Request failed with status code ${response.status}`); + utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); resolve({}); } else { - try { - responseData = JSON.parse(data); - } catch (e) { - responseData = {}; - } - if(resp.statusCode != 200) { - logger.warn(`Warn: Get Initial Details Request failed with status code ${resp.statusCode}`); - utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); - resolve({}); - } else { - if (!utils.isUndefined(responseData.grr) && responseData.grr.enabled && !utils.isUndefined(responseData.grr.urls)) { - config.uploadUrl = responseData.grr.urls.upload_url; - } - resolve(responseData); + if (!utils.isUndefined(responseData.grr) && responseData.grr.enabled && !utils.isUndefined(responseData.grr.urls)) { + config.uploadUrl = responseData.grr.urls.upload_url; } + resolve(responseData); + } + } catch (error) { + if(error.response && error.response.status !== 200) { + logger.warn(`Warn: Get Initial Details Request failed with status code ${error.response.status}`); + utils.sendUsageReport(bsConfig, args, error.response.data["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs); } - }); + resolve({}); + } }); }; diff --git a/bin/helpers/helper.js b/bin/helpers/helper.js index 3e89e971..9e9a91c6 100644 --- a/bin/helpers/helper.js +++ b/bin/helpers/helper.js @@ -6,7 +6,6 @@ const fs = require('fs'); const path = require('path'); const http = require('http'); const https = require('https'); -const request = require('request'); const gitLastCommit = require('git-last-commit'); const { v4: uuidv4 } = require('uuid'); const os = require('os'); @@ -17,16 +16,13 @@ const { spawn, execSync } = require('child_process'); const glob = require('glob'); const pGitconfig = promisify(gitconfig); const { readCypressConfigFile } = require('./readCypressConfigUtil'); -const CrashReporter = require('../testObservability/crashReporter'); const { MAX_GIT_META_DATA_SIZE_IN_BYTES, GIT_META_DATA_TRUNCATED } = require('./constants') +const HttpsProxyAgent = require('https-proxy-agent'); exports.debug = (text, shouldReport = false, throwable = null) => { if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { logger.info(`[ OBSERVABILITY ] ${text}`); } - if(shouldReport) { - CrashReporter.getInstance().uploadCrashReport(text, throwable ? throwable && throwable.stack : null); - } } exports.getFileSeparatorData = () => { @@ -442,6 +438,13 @@ exports.truncateString = (field, truncateSizeInBytes) => { return field; }; +exports.setAxiosProxy = (axiosConfig) => { + if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) { + const httpProxy = process.env.HTTP_PROXY || process.env.HTTPS_PROXY + axiosConfig.httpsAgent = new HttpsProxyAgent(httpProxy); + }; +}; + exports.combineMacWinNpmDependencies = (runSettings) => { return Object.assign({}, runSettings.npm_dependencies, runSettings.win_npm_dependencies || {}, runSettings.mac_npm_dependencies || {}) }; diff --git a/bin/helpers/reporterHTML.js b/bin/helpers/reporterHTML.js index 5859b103..9345f25d 100644 --- a/bin/helpers/reporterHTML.js +++ b/bin/helpers/reporterHTML.js @@ -1,13 +1,16 @@ +const axios = require('axios').default; + const fs = require('fs'), path = require('path'), - request = require('request'), logger = require('./logger').winstonLogger, utils = require("./utils"), Constants = require('./constants'), config = require("./config"), decompress = require('decompress'); -let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => { +const { setAxiosProxy } = require('./helper'); + +let reportGenerator = async (bsConfig, buildId, args, rawArgs, buildReportData, cb) => { let options = { url: `${config.buildUrl}${buildId}/custom_report`, auth: { @@ -21,32 +24,29 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => logger.debug('Started fetching the build json and html reports.'); - return request.get(options, async function (err, resp, body) { - let message = null; - let messageType = null; - let errorCode = null; - let build; - - if (err) { - message = err; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_report'; + let message = null; + let messageType = null; + let errorCode = null; + let build; - logger.error('Generating the build report failed.'); - logger.error(utils.formatRequest(err, resp, body)); + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + } + setAxiosProxy(axiosConfig); - utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - return; - } else { - logger.debug('Received reports data from upstream.'); - try { - build = JSON.parse(body); - } catch (error) { - build = null; - } + try { + const response = await axios.get(options.url, axiosConfig); + logger.debug('Received reports data from upstream.'); + try { + build = response.data; + } catch (error) { + build = null; } - - if (resp.statusCode == 299) { + if (response.status == 299) { messageType = Constants.messageTypes.INFO; errorCode = 'api_deprecated'; if (build) { @@ -56,18 +56,18 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => message = Constants.userMessages.API_DEPRECATED; logger.info(message); } - } else if (resp.statusCode === 422) { + } else if (response.status === 422) { messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_generate_report'; try { - response = JSON.parse(body); + response = error.response.data; message = response.message; } catch (error) { logger.error(`Error generating the report: ${error}`); response = {message: message}; } - logger.error(utils.formatRequest(err, resp, body)); - } else if (resp.statusCode != 200) { + logger.error(utils.formatRequest(response.statusText, response, response.data)); + } else if (response.status != 200) { messageType = Constants.messageTypes.ERROR; errorCode = 'api_failed_build_generate_report'; @@ -79,7 +79,7 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; } else { message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); - logger.error(utils.formatRequest(err, resp, body)); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); } } else { messageType = Constants.messageTypes.SUCCESS; @@ -92,7 +92,16 @@ let reportGenerator = (bsConfig, buildId, args, rawArgs, buildReportData, cb) => if (cb){ cb(); } - }); + } catch (error) { + message = error.response.statusText; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_report'; + + logger.error('Generating the build report failed.'); + logger.error(utils.formatRequest(error.response.statusText, error.response, error.response.data)); + utils.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); + return; + } } async function generateCypressBuildReport(report_data) { @@ -111,15 +120,16 @@ function getReportResponse(filePath, fileName, reportJsonUrl) { const writer = fs.createWriteStream(tmpFilePath); logger.debug(`Fetching build reports zip.`) return new Promise(async (resolve, reject) => { - request.get(reportJsonUrl).on('response', function(response) { - - if(response.statusCode != 200) { - let message = `Received non 200 response while fetching reports, code: ${response.statusCode}`; - reject(message); - } else { + try { + const axiosConfig = { + responseType: 'stream', + }; + setAxiosProxy(axiosConfig); + const response = await axios.get(reportJsonUrl, axiosConfig); + if(response.status === 200) { //ensure that the user can call `then()` only when the file has //been downloaded entirely. - response.pipe(writer); + response.data.pipe(writer); let error = null; writer.on('error', err => { error = err; @@ -144,7 +154,10 @@ function getReportResponse(filePath, fileName, reportJsonUrl) { //'error' stream; }); } - }); + } catch (error) { + let message = `Received non 200 response while fetching reports, code: ${error.response.status}`; + reject(message); + } }); } diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 8933868c..07e7ad31 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -1,6 +1,8 @@ "use strict"; -const request = require("request"), - config = require("../config"), + +const { default: axios } = require("axios"); + +const config = require("../config"), utils = require("../utils"), logger = require("../logger").syncCliLogger, winstonLogger = require("../logger").winstonLogger, @@ -9,6 +11,8 @@ const request = require("request"), tableStream = require('table').createStream, chalk = require('chalk'); +const { setAxiosProxy } = require('../helper'); + let whileLoop = true, whileTries = config.retries, options, timeout = 3000, n = 2, tableConfig, stream, endTime, startTime = Date.now(), buildStarted = false; let specSummary = { "buildError": null, @@ -135,27 +139,23 @@ let printSpecsStatus = (bsConfig, buildDetails, rawArgs, buildReportData) => { }); }; -let whileProcess = (whilstCallback) => { - request.post(options, function(error, response, body) { - if (error) { - whileTries -= 1; - if (whileTries === 0) { - whileLoop = false; - endTime = Date.now(); - specSummary.exitCode = config.networkErrorExitCode; - return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout - } else { - n = 2 - return setTimeout(whilstCallback, timeout * n, null); - } - } +let whileProcess = async (whilstCallback) => { + try { + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + const response = await axios.post(options.url, null, axiosConfig); whileTries = config.retries; // reset to default after every successful request - - switch (response.statusCode) { + switch (response.status) { case 202: // get data here and print it n = 2 - showSpecsStatus(body, 202); + showSpecsStatus(response.data, 202); return setTimeout(whilstCallback, timeout * n, null); case 204: // No data available, wait for some time and ask again n = 1 @@ -163,14 +163,27 @@ let whileProcess = (whilstCallback) => { case 200: // Build is completed. whileLoop = false; endTime = Date.now(); - showSpecsStatus(body, 200); + showSpecsStatus(response.data, 200); return specSummary.exitCode == Constants.BUILD_FAILED_EXIT_CODE ? whilstCallback({ status: 204, message: "No specs ran in the build"} ) : whilstCallback(null, body); default: whileLoop = false; - return whilstCallback({ status: response.statusCode, message: body }); + return whilstCallback({ status: response.status, message: response.data }); } - }); + } catch (error) { + if (error) { + whileTries -= 1; + if (whileTries === 0) { + whileLoop = false; + endTime = Date.now(); + specSummary.exitCode = config.networkErrorExitCode; + return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout + } else { + n = 2 + return setTimeout(whilstCallback, timeout * n, null); + } + } + } } let getStackTraceUrl = () => { @@ -178,7 +191,7 @@ let getStackTraceUrl = () => { } let showSpecsStatus = (data, statusCode) => { - let specData = JSON.parse(data); + let specData = data; specData["specData"].forEach(specDetails => { if (specDetails.type === Constants.CYPRESS_CUSTOM_ERRORS_TO_PRINT_KEY) { addCustomErrorToPrint(specDetails); diff --git a/bin/helpers/usageReporting.js b/bin/helpers/usageReporting.js index 5a7a250e..1e714018 100644 --- a/bin/helpers/usageReporting.js +++ b/bin/helpers/usageReporting.js @@ -1,7 +1,6 @@ 'use strict'; const cp = require("child_process"), os = require("os"), - request = require("requestretry"), fs = require('fs'), path = require('path'); @@ -10,7 +9,10 @@ const config = require('./config'), utils = require('./utils'); const { AUTH_REGEX, REDACTED_AUTH, REDACTED, CLI_ARGS_REGEX, RAW_ARGS_REGEX } = require("./constants"); -const { isTurboScaleSession } = require('../helpers/atsHelper'); +const { default: axios } = require("axios"); +const axiosRetry = require("axios-retry"); + +const { setAxiosProxy } = require('./helper'); function get_version(package_name) { try { @@ -198,56 +200,9 @@ function redactKeys(str, regex, redact) { return str.replace(regex, redact); } -function sendTurboscaleErrorLogs(args) { - let bsConfig = JSON.parse(JSON.stringify(args.bstack_config)); - let data = utils.isUndefined(args.data) ? {} : args.data; - const turboscaleErrorPayload = { - kind: 'hst-cypress-cli-error', - data: data, - error: args.message - } - - const options = { - headers: { - 'User-Agent': utils.getUserAgent() - }, - method: "POST", - auth: { - username: bsConfig.auth.username, - password: bsConfig.auth.access_key, - }, - url: `${config.turboScaleAPIUrl}/send-instrumentation`, - body: turboscaleErrorPayload, - json: true, - maxAttempts: 10, // (default) try 3 times - retryDelay: 2000, // (default) wait for 2s before trying again - retrySrategy: request.RetryStrategies.HTTPOrNetworkError, // (default) retry on 5xx or network errors - }; - - fileLogger.info(`Sending ${JSON.stringify(turboscaleErrorPayload)} to ${config.turboScaleAPIUrl}/send-instrumentation`); - request(options, function (error, res, body) { - if (error) { - //write err response to file - fileLogger.error(JSON.stringify(error)); - return; - } - // write response file - let response = { - attempts: res.attempts, - statusCode: res.statusCode, - body: body - }; - fileLogger.info(`${JSON.stringify(response)}`); - }); -} - -function send(args) { +async function send(args) { let bsConfig = JSON.parse(JSON.stringify(args.bstack_config)); - if (isTurboScaleSession(bsConfig) && args.message_type === 'error') { - sendTurboscaleErrorLogs(args); - } - if (isUsageReportingEnabled() === "true") return; let runSettings = ""; @@ -307,10 +262,6 @@ function send(args) { }, }; - if (isTurboScaleSession(bsConfig)) { - payload.event_type = 'hst_cypress_cli_stats'; - } - const options = { headers: { "Content-Type": "text/json", @@ -321,24 +272,34 @@ function send(args) { json: true, maxAttempts: 10, // (default) try 3 times retryDelay: 2000, // (default) wait for 2s before trying again - retrySrategy: request.RetryStrategies.HTTPOrNetworkError, // (default) retry on 5xx or network errors }; + const axiosConfig = { + headers: options.headers, + }; + setAxiosProxy(axiosConfig); + fileLogger.info(`Sending ${JSON.stringify(payload)} to ${config.usageReportingUrl}`); - request(options, function (error, res, body) { - if (error) { - //write err response to file - fileLogger.error(JSON.stringify(error)); - return; + axiosRetry(axios, + { + retries: 3, + retryDelay: 2000, + retryCondition: (error) => { + return (error.response.status === 503 || error.response.status === 500) } - // write response file - let response = { - attempts: res.attempts, - statusCode: res.statusCode, - body: body - }; - fileLogger.info(`${JSON.stringify(response)}`); }); + try { + const response = await axios.post(options.url, options.body, axiosConfig); + let result = { + statusText: response.statusText, + statusCode: response.status, + body: response.data + }; + fileLogger.info(`${JSON.stringify(result)}`); + } catch (error) { + fileLogger.error(JSON.stringify(error.response)); + return; + } } module.exports = { diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index fc5fd614..44a2fcd4 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -12,6 +12,7 @@ const { promisify } = require('util'); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); const TIMEZONE = require("../helpers/timezone.json"); +const { setAxiosProxy } = require('./helper'); const usageReporting = require("./usageReporting"), logger = require("./logger").winstonLogger, @@ -21,11 +22,9 @@ const usageReporting = require("./usageReporting"), fileHelpers = require("./fileHelpers"), config = require("../helpers/config"), pkg = require('../../package.json'), - transports = require('./logger').transports, - o11yHelpers = require('../testObservability/helper/helper'), - { OBSERVABILITY_ENV_VARS, TEST_OBSERVABILITY_REPORTER } = require('../testObservability/helper/constants'); + transports = require('./logger').transports -const request = require('request'); +const { default: axios } = require("axios"); exports.validateBstackJson = (bsConfigPath) => { return new Promise(function (resolve, reject) { @@ -491,10 +490,6 @@ exports.setNodeVersion = (bsConfig, args) => { // specs can be passed via command line args as a string // command line args takes precedence over config exports.setUserSpecs = (bsConfig, args) => { - if(o11yHelpers.isBrowserstackInfra() && o11yHelpers.isTestObservabilitySession() && o11yHelpers.shouldReRunObservabilityTests()) { - bsConfig.run_settings.specs = process.env.BROWSERSTACK_RERUN_TESTS; - return; - } let bsConfigSpecs = bsConfig.run_settings.specs; @@ -587,19 +582,6 @@ exports.setSystemEnvs = (bsConfig) => { logger.error(`Error in adding accessibility configs ${error}`) } - try { - OBSERVABILITY_ENV_VARS.forEach(key => { - envKeys[key] = process.env[key]; - }); - - let gitConfigPath = o11yHelpers.findGitConfig(process.cwd()); - if(!o11yHelpers.isBrowserstackInfra()) process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL = gitConfigPath; - if(gitConfigPath) { - const relativePathFromGitConfig = path.relative(gitConfigPath, process.cwd()); - envKeys["OBSERVABILITY_GIT_CONFIG_PATH"] = relativePathFromGitConfig ? relativePathFromGitConfig : 'DEFAULT'; - } - } catch(e){} - if (Object.keys(envKeys).length === 0) { bsConfig.run_settings.system_env_vars = null; } else { @@ -1018,14 +1000,27 @@ exports.checkLocalBinaryRunning = (bsConfig, localIdentifier) => { }, body: JSON.stringify({ localIdentifier: localIdentifier}), }; - return new Promise ( function(resolve, reject) { - request.post(options, function (err, resp, body) { - if(err){ - reject(err); + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers + }; + setAxiosProxy(axiosConfig); + + return new Promise (async function(resolve, reject) { + try { + const response = await axios.post(options.url, { + localIdentifier: localIdentifier + }, axiosConfig); + resolve(response.data) + } catch (error) { + if(error.response) { + reject(error.response.data.message); } - let response = JSON.parse(body); - resolve(response); - }); + } }); }; @@ -1212,11 +1207,7 @@ exports.handleSyncExit = (exitCode, dashboard_url) => { syncCliLogger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); syncCliLogger.info(dashboard_url); } - if(o11yHelpers.isTestObservabilitySession()) { - o11yHelpers.printBuildLink(true, exitCode); - } else { - process.exit(exitCode); - } + process.exit(exitCode); } exports.getNetworkErrorMessage = (dashboard_url) => { @@ -1475,10 +1466,6 @@ exports.splitStringByCharButIgnoreIfWithinARange = (str, splitChar, leftLimiter, // blindly send other passed configs with run_settings and handle at backend exports.setOtherConfigs = (bsConfig, args) => { - if(o11yHelpers.isTestObservabilitySession() && process.env.BS_TESTOPS_JWT) { - bsConfig["run_settings"]["reporter"] = TEST_OBSERVABILITY_REPORTER; - return; - } /* Non Observability use-case */ if (!this.isUndefined(args.reporter)) { @@ -1537,7 +1524,7 @@ exports.setCLIMode = (bsConfig, args) => { exports.formatRequest = (err, resp, body) => { return { err, - status: resp ? resp.statusCode : null, + status: resp ? resp.status : null, body: body ? util.format('%j', body) : null } } @@ -1557,78 +1544,80 @@ exports.setDebugMode = (args) => { exports.stopBrowserStackBuild = async (bsConfig, args, buildId, rawArgs, buildReportData = null) => { let that = this; - return new Promise(function (resolve, reject) { - let url = config.buildStopUrl + buildId; - let options = { - url: url, - auth: { - username: bsConfig["auth"]["username"], - password: bsConfig["auth"]["access_key"], - }, - headers: { - 'User-Agent': that.getUserAgent(), - }, - }; + + let url = config.buildStopUrl + buildId; + let options = { + url: url, + auth: { + username: bsConfig["auth"]["username"], + password: bsConfig["auth"]["access_key"], + }, + headers: { + 'User-Agent': that.getUserAgent(), + }, + }; - if (Constants.turboScaleObj.enabled) { - options.url = `${config.turboScaleBuildsUrl}/${buildId}/stop`; - } + const axiosConfig = { + auth: options.auth, + headers: options.headers + }; + setAxiosProxy(axiosConfig); - let message = null; - let messageType = null; - let errorCode = null; - let build = null; - request.post(options, function(err, resp, data) { - if(err) { - message = Constants.userMessages.BUILD_STOP_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; + if (Constants.turboScaleObj.enabled) { + options.url = `${config.turboScaleBuildsUrl}/${buildId}/stop`; + } + + let message = null; + let messageType = null; + let errorCode = null; + let build = null; + + try { + const response = await axios.post(options.url, {}, axiosConfig); + + build = response.data; + if (response.status == 299) { + messageType = Constants.messageTypes.INFO; + errorCode = 'api_deprecated'; + + if (build) { + message = build.message; logger.info(message); } else { - try { - build = JSON.parse(data); - if (resp.statusCode == 299) { - messageType = Constants.messageTypes.INFO; - errorCode = 'api_deprecated'; - - if (build) { - message = build.message; - logger.info(message); - } else { - message = Constants.userMessages.API_DEPRECATED; - logger.info(message); - } - } else if (resp.statusCode != 200) { - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; - - if (build) { - message = `${ - Constants.userMessages.BUILD_STOP_FAILED - } with error: \n${JSON.stringify(build, null, 2)}`; - logger.error(message); - if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; - } else { - message = Constants.userMessages.BUILD_STOP_FAILED; - logger.error(message); - } - } else { - messageType = Constants.messageTypes.SUCCESS; - message = `${JSON.stringify(build, null, 2)}`; - logger.info(message); - } - } catch(err) { - message = Constants.userMessages.BUILD_STOP_FAILED; - messageType = Constants.messageTypes.ERROR; - errorCode = 'api_failed_build_stop'; - logger.info(message); - } finally { - that.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); - } + message = Constants.userMessages.API_DEPRECATED; + logger.info(message); } - resolve(); - }); - }); + } else if (response.status !== 200) { + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_stop'; + + if (build) { + message = `${ + Constants.userMessages.BUILD_STOP_FAILED + } with error: \n${JSON.stringify(build, null, 2)}`; + logger.error(message); + if (build.message === 'Unauthorized') errorCode = 'api_auth_failed'; + } else { + message = Constants.userMessages.BUILD_STOP_FAILED; + logger.error(message); + } + } else { + messageType = Constants.messageTypes.SUCCESS; + message = `${JSON.stringify(build, null, 2)}`; + logger.info(message); + } + } catch(err) { + if(err.response) { + message = `${ + Constants.userMessages.BUILD_STOP_FAILED + } with error: ${err.response.data.message}`; + messageType = Constants.messageTypes.ERROR; + errorCode = 'api_failed_build_stop'; + logger.error(message); + } + } finally { + that.sendUsageReport(bsConfig, args, message, messageType, errorCode, buildReportData, rawArgs); + } } exports.setProcessHooks = (buildId, bsConfig, bsLocal, args, buildReportData) => { @@ -1645,36 +1634,14 @@ exports.setProcessHooks = (buildId, bsConfig, bsLocal, args, buildReportData) => process.on('uncaughtException', processExitHandler.bind(this, bindData)); } -exports.setO11yProcessHooks = (() => { - let bindData = {}; - let handlerAdded = false; - return (buildId, bsConfig, bsLocal, args, buildReportData) => { - bindData.buildId = buildId; - bindData.bsConfig = bsConfig; - bindData.bsLocal = bsLocal; - bindData.args = args; - bindData.buildReportData = buildReportData; - if (handlerAdded) return; - handlerAdded = true; - process.on('beforeExit', processO11yExitHandler.bind(this, bindData)); - } -})() - async function processExitHandler(exitData){ logger.warn(Constants.userMessages.PROCESS_KILL_MESSAGE); await this.stopBrowserStackBuild(exitData.bsConfig, exitData.args, exitData.buildId, null, exitData.buildReportData); await this.stopLocalBinary(exitData.bsConfig, exitData.bsLocalInstance, exitData.args, null, exitData.buildReportData); - await o11yHelpers.printBuildLink(true); + // await o11yHelpers.printBuildLink(true); process.exit(0); } -async function processO11yExitHandler(exitData){ - if (exitData.buildId) { - await o11yHelpers.printBuildLink(false); - } else { - await o11yHelpers.printBuildLink(true); - } -} exports.fetchZipSize = (fileName) => { try { diff --git a/bin/helpers/zipUpload.js b/bin/helpers/zipUpload.js index 441f9ec0..8b59611c 100644 --- a/bin/helpers/zipUpload.js +++ b/bin/helpers/zipUpload.js @@ -1,26 +1,27 @@ 'use strict'; -const request = require("request"), - fs = require("fs"); +const fs = require("fs"); const cliProgress = require('cli-progress'); +const { default: axios } = require("axios"); +const FormData = require("form-data"); const config = require("./config"), logger = require("./logger").winstonLogger, Constants = require("./constants"), utils = require("./utils"); +const { setAxiosProxy } = require('./helper'); const purgeUploadBar = (obj) => { obj.bar1.update(100, { speed: ((obj.size / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec }); obj.bar1.stop(); - clearInterval(obj.zipInterval); } const uploadSuits = (bsConfig, filePath, opts, obj) => { - return new Promise(function (resolve, reject) { + return new Promise(async function (resolve, reject) { let uploadProgressBarErrorFlags = { noConnectionReportSent: false, unknownErrorReportSent: false @@ -54,94 +55,69 @@ const uploadSuits = (bsConfig, filePath, opts, obj) => { let options = utils.generateUploadParams(bsConfig, filePath, opts.md5Data, opts.fileDetails) let responseData = null; - var r = request.post(options, function (err, resp, body) { - - if (err) { - reject({message: err, stacktrace: utils.formatRequest(err, resp, body)}); - } else { - try { - responseData = JSON.parse(body); - } catch (e) { - responseData = {}; - } - if (resp.statusCode != 200) { - if (resp.statusCode == 401) { - if (responseData && responseData["error"]) { - responseData["time"] = Date.now() - obj.startTime; - return reject({message: responseData["error"], stacktrace: utils.formatRequest(err, resp, body)}); - } else { - return reject({message: Constants.validationMessages.INVALID_DEFAULT_AUTH_PARAMS, stacktrace: utils.formatRequest(err, resp, body)}); - } - } - if (!opts.propogateError){ - purgeUploadBar(obj); - if (resp.statusCode == 413) { - return resolve({warn: Constants.userMessages.NODE_MODULES_LIMIT_EXCEEDED.replace("%SIZE%", (size / 1000000).toFixed(2))}); - } - return resolve({}) - } - if(responseData && responseData["error"]){ - responseData["time"] = Date.now() - obj.startTime; - reject({message: responseData["error"], stacktrace: utils.formatRequest(err, resp, body)}); + try { + const formData = new FormData(); + formData.append("file", fs.createReadStream(filePath)); + formData.append("filetype", opts.fileDetails.filetype); + formData.append("filename", opts.fileDetails.filename); + formData.append("zipMd5sum", opts.md5Data ? opts.md5Data : ''); + + const axiosConfig = { + auth: { + username: options.auth.user, + password: options.auth.password + }, + headers: options.headers, + onUploadProgress: (progressEvent) => { + let percent = parseInt(Math.floor((progressEvent.loaded * 100) / progressEvent.total)); + obj.bar1.update(percent, { + speed: ((progressEvent.bytes / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec + }); + }, + }; + setAxiosProxy(axiosConfig); + + const response = await axios.post(options.url, formData, axiosConfig); + responseData = response.data; + purgeUploadBar(obj) + logger.info(`${opts.messages.uploadingSuccess} (${responseData[opts.md5ReturnKey]})`); + opts.cleanupMethod(); + responseData["time"] = Date.now() - obj.startTime; + resolve(responseData); + } catch (error) { + let responseData = null; + if(error.response){ + responseData = error.response.data; + if (error.response.status === 401) { + if (responseData && responseData.error) { + responseData.time = Date.now() - obj.startTime; + return reject({message: responseData.error, stacktrace: utils.formatRequest(responseData.error, error.response, responseData)}); } else { - if (resp.statusCode == 413) { - reject({message: Constants.userMessages.ZIP_UPLOAD_LIMIT_EXCEEDED, stacktrace: utils.formatRequest(err, resp, body)}); - } else { - reject({message: Constants.userMessages.ZIP_UPLOADER_NOT_REACHABLE, stacktrace: utils.formatRequest(err, resp, body)}); - } - } - } else { + return reject({message: Constants.validationMessages.INVALID_DEFAULT_AUTH_PARAMS, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); + } + } + if (!opts.propogateError){ purgeUploadBar(obj); - logger.info(`${opts.messages.uploadingSuccess} (${responseData[opts.md5ReturnKey]})`); - opts.cleanupMethod(); - responseData["time"] = Date.now() - obj.startTime; - resolve(responseData); + if (error.response.status === 413) { + return resolve({warn: Constants.userMessages.NODE_MODULES_LIMIT_EXCEEDED.replace("%SIZE%", (size / 1000000).toFixed(2))}); + } + return resolve({}) } - } - }); - - obj.zipInterval = setInterval(function () { - const errorCode = 'update_upload_progress_bar_failed'; - try { - if (r && r.req && r.req.connection) { - let dispatched = r.req.connection._bytesDispatched; - let percent = dispatched * 100.0 / size; - obj.bar1.update(percent, { - speed: ((dispatched / (Date.now() - obj.startTime)) / 125).toFixed(2) //kbits per sec - }); + if(responseData && responseData["error"]){ + responseData["time"] = Date.now() - obj.startTime; + reject({message: responseData["error"], stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); } else { - if (!uploadProgressBarErrorFlags.noConnectionReportSent) { - logger.debug(Constants.userMessages.NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR); - utils.sendUsageReport( - bsConfig, - null, - Constants.userMessages.NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR, - Constants.messageTypes.WARNING, - errorCode, - null, - null - ); - uploadProgressBarErrorFlags.noConnectionReportSent = true; + if (error.response.status === 413) { + reject({message: Constants.userMessages.ZIP_UPLOAD_LIMIT_EXCEEDED, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); + } else { + reject({message: Constants.userMessages.ZIP_UPLOADER_NOT_REACHABLE, stacktrace: utils.formatRequest(error.response.statusText, error.response, responseData)}); } } - } catch (error) { - if (!uploadProgressBarErrorFlags.unknownErrorReportSent) { - logger.debug('Unable to determine progress.'); - logger.debug(error); - utils.sendUsageReport( - bsConfig, - null, - error.stack, - Constants.messageTypes.WARNING, - errorCode, - null, - null - ); - uploadProgressBarErrorFlags.unknownErrorReportSent = true; - } + reject({message: error.response, stacktrace: utils.formatRequest(error.response.statusText, error.response, error.response.data)}); + } else { + reject({}) } - }, 150); - + } }); } diff --git a/bin/testObservability/crashReporter/index.js b/bin/testObservability/crashReporter/index.js deleted file mode 100644 index 7433c164..00000000 --- a/bin/testObservability/crashReporter/index.js +++ /dev/null @@ -1,174 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const request = require('request'); -const http = require('http'); -const https = require('https'); - -const logger = require("../../helpers/logger").winstonLogger; -const utils = require('../../helpers/utils'); - -const { API_URL, consoleHolder } = require('../helper/constants'); - -/* Below global methods are added here to remove cyclic dependency with helper.js, refactor later */ -const httpsKeepAliveAgent = new https.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const debug = (text) => { - if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { - logger.info(`[ OBSERVABILITY ] ${text}`); - } -} - -let packages = {}; - -exports.requireModule = (module, internal = false) => { - let local_path = ""; - if(process.env["browserStackCwd"]){ - local_path = path.join(process.env["browserStackCwd"], 'node_modules', module); - } else if(internal) { - local_path = path.join(process.cwd(), 'node_modules', 'browserstack-cypress-cli', 'node_modules', module); - } else { - local_path = path.join(process.cwd(), 'node_modules', module); - } - if(!fs.existsSync(local_path)) { - let global_path; - if(['jest-runner', 'jest-runtime'].includes(module)) - global_path = path.join(GLOBAL_MODULE_PATH, 'jest', 'node_modules', module); - else - global_path = path.join(GLOBAL_MODULE_PATH, module); - if(!fs.existsSync(global_path)) { - throw new Error(`${module} doesn't exist.`); - } - return require(global_path); - } - return require(local_path); -} - -getPackageVersion = (package_, bsConfig = null) => { - if(packages[package_]) return packages[package_]; - let packageVersion; - /* Try to find version from module path */ - try { - packages[package_] = this.requireModule(`${package_}/package.json`).version; - logger.info(`Getting ${package_} package version from module path = ${packages[package_]}`); - packageVersion = packages[package_]; - } catch(e) { - debug(`Unable to find package ${package_} at module path with error ${e}`); - } - - /* Read package version from npm_dependencies in browserstack.json file if present */ - if(utils.isUndefined(packageVersion) && bsConfig && (process.env.BROWSERSTACK_AUTOMATION == "true" || process.env.BROWSERSTACK_AUTOMATION == "1")) { - const runSettings = bsConfig.run_settings; - if (runSettings && runSettings.npm_dependencies !== undefined && - Object.keys(runSettings.npm_dependencies).length !== 0 && - typeof runSettings.npm_dependencies === 'object') { - if (package_ in runSettings.npm_dependencies) { - packages[package_] = runSettings.npm_dependencies[package_]; - logger.info(`Getting ${package_} package version from browserstack.json = ${packages[package_]}`); - packageVersion = packages[package_]; - } - } - } - - /* Read package version from project's package.json if present */ - const packageJSONPath = path.join(process.cwd(), 'package.json'); - if(utils.isUndefined(packageVersion) && fs.existsSync(packageJSONPath)) { - const packageJSONContents = require(packageJSONPath); - if(packageJSONContents.devDependencies && !utils.isUndefined(packageJSONContents.devDependencies[package_])) packages[package_] = packageJSONContents.devDependencies[package_]; - if(packageJSONContents.dependencies && !utils.isUndefined(packageJSONContents.dependencies[package_])) packages[package_] = packageJSONContents.dependencies[package_]; - logger.info(`Getting ${package_} package version from package.json = ${packages[package_]}`); - packageVersion = packages[package_]; - } - - return packageVersion; -} - -getAgentVersion = () => { - let _path = path.join(__dirname, '../../../package.json'); - if(fs.existsSync(_path)) - return require(_path).version; -} - -class CrashReporter { - static instance; - - constructor() { - } - - static getInstance() { - if (!CrashReporter.instance) { - CrashReporter.instance = new CrashReporter(); - } - return CrashReporter.instance; - } - - setCredentialsForCrashReportUpload(credentialsStr) { - /* User credentials used for reporting crashes */ - this.credentialsForCrashReportUpload = JSON.parse(credentialsStr); - } - - setConfigDetails(credentialsStr, browserstackConfigFile, cypressConfigFile) { - /* User test config for build run */ - this.userConfigForReporting = { - framework: 'Cypress', - browserstackConfigFile: browserstackConfigFile, - cypressConfigFile: cypressConfigFile - }; - this.setCredentialsForCrashReportUpload(credentialsStr); - } - - uploadCrashReport(exception, stacktrace) { - try { - if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) { - return debug('[Crash_Report_Upload] Failed to parse user credentials while reporting crash') - } - - const data = { - hashed_id: process.env.BS_TESTOPS_BUILD_HASHED_ID, - observability_version: { - frameworkName: 'Cypress', - frameworkVersion: getPackageVersion('cypress', this.userConfigForReporting.browserstackConfigFile), - sdkVersion: getAgentVersion() - }, - exception: { - error: exception.toString(), - stackTrace: stacktrace - }, - config: this.userConfigForReporting - } - - const options = { - auth: { - ...this.credentialsForCrashReportUpload - }, - headers: { - 'Content-Type': 'application/json', - 'X-BSTACK-TESTOPS': 'true' - }, - method: 'POST', - url: `${API_URL}/api/v1/analytics`, - body: data, - json: true, - agent: httpsKeepAliveAgent - }; - - request(options, function callback(error, response, body) { - if(error) { - debug(`[Crash_Report_Upload] Failed due to ${error}`); - } else if(response.statusCode != 200) { - debug(`[Crash_Report_Upload] Failed due to ${response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`}`); - } else { - debug(`[Crash_Report_Upload] Success response: ${JSON.stringify({status: response.status, body: response.body})}`) - } - }); - } catch(e) { - debug(`[Crash_Report_Upload] Processing failed due to ${e && e.stack}`); - } - } -} - -module.exports = CrashReporter; diff --git a/bin/testObservability/cypress/index.js b/bin/testObservability/cypress/index.js deleted file mode 100644 index 31c8d08e..00000000 --- a/bin/testObservability/cypress/index.js +++ /dev/null @@ -1,158 +0,0 @@ -/* Event listeners + custom commands for Cypress */ - -/* Used to detect Gherkin steps */ -Cypress.on('log:added', (log) => { - return () => { - return cy.now('task', 'test_observability_step', { - log - }, {log: false}) - } -}); - -Cypress.on('command:start', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { - return; - } - /* Send command details */ - cy.now('task', 'test_observability_command', { - type: 'COMMAND_START', - command: { - attributes: { - id: command.attributes.id, - name: command.attributes.name, - args: command.attributes.args - }, - state: 'pending' - } - }, {log: false}).then((res) => { - }).catch((err) => { - }); - - /* Send platform details */ - cy.now('task', 'test_observability_platform_details', { - testTitle: Cypress.currentTest.title, - browser: Cypress.browser, - platform: Cypress.platform, - cypressVersion: Cypress.version - }, {log: false}).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.on('command:retry', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { - return; - } - cy.now('task', 'test_observability_command', { - type: 'COMMAND_RETRY', - command: { - _log: command._log, - error: { - message: command && command.error ? command.error.message : null, - isDefaultAssertionErr: command && command.error ? command.error.isDefaultAssertionErr : null - } - } - }, {log: false}).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.on('command:end', (command) => { - if(!command || !command.attributes) return; - if(command.attributes.name == 'log' || (command.attributes.name == 'task' && (command.attributes.args.includes('test_observability_command') || command.attributes.args.includes('test_observability_log')))) { - return; - } - cy.now('task', 'test_observability_command', { - 'type': 'COMMAND_END', - 'command': { - 'attributes': { - 'id': command.attributes.id, - 'name': command.attributes.name, - 'args': command.attributes.args - }, - 'state': command.state - } - }, {log: false}).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.overwrite('log', (originalFn, ...args) => { - if(args.includes('test_observability_log') || args.includes('test_observability_command')) return; - const message = args.reduce((result, logItem) => { - if (typeof logItem === 'object') { - return [result, JSON.stringify(logItem)].join(' '); - } - - return [result, logItem ? logItem.toString() : ''].join(' '); - }, ''); - cy.now('task', 'test_observability_log', { - 'level': 'info', - message, - }, {log: false}).then((res) => { - }).catch((err) => { - }); - originalFn(...args); -}); - -Cypress.Commands.add('trace', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'trace', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.add('logDebug', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'debug', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.add('info', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'info', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.add('warn', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'warn', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.add('error', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'error', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); - -Cypress.Commands.add('fatal', (message, file) => { - cy.now('task', 'test_observability_log', { - level: 'fatal', - message, - file, - }).then((res) => { - }).catch((err) => { - }); -}); diff --git a/bin/testObservability/helper/constants.js b/bin/testObservability/helper/constants.js deleted file mode 100644 index dbf5e053..00000000 --- a/bin/testObservability/helper/constants.js +++ /dev/null @@ -1,36 +0,0 @@ -const path = require('path'); - -exports.consoleHolder = Object.assign({},console); -exports.BATCH_SIZE = 1000; -exports.BATCH_INTERVAL = 2000; -exports.API_URL = 'https://collector-observability.browserstack.com'; - -exports.IPC_EVENTS = { - LOG: 'testObservability:cypressLog', - CONFIG: 'testObservability:cypressConfig', - SCREENSHOT: 'testObservability:cypressScreenshot', - COMMAND: 'testObservability:cypressCommand', - CUCUMBER: 'testObservability:cypressCucumberStep', - PLATFORM_DETAILS: 'testObservability:cypressPlatformDetails' -}; - -exports.OBSERVABILITY_ENV_VARS = [ - "BROWSERSTACK_TEST_OBSERVABILITY", - "BROWSERSTACK_AUTOMATION", - "BS_TESTOPS_BUILD_COMPLETED", - "BS_TESTOPS_JWT", - "BS_TESTOPS_BUILD_HASHED_ID", - "BS_TESTOPS_ALLOW_SCREENSHOTS", - "OBSERVABILITY_LAUNCH_SDK_VERSION", - "BROWSERSTACK_OBSERVABILITY_DEBUG", - "OBS_CRASH_REPORTING_USERNAME", - "OBS_CRASH_REPORTING_ACCESS_KEY", - "OBS_CRASH_REPORTING_BS_CONFIG_PATH", - "OBS_CRASH_REPORTING_CYPRESS_CONFIG_PATH" -]; - -exports.TEST_OBSERVABILITY_REPORTER = 'browserstack-cypress-cli/bin/testObservability/reporter'; - -exports.TEST_OBSERVABILITY_REPORTER_LOCAL = path.join(__dirname, '..', 'reporter'); - -exports.PENDING_QUEUES_FILE = `pending_queues_${process.pid}.json`; diff --git a/bin/testObservability/helper/helper.js b/bin/testObservability/helper/helper.js deleted file mode 100644 index 1b7da41f..00000000 --- a/bin/testObservability/helper/helper.js +++ /dev/null @@ -1,945 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const http = require('http'); -const https = require('https'); -const request = require('requestretry'); -const { v4: uuidv4 } = require('uuid'); -const os = require('os'); -const { promisify } = require('util'); -const gitconfig = require('gitconfiglocal'); -const { spawn, execSync } = require('child_process'); -const glob = require('glob'); -const util = require('util'); - -const { runOptions } = require('../../helpers/runnerArgs') - -const pGitconfig = promisify(gitconfig); - -const logger = require("../../helpers/logger").winstonLogger; -const utils = require('../../helpers/utils'); -const helper = require('../../helpers/helper'); - -const CrashReporter = require('../crashReporter'); - -// Getting global packages path -const GLOBAL_MODULE_PATH = execSync('npm root -g').toString().trim(); - -const { name, version } = require('../../../package.json'); - -const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../../helpers/constants'); -const { consoleHolder, API_URL, TEST_OBSERVABILITY_REPORTER, TEST_OBSERVABILITY_REPORTER_LOCAL } = require('./constants'); - -const ALLOWED_MODULES = [ - 'cypress/package.json', - 'mocha/lib/reporters/base.js', - 'mocha/lib/utils.js', - 'mocha' -]; - -exports.pending_test_uploads = { - count: 0 -}; - -exports.debugOnConsole = (text) => { - if ((process.env.BROWSERSTACK_OBSERVABILITY_DEBUG + '') === "true" || - (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG + '') === "1") { - consoleHolder.log(`[ OBSERVABILITY ] ${text}`); - } -} - -exports.debug = (text, shouldReport = false, throwable = null) => { - if (process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "true" || process.env.BROWSERSTACK_OBSERVABILITY_DEBUG === "1") { - logger.info(`[ OBSERVABILITY ] ${text}`); - } - if(shouldReport) { - CrashReporter.getInstance().uploadCrashReport(text, throwable ? throwable && throwable.stack : null); - } -} - -const supportFileContentMap = {}; - -exports.httpsKeepAliveAgent = new https.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const httpsScreenshotsKeepAliveAgent = new https.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const supportFileCleanup = () => { - Object.keys(supportFileContentMap).forEach(file => { - try { - if(typeof supportFileContentMap[file] === 'object') { - let fileOrDirpath = file; - if(supportFileContentMap[file].deleteSupportDir) { - fileOrDirpath = path.join(process.cwd(), 'cypress', 'support'); - } - helper.deleteSupportFileOrDir(fileOrDirpath); - } else { - fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'}); - } - } catch(e) { - exports.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e); - } - }); -} - -exports.buildStopped = false; - -exports.printBuildLink = async (shouldStopSession, exitCode = null) => { - if(!this.isTestObservabilitySession() || exports.buildStopped) return; - exports.buildStopped = true; - try { - if(shouldStopSession) { - supportFileCleanup(); - await this.stopBuildUpstream(); - } - try { - if(process.env.BS_TESTOPS_BUILD_HASHED_ID - && process.env.BS_TESTOPS_BUILD_HASHED_ID != "null" - && process.env.BS_TESTOPS_BUILD_HASHED_ID != "undefined") { - console.log(); - logger.info(`Visit 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(err) { - exports.debug('Build Not Found'); - } - } catch(err) { - exports.debug(`Error while stopping build with error : ${err}`, true, err); - } - if(exitCode) process.exit(exitCode); -} - -const nodeRequest = (type, url, data, config) => { - return new Promise(async (resolve, reject) => { - const options = {...config,...{ - method: type, - url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - agent: this.httpsKeepAliveAgent, - maxAttempts: 2 - }}; - - if(url === exports.requestQueueHandler.screenshotEventUrl) { - options.agent = httpsScreenshotsKeepAliveAgent; - } - - request(options, function callback(error, response, body) { - if(error) { - reject(error); - } else if(response.statusCode != 200) { - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); - } else { - try { - if(typeof(body) !== 'object') body = JSON.parse(body); - } catch(e) { - if(!url.includes('/stop')) { - reject('Not a JSON response from BrowserStack Server'); - } - } - resolve({ - data: body - }); - } - }); - }); -} - -exports.failureData = (errors,tag) => { - if(!errors) return []; - try { - if(tag === 'test') { - return errors.map((failure) => { - let {stack, ...expanded} = failure - let expandedArray = Object.keys(expanded).map((key) => { - return `${key}: ${expanded[key]}` - }) - return { backtrace: stack.split(/\r?\n/), expanded: expandedArray } - }) - } else if(tag === 'err') { - let failureArr = [], failureChildArr = []; - Object.keys(errors).forEach((key) => { - try { - failureChildArr.push(`${key}: ${errors[key]}`); - } catch(e) { - exports.debug(`Exception in populating test failure data with error : ${e.message} : ${e.backtrace}`, true, e); - } - }) - failureArr.push({ backtrace: errors.stack.split(/\r?\n/), expanded: failureChildArr }); - return failureArr; - } else { - return []; - } - } catch(e) { - exports.debug(`Exception in populating test failure data with error : ${e.message} : ${e.backtrace}`, true, e); - } - return []; -} - -exports.getTestEnv = () => { - return { - "ci": "generic", - "key": uuidv4(), - "version": version, - "collector": `js-${name}`, - } -} - -exports.getFileSeparatorData = () => { - return /^win/.test(process.platform) ? "\\" : "/"; -} - -exports.findGitConfig = (filePath) => { - const fileSeparator = exports.getFileSeparatorData(); - if(filePath == null || filePath == '' || filePath == fileSeparator) { - return null; - } - try { - fs.statSync(filePath + fileSeparator + '.git' + fileSeparator + 'config'); - return filePath; - } catch(e) { - let parentFilePath = filePath.split(fileSeparator); - parentFilePath.pop(); - return exports.findGitConfig(parentFilePath.join(fileSeparator)); - } -} - -let packages = {}; - -exports.getPackageVersion = (package_, bsConfig = null) => { - if(packages[package_]) return packages[package_]; - let packageVersion; - /* Try to find version from module path */ - try { - packages[package_] = this.requireModule(`${package_}/package.json`).version; - logger.info(`Getting ${package_} package version from module path = ${packages[package_]}`); - packageVersion = packages[package_]; - } catch(e) { - exports.debug(`Unable to find package ${package_} at module path with error ${e}`); - } - - /* Read package version from npm_dependencies in browserstack.json file if present */ - if(utils.isUndefined(packageVersion) && bsConfig && (process.env.BROWSERSTACK_AUTOMATION == "true" || process.env.BROWSERSTACK_AUTOMATION == "1")) { - const runSettings = bsConfig.run_settings; - if (runSettings && runSettings.npm_dependencies !== undefined && - Object.keys(runSettings.npm_dependencies).length !== 0 && - typeof runSettings.npm_dependencies === 'object') { - if (package_ in runSettings.npm_dependencies) { - packages[package_] = runSettings.npm_dependencies[package_]; - logger.info(`Getting ${package_} package version from browserstack.json = ${packages[package_]}`); - packageVersion = packages[package_]; - } - } - } - - /* Read package version from project's package.json if present */ - const packageJSONPath = path.join(process.cwd(), 'package.json'); - if(utils.isUndefined(packageVersion) && fs.existsSync(packageJSONPath)) { - const packageJSONContents = require(packageJSONPath); - if(packageJSONContents.devDependencies && !utils.isUndefined(packageJSONContents.devDependencies[package_])) packages[package_] = packageJSONContents.devDependencies[package_]; - if(packageJSONContents.dependencies && !utils.isUndefined(packageJSONContents.dependencies[package_])) packages[package_] = packageJSONContents.dependencies[package_]; - logger.info(`Getting ${package_} package version from package.json = ${packages[package_]}`); - packageVersion = packages[package_]; - } - - return packageVersion; -} - -const setEnvironmentVariablesForRemoteReporter = (BS_TESTOPS_JWT, BS_TESTOPS_BUILD_HASHED_ID, BS_TESTOPS_ALLOW_SCREENSHOTS, OBSERVABILITY_LAUNCH_SDK_VERSION) => { - process.env.BS_TESTOPS_JWT = BS_TESTOPS_JWT; - process.env.BS_TESTOPS_BUILD_HASHED_ID = BS_TESTOPS_BUILD_HASHED_ID; - process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = BS_TESTOPS_ALLOW_SCREENSHOTS; - process.env.OBSERVABILITY_LAUNCH_SDK_VERSION = OBSERVABILITY_LAUNCH_SDK_VERSION; -} - -const getCypressCommandEventListener = (isJS) => { - return isJS ? ( - `require('browserstack-cypress-cli/bin/testObservability/cypress');` - ) : ( - `import 'browserstack-cypress-cli/bin/testObservability/cypress'` - ) -} - -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(!file.includes('commands.js')) { - 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 parse support files to set event listeners with error ${e}`, true, e); - } -} - -const getCypressConfigFileContent = (bsConfig, cypressConfigPath) => { - try { - const cypressConfigFile = require(path.resolve(bsConfig ? bsConfig.run_settings.cypress_config_file : cypressConfigPath)); - if(bsConfig) process.env.OBS_CRASH_REPORTING_CYPRESS_CONFIG_PATH = bsConfig.run_settings.cypress_config_file; - return cypressConfigFile; - } catch(e) { - exports.debug(`Encountered an error when trying to import Cypress Config File ${e}`); - return {}; - } -} - -exports.setCrashReportingConfigFromReporter = (credentialsStr, bsConfigPath, cypressConfigPath) => { - try { - const browserstackConfigFile = utils.readBsConfigJSON(bsConfigPath); - const cypressConfigFile = getCypressConfigFileContent(null, cypressConfigPath); - - if(!credentialsStr) { - credentialsStr = JSON.stringify({ - username: process.env.OBS_CRASH_REPORTING_USERNAME, - password: process.env.OBS_CRASH_REPORTING_ACCESS_KEY - }); - } - CrashReporter.getInstance().setConfigDetails(credentialsStr, browserstackConfigFile, cypressConfigFile); - } catch(e) { - exports.debug(`Encountered an error when trying to set Crash Reporting Config from reporter ${e}`); - } -} - -const setCrashReportingConfig = (bsConfig, bsConfigPath) => { - try { - const browserstackConfigFile = utils.readBsConfigJSON(bsConfigPath); - const cypressConfigFile = getCypressConfigFileContent(bsConfig, null); - const credentialsStr = JSON.stringify({ - username: bsConfig["auth"]["username"], - password: bsConfig["auth"]["access_key"] - }); - CrashReporter.getInstance().setConfigDetails(credentialsStr, browserstackConfigFile, cypressConfigFile); - process.env.OBS_CRASH_REPORTING_USERNAME = bsConfig["auth"]["username"]; - process.env.OBS_CRASH_REPORTING_ACCESS_KEY = bsConfig["auth"]["access_key"]; - process.env.OBS_CRASH_REPORTING_BS_CONFIG_PATH = bsConfigPath ? path.relative(process.cwd(), bsConfigPath) : null; - } catch(e) { - exports.debug(`Encountered an error when trying to set Crash Reporting Config ${e}`); - } -} - -exports.launchTestSession = async (user_config, bsConfigPath) => { - setCrashReportingConfig(user_config, bsConfigPath); - - const obsUserName = user_config["auth"]["username"]; - const obsAccessKey = user_config["auth"]["access_key"]; - - const BSTestOpsToken = `${obsUserName || ''}:${obsAccessKey || ''}`; - if(BSTestOpsToken === '') { - exports.debug('EXCEPTION IN BUILD START EVENT : Missing authentication token', true, null); - process.env.BS_TESTOPS_BUILD_COMPLETED = false; - return [null, null]; - } else { - try { - const { - buildName, - projectName, - buildDescription, - buildTags - } = helper.getBuildDetails(user_config, true); - const data = { - 'format': 'json', - 'project_name': projectName, - 'name': buildName, - 'description': buildDescription, - 'start_time': (new Date()).toISOString(), - 'tags': buildTags, - 'host_info': { - hostname: os.hostname(), - platform: os.platform(), - type: os.type(), - version: os.version(), - arch: os.arch() - }, - 'ci_info': helper.getCiInfo(), - 'build_run_identifier': process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER, - 'failed_tests_rerun': process.env.BROWSERSTACK_RERUN || false, - 'version_control': await helper.getGitMetaData(), - 'observability_version': { - frameworkName: "Cypress", - frameworkVersion: exports.getPackageVersion('cypress', user_config), - sdkVersion: helper.getAgentVersion() - } - }; - const config = { - auth: { - username: obsUserName, - password: obsAccessKey - }, - headers: { - 'Content-Type': 'application/json', - 'X-BSTACK-TESTOPS': 'true' - } - }; - - const response = await nodeRequest('POST','api/v1/builds',data,config); - exports.debug('Build creation successfull!'); - process.env.BS_TESTOPS_BUILD_COMPLETED = true; - setEnvironmentVariablesForRemoteReporter(response.data.jwt, response.data.build_hashed_id, response.data.allow_screenshots, data.observability_version.sdkVersion); - if(this.isBrowserstackInfra()) helper.setBrowserstackCypressCliDependency(user_config); - } catch(error) { - if(!error.errorType) { - if (error.response) { - exports.debug(`EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); - } else { - exports.debug(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`, true, error); - } - } else { - const { errorType, message } = error; - switch (errorType) { - case 'ERROR_INVALID_CREDENTIALS': - logger.error(message); - break; - case 'ERROR_ACCESS_DENIED': - logger.info(message); - break; - case 'ERROR_SDK_DEPRECATED': - logger.error(message); - break; - default: - logger.error(message); - } - } - - process.env.BS_TESTOPS_BUILD_COMPLETED = false; - setEnvironmentVariablesForRemoteReporter(null, null, null); - } - } -} - -exports.getHookDetails = (hookTitle) => { - if(!hookTitle || typeof(hookTitle) != 'string') return [null, null]; - if(hookTitle.indexOf('hook:') !== -1) { - const hook_details = hookTitle.split('hook:'); - return [hook_details[0].slice(0,-1).split('"')[1], hook_details[1].substring(1)]; - } else if(hookTitle.indexOf('hook') !== -1) { - const hook_details = hookTitle.split('hook'); - return [hook_details[0].slice(0,-1).split('"')[1], hookTitle]; - } else { - return [null, null]; - } -} - -exports.getHooksForTest = (test) => { - if(!test || !test.parent) return []; - const hooksArr = []; - ['_beforeAll','_afterAll','_beforeEach','_afterEach'].forEach(hookType => { - let hooks = test.parent[hookType] || [] - hooks.forEach(testHook => { - if(testHook.hookAnalyticsId) hooksArr.push(testHook.hookAnalyticsId); - }) - }); - return [...hooksArr,...exports.getHooksForTest(test.parent)]; -} - -exports.mapTestHooks = (test) => { - if(!test || !test.parent) return; - ['_beforeAll','_afterAll','_beforeEach','_afterEach'].forEach(hookType => { - let hooks = test.parent[hookType] || [] - hooks.forEach(testHook => { - if(!testHook.hookAnalyticsId) { - testHook.hookAnalyticsId = uuidv4(); - } else if(testHook.markedStatus && hookType == '_afterEach') { - testHook.hookAnalyticsId = uuidv4(); - delete testHook.markedStatus; - } - testHook['test_run_id'] = testHook['test_run_id'] || test.testAnalyticsId; - }) - }); - exports.mapTestHooks(test.parent); -} - -exports.batchAndPostEvents = async (eventUrl, kind, data) => { - const config = { - headers: { - 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, - 'Content-Type': 'application/json', - 'X-BSTACK-TESTOPS': 'true' - } - }; - - try { - const eventsUuids = data.map(eventData => `${eventData.event_type}:${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)}`).join(', '); - exports.debugOnConsole(`[Request Batch Send] for events:uuids ${eventsUuids}`); - const response = await nodeRequest('POST',eventUrl,data,config); - exports.debugOnConsole(`[Request Batch Response] for events:uuids ${eventsUuids}`); - if(response.data.error) { - throw({message: response.data.error}); - } else { - exports.debug(`${kind} event successfull!`) - exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - data.length); - } - } catch(error) { - exports.debugOnConsole(`[Request Error] Error in sending request ${util.format(error)}`); - if (error.response) { - exports.debug(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); - } else { - exports.debug(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); - } - exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - data.length); - } -} - -const RequestQueueHandler = require('./requestQueueHandler'); -exports.requestQueueHandler = new RequestQueueHandler(); - -exports.uploadEventData = async (eventData, run=0) => { - exports.debugOnConsole(`[uploadEventData] ${eventData.event_type}`); - 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', - ['CBTSessionCreated']: 'CBT_Upload', - ['BuildUpdate']: 'Build_Update' - }[eventData.event_type]; - - if(run === 0 && process.env.BS_TESTOPS_JWT != "null") exports.pending_test_uploads.count += 1; - - if (process.env.BS_TESTOPS_BUILD_COMPLETED === "true") { - if(process.env.BS_TESTOPS_JWT == "null") { - exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); - exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count-1); - return { - status: 'error', - message: 'Token/buildID is undefined, build creation might have failed' - }; - } else { - let data = eventData, event_api_url = 'api/v1/event'; - - exports.requestQueueHandler.start(); - const { shouldProceed, proceedWithData, proceedWithUrl } = exports.requestQueueHandler.add(eventData); - exports.debugOnConsole(`[Request Queue] ${eventData.event_type} with uuid ${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)} is added`) - 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 eventsUuids = data.map(eventData => `${eventData.event_type}:${eventData.test_run ? eventData.test_run.uuid : (eventData.hook_run ? eventData.hook_run.uuid : null)}`).join(', '); - exports.debugOnConsole(`[Request Send] for events:uuids ${eventsUuids}`); - const response = await nodeRequest('POST',event_api_url,data,config); - exports.debugOnConsole(`[Request Repsonse] ${util.format(response.data)} for events:uuids ${eventsUuids}`) - if(response.data.error) { - throw({message: response.data.error}); - } else { - exports.debug(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'}[${run}] event successfull!`) - exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); - return { - status: 'success', - message: '' - }; - } - } catch(error) { - exports.debugOnConsole(`[Request Error] Error in sending request ${util.format(error)}`); - if (error.response) { - exports.debug(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); - } else { - exports.debug(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); - } - exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); - return { - status: 'error', - message: error.message || (error.response ? `${error.response.status}:${error.response.statusText}` : error) - }; - } - } - } else if (run >= 5) { - exports.debug(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : Build Start is not completed and ${log_tag} retry runs exceeded`); - if(process.env.BS_TESTOPS_JWT != "null") exports.pending_test_uploads.count = Math.max(0,exports.pending_test_uploads.count-1); - return { - status: 'error', - message: 'Retry runs exceeded' - }; - } else if(process.env.BS_TESTOPS_BUILD_COMPLETED !== "false") { - setTimeout(function(){ exports.uploadEventData(eventData, run+1) }, 1000); - } -} - -exports.isTestObservabilitySupportedCypressVersion = (cypress_config_filename) => { - const extension = cypress_config_filename.split('.').pop(); - return CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension); -} - -exports.setTestObservabilityFlags = (bsConfig) => { - /* testObservability */ - let isTestObservabilitySession = false; - try { - /* set default again but under try catch in case of wrong config */ - isTestObservabilitySession = utils.nonEmptyArray(bsConfig.run_settings.downloads) ? false : true; - - if(!utils.isUndefined(bsConfig["testObservability"])) isTestObservabilitySession = ( bsConfig["testObservability"] == true || bsConfig["testObservability"] == 1 ); - if(!utils.isUndefined(process.env.BROWSERSTACK_TEST_OBSERVABILITY)) isTestObservabilitySession = ( process.env.BROWSERSTACK_TEST_OBSERVABILITY == "true" || process.env.BROWSERSTACK_TEST_OBSERVABILITY == "1" ); - if(process.argv.includes('--disable-test-observability')) isTestObservabilitySession = false; - isTestObservabilitySession = isTestObservabilitySession && this.isTestObservabilitySupportedCypressVersion(bsConfig.run_settings.cypress_config_file); - } catch(e) { - isTestObservabilitySession = false; - exports.debug(`EXCEPTION while parsing testObservability capability with error ${e}`, true, e); - } - - /* browserstackAutomation */ - let isBrowserstackInfra = true; - try { - if(!utils.isUndefined(bsConfig["browserstackAutomation"])) isBrowserstackInfra = ( bsConfig["browserstackAutomation"] == true || bsConfig["browserstackAutomation"] == 1 ); - if(!utils.isUndefined(process.env.BROWSERSTACK_AUTOMATION)) isBrowserstackInfra = ( process.env.BROWSERSTACK_AUTOMATION == "true" || process.env.BROWSERSTACK_AUTOMATION == "1" ); - if(process.argv.includes('--disable-browserstack-automation')) isBrowserstackInfra = false; - } catch(e) { - isBrowserstackInfra = true; - exports.debug(`EXCEPTION while parsing browserstackAutomation capability with error ${e}`, true, e); - } - - if(isTestObservabilitySession) logger.warn("testObservability is set to true. Other test reporters you are using will be automatically disabled. Learn more at browserstack.com/docs/test-observability/overview/what-is-test-observability"); - - process.env.BROWSERSTACK_TEST_OBSERVABILITY = isTestObservabilitySession; - process.env.BROWSERSTACK_AUTOMATION = isBrowserstackInfra; - - return [isTestObservabilitySession, isBrowserstackInfra]; -} - -exports.isTestObservabilitySession = () => { - return ( process.env.BROWSERSTACK_TEST_OBSERVABILITY == "true" ); -} - -exports.isBrowserstackInfra = () => { - return ( process.env.BROWSERSTACK_AUTOMATION == "true" ); -} - -exports.shouldReRunObservabilityTests = () => { - return (process.env.BROWSERSTACK_RERUN_TESTS && process.env.BROWSERSTACK_RERUN_TESTS !== "null") ? true : false -} - -exports.stopBuildUpstream = async () => { - if (process.env.BS_TESTOPS_BUILD_COMPLETED === "true") { - if(process.env.BS_TESTOPS_JWT == "null" || process.env.BS_TESTOPS_BUILD_HASHED_ID == "null") { - exports.debug('EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : Missing authentication token'); - return { - status: 'error', - message: 'Token/buildID is undefined, build creation might have failed' - }; - } else { - const data = { - 'stop_time': (new Date()).toISOString() - }; - const config = { - headers: { - 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, - 'Content-Type': 'application/json', - 'X-BSTACK-TESTOPS': 'true' - } - }; - - try { - const response = await nodeRequest('PUT',`api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`,data,config); - if(response.data && response.data.error) { - throw({message: response.data.error}); - } else { - exports.debug(`stopBuildUpstream event successfull!`) - return { - status: 'success', - message: '' - }; - } - } catch(error) { - if (error.response) { - exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`, true, error); - } else { - exports.debug(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`, true, error); - } - return { - status: 'error', - message: error.message || error.response ? `${error.response.status}:${error.response.statusText}` : error - }; - } - } - } -} - -exports.getHookSkippedTests = (suite) => { - const subSuitesSkippedTests = suite.suites.reduce((acc, subSuite) => { - const subSuiteSkippedTests = exports.getHookSkippedTests(subSuite); - if (subSuiteSkippedTests) { - acc = acc.concat(subSuiteSkippedTests); - } - return acc; - }, []); - const tests = suite.tests.filter(test => { - const isSkippedTest = test.type != 'hook' && - !test.markedStatus && - test.state != 'passed' && - test.state != 'failed' && - !test.pending - return isSkippedTest; - }); - return tests.concat(subSuitesSkippedTests); -} - -const getPlatformName = () => { - if (process.platform === 'win32') return 'Windows' - if (process.platform === 'darwin') return 'OS X' - if (process.platform === "linux") return 'Linux' - return 'Unknown' -} - -const getMacOSVersion = () => { - return execSync("awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'macOS ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}'").toString().trim() -} - -exports.getOSDetailsFromSystem = async (product) => { - let platformName = getPlatformName(); - let platformVersion = os.release().toString(); - - switch (platformName) { - case 'OS X': - platformVersion = getMacOSVersion(); - break; - case 'Windows': - try { - const windowsRelease = (await import('windows-release')).default; - platformVersion = windowsRelease(); - } catch (e) { - } - break - case 'Linux': - try { - const details = await getLinuxDetails(); - if (details.dist) platformName = details.dist; - if (details.release) platformVersion = details.release.toString(); - } catch (e) { - } - break; - default: - break; - } - - return { - os: product == 'automate' && platformName == 'Linux' ? 'OS X' : platformName, - os_version: platformVersion - }; -} - -let WORKSPACE_MODULE_PATH; - -exports.requireModule = (module) => { - const modulePath = exports.resolveModule(module); - if (modulePath.error) { - throw new Error(`${module} doesn't exist.`); - } - - return require(modulePath.path); -}; - -exports.resolveModule = (module) => { - if (!ALLOWED_MODULES.includes(module)) { - throw new Error('Invalid module name'); - } - - if (WORKSPACE_MODULE_PATH == undefined) { - try { - WORKSPACE_MODULE_PATH = execSync('npm ls').toString().trim(); - WORKSPACE_MODULE_PATH = WORKSPACE_MODULE_PATH.split('\n')[0].split(' ')[1]; - } catch (e) { - WORKSPACE_MODULE_PATH = null; - exports.debug(`Could not locate npm module path with error ${e}`); - } - } - - /* - Modules will be resolved in the following order, - current working dir > workspaces dir > NODE_PATH env var > global node modules path - */ - - try { - exports.debug('requireModuleV2'); - - return {path: require.resolve(module), foundAt: 'resolve'}; - } catch (_) { - /* Find from current working directory */ - exports.debug(`Getting ${module} from ${process.cwd()}`); - let local_path = path.join(process.cwd(), 'node_modules', module); - if (!fs.existsSync(local_path)) { - exports.debug(`${module} doesn't exist at ${process.cwd()}`); - - /* Find from workspaces */ - if (WORKSPACE_MODULE_PATH) { - exports.debug(`Getting ${module} from path ${WORKSPACE_MODULE_PATH}`); - let workspace_path = null; - workspace_path = path.join(WORKSPACE_MODULE_PATH, 'node_modules', module); - if (workspace_path && fs.existsSync(workspace_path)) { - exports.debug(`Found ${module} from ${WORKSPACE_MODULE_PATH}`); - - return {path: workspace_path, foundAt: 'workspaces'}; - } - } - - /* Find from node path */ - let node_path = null; - if (!exports.isUndefined(process.env.NODE_PATH)) { - node_path = path.join(process.env.NODE_PATH, module); - } - if (node_path && fs.existsSync(node_path)) { - exports.debug(`Getting ${module} from ${process.env.NODE_PATH}`); - - return {path: node_path, foundAt: 'nodePath'}; - } - - /* Find from global node modules path */ - exports.debug(`Getting ${module} from ${GLOBAL_MODULE_PATH}`); - - let global_path = path.join(GLOBAL_MODULE_PATH, module); - if (!global_path || !fs.existsSync(global_path)) { - return {error: 'module_not_found'}; - } - - return {path: global_path, foundAt: 'local'}; - } - - return {path: local_path, foundAt: 'global'}; - } -}; - -const getReRunSpecs = (rawArgs) => { - let finalArgs = rawArgs; - if (this.isTestObservabilitySession() && this.shouldReRunObservabilityTests()) { - let startIdx = -1, numEle = 0; - for(let idx=0; idx item !== '--disable-test-observability' && item !== '--disable-browserstack-automation'); -} - -const getLocalSessionReporter = () => { - if(this.isTestObservabilitySession() && process.env.BS_TESTOPS_JWT) { - return ['--reporter', TEST_OBSERVABILITY_REPORTER_LOCAL]; - } else { - return []; - } -} - -const cleanupTestObservabilityFlags = (rawArgs) => { - const newArgs = []; - const aliasMap = Object.keys(runOptions).reduce( (acc, key) => { - const curr = runOptions[key]; - if (curr.alias) { - const aliases = Array.isArray(curr.alias) ? curr.alias : [curr.alias] - for (const alias of aliases) { - acc[alias] = curr; - } - } - return acc; - }, {}) - - const cliArgs = { - ...runOptions, - ...aliasMap - } - - // these flags are present in cypress too, but in some the same cli and - // cypress flags have different meaning. In that case, we assume user has - // given cypress related args - const retain = ['c', 'p', 'b', 'o', 's', 'specs', 'spec'] - - for (let i = 0;i < rawArgs.length;i++) { - const arg = rawArgs[i]; - if (arg.startsWith('-')) { - const argName = arg.length > 1 && arg[1] == '-' ? arg.slice(2) : arg.slice(1); - // If this flag belongs to cli, we omit it and its value - if (cliArgs[argName] && !retain.includes(argName)) { - const nextArg = i + 1 < rawArgs.length ? rawArgs[i+1] : '' - // if the flag is bound to have a value, we ignore it - if (cliArgs[argName].type && cliArgs[argName].type !== 'boolean' && !nextArg.startsWith('-')) { - i++; - } - continue; - } - } - newArgs.push(rawArgs[i]); - } - return newArgs; -} - -exports.runCypressTestsLocally = (bsConfig, args, rawArgs) => { - try { - rawArgs = cleanupTestObservabilityFlags(rawArgs); - logger.info(`Running npx cypress run ${getReRunSpecs(rawArgs.slice(1)).join(' ')} ${getLocalSessionReporter().join(' ')}`); - const cypressProcess = spawn( - 'npx', - ['cypress', 'run', ...getReRunSpecs(rawArgs.slice(1)), ...getLocalSessionReporter()], - { stdio: 'inherit', cwd: process.cwd(), env: process.env, shell: true } - ); - cypressProcess.on('close', async (code) => { - logger.info(`Cypress process exited with code ${code}`); - await this.printBuildLink(true); - }); - - cypressProcess.on('error', (err) => { - logger.info(`Cypress process encountered an error ${err}`); - }); - } catch(e) { - exports.debug(`Encountered an error when trying to spawn a Cypress test locally ${e}`, true, e); - } -} - -class PathHelper { - constructor(config, prefix) { - this.config = config - this.prefix = prefix - } - - relativeTestFilePath(testFilePath) { - // Based upon https://github.com/facebook/jest/blob/49393d01cdda7dfe75718aa1a6586210fa197c72/packages/jest-reporters/src/relativePath.ts#L11 - const dir = this.config.cwd || this.config.rootDir - return path.relative(dir, testFilePath) - } - - prefixTestPath(testFilePath) { - const relativePath = this.relativeTestFilePath(testFilePath) - return this.prefix ? path.join(this.prefix, relativePath) : relativePath - } -} -exports.PathHelper = PathHelper; diff --git a/bin/testObservability/helper/requestQueueHandler.js b/bin/testObservability/helper/requestQueueHandler.js deleted file mode 100644 index 172384f2..00000000 --- a/bin/testObservability/helper/requestQueueHandler.js +++ /dev/null @@ -1,101 +0,0 @@ -const fs = require('fs'); -const cp = require('child_process'); -const path = require('path'); - -const { BATCH_SIZE, BATCH_INTERVAL, PENDING_QUEUES_FILE } = require('./constants'); -const { batchAndPostEvents } = require('./helper'); - -class RequestQueueHandler { - constructor() { - this.queue = []; - this.started = false; - this.eventUrl = 'api/v1/batch'; - this.screenshotEventUrl = 'api/v1/screenshots'; - this.BATCH_EVENT_TYPES = ['LogCreated', 'CBTSessionCreated', 'TestRunFinished', 'TestRunSkipped', 'HookRunFinished', 'TestRunStarted', 'HookRunStarted', 'BuildUpdate']; - this.pollEventBatchInterval = null; - } - - start = () => { - if(!this.started) { - this.started = true; - this.startEventBatchPolling(); - } - } - - add = (event) => { - if(this.BATCH_EVENT_TYPES.includes(event.event_type)) { - if(event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') { - return { - shouldProceed: true, - proceedWithData: [event], - proceedWithUrl: this.screenshotEventUrl - } - } - - this.queue.push(event); - let data = null, shouldProceed = this.shouldProceed(); - if(shouldProceed) { - data = this.queue.slice(0,BATCH_SIZE); - this.queue.splice(0,BATCH_SIZE); - this.resetEventBatchPolling(); - } - - return { - shouldProceed: shouldProceed, - proceedWithData: data, - proceedWithUrl: this.eventUrl - } - } else { - return { - shouldProceed: true - } - } - } - - shutdownSync = () => { - this.removeEventBatchPolling('REMOVING'); - - fs.writeFileSync(path.join(__dirname, PENDING_QUEUES_FILE), JSON.stringify(this.queue)); - this.queue = []; - cp.spawnSync('node', [path.join(__dirname, 'cleanupQueueSync.js'), path.join(__dirname, PENDING_QUEUES_FILE)], {stdio: 'inherit'}); - fs.unlinkSync(path.join(__dirname, PENDING_QUEUES_FILE)); - } - - shutdown = async () => { - this.removeEventBatchPolling('REMOVING'); - while(this.queue.length > 0) { - const data = this.queue.slice(0,BATCH_SIZE); - this.queue.splice(0,BATCH_SIZE); - await batchAndPostEvents(this.eventUrl,'Shutdown-Queue',data); - } - } - - startEventBatchPolling = () => { - this.pollEventBatchInterval = setInterval(async () => { - if(this.queue.length > 0) { - const data = this.queue.slice(0,BATCH_SIZE); - this.queue.splice(0,BATCH_SIZE); - await batchAndPostEvents(this.eventUrl,'Interval-Queue',data); - } - }, BATCH_INTERVAL); - } - - resetEventBatchPolling = () => { - this.removeEventBatchPolling('RESETTING'); - this.startEventBatchPolling(); - } - - removeEventBatchPolling = (tag) => { - if(this.pollEventBatchInterval) { - clearInterval(this.pollEventBatchInterval); - this.pollEventBatchInterval = null; - if(tag === 'REMOVING') this.started = false; - } - } - - shouldProceed = () => { - return this.queue.length >= BATCH_SIZE; - } -} - -module.exports = RequestQueueHandler; diff --git a/bin/testObservability/plugin/index.js b/bin/testObservability/plugin/index.js deleted file mode 100644 index 6880eb75..00000000 --- a/bin/testObservability/plugin/index.js +++ /dev/null @@ -1,40 +0,0 @@ -const ipc = require('node-ipc'); -const { connectIPCClient } = require('./ipcClient'); -const { IPC_EVENTS } = require('../helper/constants'); - -const browserstackTestObservabilityPlugin = (on, config, callbacks) => { - connectIPCClient(config); - - on('task', { - test_observability_log(log) { - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.LOG, log); - return null; - }, - test_observability_command(commandObj) { - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.COMMAND, commandObj); - return null; - }, - test_observability_platform_details(platformObj) { - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.PLATFORM_DETAILS, platformObj); - return null; - }, - test_observability_step(log) { - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.CUCUMBER, log); - return null; - } - }); - - on('after:screenshot', (screenshotInfo) => { - let logMessage; - if (callbacks && callbacks.screenshotLogFn && typeof callbacks.screenshotLogFn === 'function') { - logMessage = callbacks.screenshotLogFn(screenshotInfo); - } - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.SCREENSHOT, { - logMessage, - screenshotInfo, - }); - return null; - }); -}; - -module.exports = browserstackTestObservabilityPlugin; diff --git a/bin/testObservability/plugin/ipcClient.js b/bin/testObservability/plugin/ipcClient.js deleted file mode 100644 index 5171f861..00000000 --- a/bin/testObservability/plugin/ipcClient.js +++ /dev/null @@ -1,16 +0,0 @@ -const ipc = require('node-ipc'); -const { IPC_EVENTS } = require('../helper/constants'); - -exports.connectIPCClient = (config) => { - ipc.config.id = 'browserstackTestObservability'; - ipc.config.retry = 1500; - ipc.config.silent = true; - - ipc.connectTo('browserstackTestObservability', () => { - ipc.of.browserstackTestObservability.on('connect', () => { - ipc.of.browserstackTestObservability.emit(IPC_EVENTS.CONFIG, config); - }); - ipc.of.browserstackTestObservability.on('disconnect', () => { - }); - }); -}; diff --git a/bin/testObservability/plugin/ipcServer.js b/bin/testObservability/plugin/ipcServer.js deleted file mode 100644 index 16fc7236..00000000 --- a/bin/testObservability/plugin/ipcServer.js +++ /dev/null @@ -1,38 +0,0 @@ -const ipc = require('node-ipc'); -const { consoleHolder } = require('../helper/constants'); -const { requestQueueHandler } = require('../helper/helper'); - -exports.startIPCServer = (subscribeServerEvents, unsubscribeServerEvents) => { - if (ipc.server) { - unsubscribeServerEvents(ipc.server); - subscribeServerEvents(ipc.server); - return; - } - ipc.config.id = 'browserstackTestObservability'; - ipc.config.retry = 1500; - ipc.config.silent = true; - - ipc.serve(() => { - - ipc.server.on('socket.disconnected', (socket, destroyedSocketID) => { - ipc.log(`client ${destroyedSocketID} has disconnected!`); - }); - - ipc.server.on('destroy', () => { - ipc.log('server destroyed'); - }); - - subscribeServerEvents(ipc.server); - - process.on('exit', () => { - unsubscribeServerEvents(ipc.server); - ipc.server.stop(); - // Cleaning up all remaining event in request queue handler. Any synchronous operations - // on exit handler will block the process - requestQueueHandler.shutdownSync(); - }); - - }); - - ipc.server.start(); -}; diff --git a/bin/testObservability/reporter/index.js b/bin/testObservability/reporter/index.js deleted file mode 100644 index d8c4cb9c..00000000 --- a/bin/testObservability/reporter/index.js +++ /dev/null @@ -1,762 +0,0 @@ -'use strict'; - -const util = require('util'); -const fs = require('fs'); -const path = require('path'); -const { requireModule } = require('../helper/helper'); -const Base = requireModule('mocha/lib/reporters/base.js'), - utils = requireModule('mocha/lib/utils.js'); -const color = Base.color; -const Mocha = requireModule('mocha'); -// const Runnable = requireModule('mocha/lib/runnable'); -const Runnable = require('mocha/lib/runnable'); // need to handle as this isn't present in older mocha versions -const { v4: uuidv4 } = require('uuid'); - -const { IPC_EVENTS } = require('../helper/constants'); -const { startIPCServer } = require('../plugin/ipcServer'); - -const HOOK_TYPES_MAP = { - "before all": "BEFORE_ALL", - "after all": "AFTER_ALL", - "before each": "BEFORE_EACH", - "after each": "AFTER_EACH", -} - -const { - EVENT_RUN_END, - EVENT_TEST_BEGIN, - EVENT_TEST_END, - EVENT_TEST_PENDING, - EVENT_RUN_BEGIN, - EVENT_TEST_FAIL, - EVENT_TEST_PASS, - EVENT_SUITE_BEGIN, - EVENT_SUITE_END, - EVENT_HOOK_BEGIN, - EVENT_HOOK_END -} = Mocha.Runner.constants; - -const { - STATE_PASSED, - STATE_PENDING, - STATE_FAILED, -} = Runnable.constants; - -const { - uploadEventData, - failureData, - PathHelper, - getTestEnv, - getHookDetails, - getHooksForTest, - mapTestHooks, - debug, - isBrowserstackInfra, - requestQueueHandler, - getHookSkippedTests, - getOSDetailsFromSystem, - findGitConfig, - getFileSeparatorData, - setCrashReportingConfigFromReporter, - debugOnConsole -} = require('../helper/helper'); - -const { consoleHolder } = require('../helper/constants'); - -// this reporter outputs test results, indenting two spaces per suite -class MyReporter { - constructor(runner, options) { - this.testObservability = true; - Base.call(this, runner, options); - this._testEnv = getTestEnv(); - this._paths = new PathHelper({ cwd: process.cwd() }, this._testEnv.location_prefix); - this.currentTestSteps = []; - this.currentTestCucumberSteps = []; - this.hooksStarted = {}; - this.beforeHooks = []; - this.platformDetailsMap = {}; - this.runStatusMarkedHash = {}; - this.haveSentBuildUpdate = false; - this.registerListeners(); - setCrashReportingConfigFromReporter(null, process.env.OBS_CRASH_REPORTING_BS_CONFIG_PATH, process.env.OBS_CRASH_REPORTING_CYPRESS_CONFIG_PATH); - - runner - .once(EVENT_RUN_BEGIN, async () => { - }) - - .on(EVENT_SUITE_BEGIN, (suite) => { - }) - - .on(EVENT_HOOK_BEGIN, async (hook) => { - debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_BEGIN`); - if(this.testObservability == true) { - if(!hook.hookAnalyticsId) { - hook.hookAnalyticsId = uuidv4(); - } else if(this.runStatusMarkedHash[hook.hookAnalyticsId]) { - delete this.runStatusMarkedHash[hook.hookAnalyticsId]; - hook.hookAnalyticsId = uuidv4(); - } - debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_BEGIN for uuid: ${hook.hookAnalyticsId}`); - hook.hook_started_at = (new Date()).toISOString(); - hook.started_at = (new Date()).toISOString(); - this.current_hook = hook; - await this.sendTestRunEvent(hook,undefined,false,"HookRunStarted"); - } - }) - - .on(EVENT_HOOK_END, async (hook) => { - debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_END`); - if(this.testObservability == true) { - if(!this.runStatusMarkedHash[hook.hookAnalyticsId]) { - if(!hook.hookAnalyticsId) { - /* Hook objects don't maintain uuids in Cypress-Mocha */ - hook.hookAnalyticsId = this.current_hook.hookAnalyticsId; - this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] = true; - } else { - this.runStatusMarkedHash[hook.hookAnalyticsId] = true; - } - - // Remove hooks added at hook start - delete this.hooksStarted[hook.hookAnalyticsId]; - - debugOnConsole(`[MOCHA EVENT] EVENT_HOOK_END for uuid: ${hook.hookAnalyticsId}`); - - await this.sendTestRunEvent(hook,undefined,false,"HookRunFinished"); - } - } - }) - - .on(EVENT_SUITE_END, (suite) => { - }) - - .on(EVENT_TEST_PASS, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PASS`); - if(this.testObservability == true) { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PASS for uuid: ${test.testAnalyticsId}`); - if(!this.runStatusMarkedHash[test.testAnalyticsId]) { - if(test.testAnalyticsId) this.runStatusMarkedHash[test.testAnalyticsId] = true; - await this.sendTestRunEvent(test); - } - } - }) - - .on(EVENT_TEST_FAIL, async (test, err) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_FAIL`); - if(this.testObservability == true) { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_FAIL for uuid: ${test.testAnalyticsId}`); - if((test.testAnalyticsId && !this.runStatusMarkedHash[test.testAnalyticsId]) || (test.hookAnalyticsId && !this.runStatusMarkedHash[test.hookAnalyticsId])) { - if(test.testAnalyticsId) { - this.runStatusMarkedHash[test.testAnalyticsId] = true; - await this.sendTestRunEvent(test,err); - } else if(test.hookAnalyticsId) { - this.runStatusMarkedHash[test.hookAnalyticsId] = true; - await this.sendTestRunEvent(test,err,false,"HookRunFinished"); - } - } - } - }) - - .on(EVENT_TEST_PENDING, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PENDING`); - if(this.testObservability == true) { - if(!test.testAnalyticsId) test.testAnalyticsId = uuidv4(); - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_PENDING for uuid: ${test.testAnalyticsId}`); - if(!this.runStatusMarkedHash[test.testAnalyticsId]) { - this.runStatusMarkedHash[test.testAnalyticsId] = true; - await this.sendTestRunEvent(test,undefined,false,"TestRunSkipped"); - } - } - }) - - .on(EVENT_TEST_BEGIN, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN`); - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN for uuid: ${test.testAnalyticsId}`); - if (this.runStatusMarkedHash[test.testAnalyticsId]) return; - if(this.testObservability == true) { - await this.testStarted(test); - } - }) - - .on(EVENT_TEST_END, async (test) => { - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_END`); - debugOnConsole(`[MOCHA EVENT] EVENT_TEST_BEGIN for uuid: ${test.testAnalyticsId}`); - if (this.runStatusMarkedHash[test.testAnalyticsId]) return; - if(this.testObservability == true) { - if(!this.runStatusMarkedHash[test.testAnalyticsId]) { - if(test.testAnalyticsId) this.runStatusMarkedHash[test.testAnalyticsId] = true; - await this.sendTestRunEvent(test); - } - } - }) - - .once(EVENT_RUN_END, async () => { - try { - debugOnConsole(`[MOCHA EVENT] EVENT_RUN_END`); - if(this.testObservability == true) { - const hookSkippedTests = getHookSkippedTests(this.runner.suite); - for(const test of hookSkippedTests) { - if(!test.testAnalyticsId) test.testAnalyticsId = uuidv4(); - debugOnConsole(`[MOCHA EVENT] EVENT_RUN_END TestRunSkipped for uuid: ${test.testAnalyticsId}`); - await this.sendTestRunEvent(test,undefined,false,"TestRunSkipped"); - } - } - } catch(err) { - debug(`Exception in populating test data for hook skipped test with error : ${err}`, true, err); - } - - await this.uploadTestSteps(); - }); - } - - registerListeners() { - startIPCServer( - (server) => { - server.on(IPC_EVENTS.CONFIG, this.cypressConfigListener.bind(this)); - server.on(IPC_EVENTS.LOG, this.cypressLogListener.bind(this)); - server.on(IPC_EVENTS.SCREENSHOT, this.cypressScreenshotListener.bind(this)); - server.on(IPC_EVENTS.COMMAND, this.cypressCommandListener.bind(this)); - server.on(IPC_EVENTS.CUCUMBER, this.cypressCucumberStepListener.bind(this)); - server.on(IPC_EVENTS.PLATFORM_DETAILS, this.cypressPlatformDetailsListener.bind(this)); - }, - (server) => { - server.off(IPC_EVENTS.CONFIG, '*'); - server.off(IPC_EVENTS.LOG, '*'); - server.off(IPC_EVENTS.SCREENSHOT, '*'); - }, - ); - } - - testStarted = async (test) => { - try { - const lastTest = this.current_test; - this.current_test = test; - test.retryOf = null; - test.testAnalyticsId = uuidv4(); - test.started_at = (new Date()).toISOString(); - test.test_started_at = test.started_at; - if(test._currentRetry > 0 && lastTest && lastTest.title == test.title) { - /* Sending async to current test start to avoid current test end call getting fired before its start call */ - test.retryOf = lastTest.testAnalyticsId - await this.sendTestRunEvent(test, undefined, false, "TestRunStarted"); - lastTest.state = STATE_FAILED; - await this.sendTestRunEvent(lastTest, undefined, true); - } else { - await this.sendTestRunEvent(test, undefined, false, "TestRunStarted"); - } - this.lastTest = lastTest; - } catch(err) { - debug(`Exception in populating test data for test start with error : ${err}`, true, err); - } - } - - uploadTestSteps = async (shouldClearCurrentSteps = true, cypressSteps = null) => { - const currentTestSteps = cypressSteps ? cypressSteps : JSON.parse(JSON.stringify(this.currentTestSteps)); - /* TODO - Send as test logs */ - const allStepsAsLogs = []; - currentTestSteps.forEach(step => { - const currentStepAsLog = { - test_run_uuid : step.test_run_uuid, - hook_run_uuid : step.hook_run_uuid, - timestamp: step.started_at, - duration: step.duration, - level: step.result, - message: step.text, - failure: step.failure, - failure_reason: step.failure_reason, - failure_type: step.failure_type, - kind: 'TEST_STEP', - http_response: {} - }; - allStepsAsLogs.push(currentStepAsLog); - }); - await uploadEventData({ - event_type: 'LogCreated', - logs: allStepsAsLogs - }); - if(shouldClearCurrentSteps) this.currentTestSteps = []; - } - - sendTestRunEvent = async (test, err = undefined, customFinished=false, eventType = "TestRunFinished") => { - try { - if(test.body && test.body.match(/browserstack internal helper hook/)) return; - let failureArgs = []; - if(test.state === STATE_FAILED || eventType.match(/HookRun/)) { - if(test.err !== undefined) { - failureArgs = test.err.multiple ? [test.err.multiple, 'test'] : [test.err, 'err']; - } else if(err !== undefined) { - failureArgs = [err, 'err']; - } else { - failureArgs = []; - } - } - - const failureReason = test.err !== undefined ? test.err.toString() : err !== undefined ? err.toString() : undefined; - if(eventType == 'TestRunFinished' && failureReason && this.currentTestCucumberSteps.length) { - this.currentTestCucumberSteps[this.currentTestCucumberSteps.length - 1] = { - ...this.currentTestCucumberSteps[this.currentTestCucumberSteps.length - 1], - result: 'failed' - } - } - - let rootParentFile; - try { - rootParentFile = this.getRootParentFile(test) - } catch(e) { - rootParentFile = null; - } - let gitConfigPath = process.env.OBSERVABILITY_GIT_CONFIG_PATH ? process.env.OBSERVABILITY_GIT_CONFIG_PATH.toString() : (rootParentFile ? findGitConfig(rootParentFile) : null); - if(!isBrowserstackInfra()) gitConfigPath = process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL ? process.env.OBSERVABILITY_GIT_CONFIG_PATH_LOCAL.toString() : null; - const prefixedTestPath = rootParentFile ? this._paths.prefixTestPath(rootParentFile) : 'File path could not be found'; - - const fileSeparator = getFileSeparatorData(); - - let testData = { - 'framework': 'Cypress', - 'uuid': (eventType.includes("Test") ? test.testAnalyticsId : test.hookAnalyticsId) || uuidv4(), - 'name': test.title, - 'body': { - 'lang': 'javascript', - 'code': test.body - }, - 'scope': this.scope(test), - 'scopes': this.scopes(test), - 'identifier': test.fullTitle(), - 'file_name': prefixedTestPath.replaceAll("\\", "/"), - 'vc_filepath': !isBrowserstackInfra() ? ( gitConfigPath ? path.relative(gitConfigPath, rootParentFile) : null ) : ( gitConfigPath ? ((gitConfigPath == 'DEFAULT' ? '' : gitConfigPath) + fileSeparator + rootParentFile).replaceAll("\\", "/") : null ), - 'location': prefixedTestPath.replaceAll("\\", "/"), - 'result': eventType === "TestRunSkipped" ? 'skipped' : ( eventType === "TestRunStarted" ? 'pending' : this.analyticsResult(test, eventType, err) ), - 'failure_reason': failureReason, - 'duration_in_ms': test.duration || (eventType.match(/Finished/) || eventType.match(/Skipped/) ? Date.now() - (new Date(test.started_at)).getTime() : null), - 'started_at': ( ( (eventType.match(/TestRun/) ? test.test_started_at : test.hook_started_at) || test.started_at ) || (new Date()).toISOString() ), - 'finished_at': eventType.match(/Finished/) || eventType.match(/Skipped/) ? (new Date()).toISOString() : null, - 'failure': failureData(...failureArgs), - 'failure_type': !failureReason ? null : failureReason.match(/AssertionError/) ? 'AssertionError' : 'UnhandledError', - 'retry_of': test.retryOf, - 'meta': { - steps: [] - } - }; - - debugOnConsole(`${eventType} for uuid: ${testData.uuid}`); - - if(eventType.match(/TestRunFinished/) || eventType.match(/TestRunSkipped/)) { - testData['meta'].steps = JSON.parse(JSON.stringify(this.currentTestCucumberSteps)); - this.currentTestCucumberSteps = []; - } - - const { os, os_version } = await getOSDetailsFromSystem(process.env.observability_product); - if(process.env.observability_integration) { - testData = {...testData, integrations: { - [process.env.observability_integration || 'local_grid' ]: { - 'build_id': process.env.observability_build_id, - 'session_id': process.env.observability_automate_session_id + btoa(prefixedTestPath.replaceAll("\\", "/")), - 'capabilities': {}, - 'product': process.env.observability_product, - 'platform': process.env.observability_os || os, - 'platform_version': process.env.observability_os_version || os_version, - 'browser': process.env.observability_browser, - 'browser_version': process.env.observability_browser_version - } - }}; - } else if(this.platformDetailsMap[process.pid] && this.platformDetailsMap[process.pid][test.title]) { - const {browser, platform} = this.platformDetailsMap[process.pid][test.title]; - testData = {...testData, integrations: { - 'local_grid': { - 'capabilities': {}, - 'platform': os, - 'platform_version': os_version, - 'browser': browser.name, - 'browser_version': browser.majorVersion - } - }}; - if(eventType === "TestRunFinished" || eventType === "TestRunSkipped") { - delete this.platformDetailsMap[process.pid][test.title]; - } - } - - if (eventType === "TestRunSkipped" && !testData['started_at']) { - testData['started_at'] = testData['finished_at']; - } - - try { - if(eventType.match(/HookRun/)) { - [testData.hook_type, testData.name] = getHookDetails(test.fullTitle() || test.originalTitle || test.title); - if(eventType === "HookRunFinished") { - if(testData.result === 'pending') testData.result = 'passed'; - if(testData.hook_type == 'before each' && testData.result === 'failed' && ( !this.runStatusMarkedHash[test.ctx.currentTest.testAnalyticsId] )) { - if(test.ctx.currentTest.testAnalyticsId) this.runStatusMarkedHash[test.ctx.currentTest.testAnalyticsId] = true; - test.ctx.currentTest.state = STATE_FAILED; - await this.sendTestRunEvent(test.ctx.currentTest,undefined,true); - } - } - if(testData.hook_type.includes('each')) { - testData['test_run_id'] = testData['test_run_id'] || test.testAnalyticsId; - } else if(testData.hook_type.includes('after')) { - testData['test_run_id'] = this.lastTest ? this.lastTest.testAnalyticsId : testData['test_run_id']; - } - } else if(eventType.match(/TestRun/)) { - mapTestHooks(test); - } - } catch(e) { - debugOnConsole(`Exception in processing hook data for event ${eventType} with error : ${e}`); - debug(`Exception in processing hook data for event ${eventType} with error : ${e}`, true, e); - } - - const failure_data = testData['failure'][0]; - if (failure_data) { - testData['failure_backtrace'] = failure_data['backtrace'] - testData['failure_reason_expanded'] = failure_data['expanded'] - } - - if(["TestRunFinished","TestRunSkipped"].includes(eventType)) { - testData.hooks = getHooksForTest(test); - } - - let uploadData = { - event_type: eventType === "TestRunSkipped" ? "TestRunFinished" : eventType, - } - - if(eventType == "HookRunFinished") delete testData.started_at; - - if(eventType.match(/HookRun/)) { - testData['hook_type'] = HOOK_TYPES_MAP[testData['hook_type']]; - uploadData['hook_run'] = testData; - } else { - uploadData['test_run'] = testData; - } - - if(eventType == 'HookRunFinished' && testData['hook_type'] == 'BEFORE_ALL') { - uploadData.cypressSteps = JSON.parse(JSON.stringify(this.currentTestSteps)); - this.beforeHooks.push(uploadData); - this.currentTestSteps = []; - } else { - await uploadEventData(uploadData); - - if(eventType.match(/Finished/)) { - await this.uploadTestSteps(); - } - - if(eventType.match(/TestRun/)) { - this.beforeHooks.forEach(async(hookUploadObj) => { - const currentTestSteps = hookUploadObj.cypressSteps; - delete hookUploadObj.cypressSteps; - hookUploadObj['hook_run']['test_run_id'] = test.testAnalyticsId; - await uploadEventData(hookUploadObj); - await this.uploadTestSteps(false, currentTestSteps); - }); - this.beforeHooks = []; - } - } - - if(!this.haveSentBuildUpdate && (process.env.observability_framework_version || this.currentCypressVersion)) { - this.shouldSendBuildUpdate = true; - const buildUpdateData = { - event_type: 'BuildUpdate', - 'misc': { - observability_version: { - frameworkName: "Cypress", - sdkVersion: process.env.OBSERVABILITY_LAUNCH_SDK_VERSION, - frameworkVersion: ( process.env.observability_framework_version || this.currentCypressVersion ) - } - } - }; - await uploadEventData(buildUpdateData); - } - - // Add started hooks to the hash - if(eventType === 'HookRunStarted' && ['BEFORE_EACH', 'AFTER_EACH', 'BEFORE_ALL'].includes(testData['hook_type'])) { - this.hooksStarted[testData.uuid] = uploadData; - } - - // Send pending hook finsihed events for hook starts - if (eventType === 'TestRunFinished' || eventType === 'TestRunSkipped') { - Object.values(this.hooksStarted).forEach(async hookData => { - hookData['event_type'] = 'HookRunFinished'; - hookData['hook_run'] = { - ...hookData['hook_run'], - result: uploadData['test_run'].result, - failure: uploadData['test_run'].failure, - failure_type: uploadData['test_run'].failure_type, - failure_reason: uploadData['test_run'].failure_reason, - failure_reason_expanded: uploadData['test_run'].failure_reason_expanded, - failure_backtrace: uploadData['test_run'].failure_backtrace - - } - - if (hookData['hook_run']['hook_type'] === 'BEFORE_ALL') { - hookData['hook_run'].finished_at = uploadData['test_run'].finished_at; - hookData['hook_run'].duration_in_ms = new Date(hookData['hook_run'].finished_at).getTime() - new Date(hookData['hook_run'].started_at).getTime(); - } else { - hookData['hook_run'].finished_at = hookData['hook_run'].started_at; - hookData['hook_run'].duration_in_ms = 0; - } - await uploadEventData(hookData); - }) - this.hooksStarted = {}; - } - } catch(error) { - debugOnConsole(`Exception in populating test data for event ${eventType} with error : ${error}`); - debug(`Exception in populating test data for event ${eventType} with error : ${error}`, true, error); - } - } - - appendTestItemLog = async (log) => { - try { - if(this.current_hook && ( this.current_hook.hookAnalyticsId && !this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] )) { - log.hook_run_uuid = this.current_hook.hookAnalyticsId; - } - if(!log.hook_run_uuid && this.current_test && ( this.current_test.testAnalyticsId && !this.runStatusMarkedHash[this.current_test.testAnalyticsId] )) log.test_run_uuid = this.current_test.testAnalyticsId; - if(log.hook_run_uuid || log.test_run_uuid) { - await uploadEventData({ - event_type: 'LogCreated', - logs: [log] - }); - } - } catch(error) { - debug(`Exception in uploading log data to Observability with error : ${error}`, true, error); - } - } - - cypressConfigListener = async (config) => { - } - - cypressCucumberStepListener = async ({log}) => { - if(log.name == 'step' && log.consoleProps && log.consoleProps.step && log.consoleProps.step.keyword) { - this.currentTestCucumberSteps = [ - ...this.currentTestCucumberSteps, - { - id: log.chainerId, - keyword: log.consoleProps.step.keyword, - text: log.consoleProps.step.text, - started_at: new Date().toISOString(), - finished_at: new Date().toISOString(), - duration: 0, - result: 'passed' - } - ]; - } else if(log.name == 'then' && log.type == 'child' && log.chainerId) { - this.currentTestCucumberSteps.forEach((gherkinStep, idx) => { - if(gherkinStep.id == log.chainerId) { - this.currentTestCucumberSteps[idx] = { - ...gherkinStep, - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(gherkinStep.started_at)).getTime(), - result: log.state, - failure: log.err?.stack || log.err?.message, - failure_reason: log.err?.stack || log.err?.message, - failure_type: log.err?.name || 'UnhandledError' - } - } - }) - } - } - - cypressLogListener = async ({level, message, file}) => { - this.appendTestItemLog({ - timestamp: new Date().toISOString(), - level: level.toUpperCase(), - message, - kind: 'TEST_LOG', - http_response: {} - }); - } - - cypressScreenshotListener = async ({logMessage, screenshotInfo}) => { - if(screenshotInfo.path) { - const screenshotAsBase64 = fs.readFileSync(screenshotInfo.path, {encoding: 'base64'}); - if(screenshotAsBase64) { - this.appendTestItemLog({ - timestamp: screenshotInfo.takenAt || new Date().toISOString(), - message: screenshotAsBase64, - kind: 'TEST_SCREENSHOT' - }); - } - } - } - - cypressPlatformDetailsListener = async({testTitle, browser, platform, cypressVersion}) => { - if(!process.env.observability_integration) { - this.platformDetailsMap[process.pid] = this.platformDetailsMap[process.pid] || {}; - if(testTitle) this.platformDetailsMap[process.pid][testTitle] = { browser, platform }; - } - this.currentCypressVersion = cypressVersion; - } - - getFormattedArgs = (args) => { - if(!args) return ''; - let res = ''; - args.forEach((val) => { - res = res + (res.length ? ', ' : '') + JSON.stringify(val); - }); - return res; - } - - cypressCommandListener = async ({type, command}) => { - if(!command || command?.attributes?.name == 'then') return; - - if(type == 'COMMAND_RETRY') { - command.id = command._log.chainerId; - } - - if(type == 'COMMAND_START') { - let isCommandPresent = null; - for(let idx=0; idx { - if(val.id == command.attributes.id) { - this.currentTestSteps[idx] = { - ...val, - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(val.started_at)).getTime(), - result: command.state - }; - stepUpdated = true; - } - }); - - if(!stepUpdated) { - /* COMMAND_END reported before COMMAND_START */ - const currentStepObj = { - id: command.attributes.id, - text: 'cy.' + command.attributes.name + '(' + this.getFormattedArgs(command.attributes.args) + ')', - started_at: new Date().toISOString(), - finished_at: new Date().toISOString(), - duration: 0, - result: command.state, - test_run_uuid: this.current_test?.testAnalyticsId && !this.runStatusMarkedHash[this.current_test.testAnalyticsId] ? this.current_test.testAnalyticsId : null, - hook_run_uuid : this.current_hook?.hookAnalyticsId && !this.runStatusMarkedHash[this.current_hook.hookAnalyticsId] ? this.current_hook.hookAnalyticsId : null - }; - if(currentStepObj.hook_run_uuid && currentStepObj.test_run_uuid) delete currentStepObj.test_run_uuid; - this.currentTestSteps = [ - ...this.currentTestSteps, - currentStepObj - ]; - } - } else if(type == 'COMMAND_RETRY') { - if(!command.id) return; - - let isRetryStepFound = false; - /* Parse steps array in reverse and update the last step with common chainerId */ - for(let idx=this.currentTestSteps.length-1; idx>=0; idx--) { - const val = this.currentTestSteps[idx]; - if(val.id.includes(command.id)) { - this.currentTestSteps[idx] = { - ...val, - failure: command?.error?.message, - failure_reason: command?.error?.message, - failure_type: command?.error?.isDefaultAssertionErr ? 'AssertionError' : 'UnhandledError', - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(val.started_at)).getTime(), - result: command?.error?.message ? 'failed' : 'pending' - }; - isRetryStepFound = true; - break; - } - } - - /* As a backup, parse steps array in reverse and update the last step with pending status */ - if(!isRetryStepFound) { - for(let idx=this.currentTestSteps.length-1; idx>=0; idx--) { - const val = this.currentTestSteps[idx]; - if(val.state == 'pending') { - this.currentTestSteps[idx] = { - ...val, - failure: command?.error?.message, - failure_reason: command?.error?.message, - failure_type: command?.error?.isDefaultAssertionErr ? 'AssertionError' : 'UnhandledError', - finished_at: new Date().toISOString(), - duration: Date.now() - (new Date(val.started_at)).getTime(), - result: command?.error?.message ? 'failed' : 'pending' - }; - isRetryStepFound = true; - break; - } - } - } - } - } - - analyticsResult(test, eventType, err) { - if(eventType.match(/HookRun/)) { - if(test.isFailed() || test.err || err) { - return 'failed'; - } else if(eventType == 'HookRunFinished') { - return 'passed'; - } else { - return 'pending'; - } - } else { - return { - [STATE_PASSED]: 'passed', - [STATE_PENDING]: 'pending', - [STATE_FAILED]: 'failed', - }[test.state] - } - } - - scope(test) { - const titlePath = test.titlePath() - // titlePath returns an array of the scope + the test title. - // as the test title is the last array item, we just remove it - // and then join the rest of the array as a space separated string - return titlePath.slice(0, titlePath.length - 1).join(' ') - } - - scopes(test) { - const titlePath = test.titlePath() - return titlePath.slice(0, titlePath.length - 1) - } - - // Recursively find the root parent, and return the parents file - // This is required as test.file can be undefined in some tests on cypress - getRootParentFile(test) { - if (test.file) { - return test.file - } - if(test.ctx) { - const ctxRes = (test.ctx.currentTest ? this.getRootParentFile(test.ctx.currentTest) : null); - if(ctxRes) return ctxRes; - } - if (test.parent) { - const parentRes = this.getRootParentFile(test.parent) || (test.parent.ctx && test.parent.ctx.currentTest ? this.getRootParentFile(test.parent.ctx.currentTest) : null); - if(parentRes) return parentRes; - - if(test.parent.suites && test.parent.suites.length > 0) { - test.parent.suites.forEach(suite => { - const suiteRes = suite.ctx ? this.getRootParentFile(suite.ctx) : null; - if(suiteRes) return suiteRes; - }); - } - } - - return null - } -} - -module.exports = MyReporter; diff --git a/package.json b/package.json index 3cdde6ea..ef4718e9 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,12 @@ "dependencies": { "archiver": "5.3.0", "async": "3.2.3", + "axios": "^1.7.7", + "axios-retry": "^3.5.0", "browserstack-local": "1.5.4", "chalk": "4.1.2", - "cli-progress": "3.10.0", + "cli-progress": "^3.10.0", + "form-data": "^4.0.0", "fs-extra": "8.1.0", "getmac": "5.20.0", "git-last-commit": "^1.0.1", @@ -26,8 +29,6 @@ "mocha": "^10.2.0", "mkdirp": "1.0.4", "node-ipc": "9.1.1", - "request": "2.88.2", - "requestretry": "7.1.0", "table": "5.4.6", "update-notifier": "7.0.0", "uuid": "8.3.2", diff --git a/test/unit/bin/commands/info.js b/test/unit/bin/commands/info.js index f486be44..f928084f 100644 --- a/test/unit/bin/commands/info.js +++ b/test/unit/bin/commands/info.js @@ -1,7 +1,7 @@ +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require('sinon'), - request = require('request'); + sinon = require('sinon'); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -49,7 +49,7 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.INFO; let errorCode = "api_deprecated"; - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, null); + let axiosStub = sandbox.stub(axios, "get").resolves({ status: 299 }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -68,7 +68,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -76,7 +75,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -91,9 +90,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.INFO; let errorCode = "api_deprecated"; - let requestStub = sandbox - .stub(request, "get") - .yields(null, { statusCode: 299 }, JSON.stringify(body)); + let axiosStub = sandbox + .stub(axios, "get") + .resolves({ status: 299, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -112,7 +111,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); getInitialDetailsStub.returns(Promise.resolve({})); @@ -120,7 +118,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -160,9 +158,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_failed_build_info"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 400 }, null); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 400 }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -181,7 +179,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -189,7 +186,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -210,9 +207,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_auth_failed"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 401 }, JSON.stringify(body_with_message)); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 401, data: body_with_message }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -231,14 +228,13 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); getInitialDetailsStub.returns(Promise.resolve({})); return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -254,9 +250,9 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.ERROR; let errorCode = "api_failed_build_info"; - let requestStub = sinon - .stub(request, "get") - .yields(null, { statusCode: 402 }, JSON.stringify(body)); + let axiosStub = sinon + .stub(axios, "get") + .resolves({ status: 402, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -275,7 +271,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -283,7 +278,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); @@ -325,7 +320,7 @@ describe("buildInfo", () => { let messageType = Constants.messageTypes.SUCCESS; let errorCode = null; - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, JSON.stringify(body)); + let axiosStub = sandbox.stub(axios, "get").resolves({ status: 200, data: body }); const info = proxyquire('../../../../bin/commands/info', { '../helpers/utils': { @@ -343,7 +338,6 @@ describe("buildInfo", () => { '../helpers/getInitialDetails': { getInitialDetails: getInitialDetailsStub, }, - request: {get: requestStub}, }); validateBstackJsonStub.returns(Promise.resolve(bsConfig)); @@ -351,7 +345,7 @@ describe("buildInfo", () => { return info(args, rawArgs) .then(function (_bsConfig) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(getConfigPathStub); sinon.assert.calledOnce(getInitialDetailsStub); diff --git a/test/unit/bin/commands/stop.js b/test/unit/bin/commands/stop.js index b8116053..6ee61753 100644 --- a/test/unit/bin/commands/stop.js +++ b/test/unit/bin/commands/stop.js @@ -1,7 +1,6 @@ const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, diff --git a/test/unit/bin/helpers/build.js b/test/unit/bin/helpers/build.js index 95208b8a..aafac700 100644 --- a/test/unit/bin/helpers/build.js +++ b/test/unit/bin/helpers/build.js @@ -1,7 +1,7 @@ +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -31,9 +31,9 @@ describe("build", () => { }); it("reject with error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + let axiosStub = sandbox + .stub(axios, "post") + .rejects(new Error("random error")); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -43,7 +43,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build.createBuild(bsConfig, "random_zip_file") @@ -51,7 +50,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error.message, "random error"); }); @@ -59,9 +58,9 @@ describe("build", () => { describe("handle API deprecated", () => { it("build is null", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 299 }, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 299 }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -71,7 +70,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -80,7 +78,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, Constants.userMessages.API_DEPRECATED); }); @@ -88,9 +86,9 @@ describe("build", () => { it("build is not null", () => { let build_message = "random message"; - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 299 }, JSON.stringify({ message: build_message })); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 299, data: { message: build_message} }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -99,13 +97,12 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build .createBuild(bsConfig, "random_zip_file") .then(function (data) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(data, "random message"); }) @@ -117,9 +114,9 @@ describe("build", () => { describe("handle statusCode != 201", () => { it("build is null", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 400 }, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 400 }); const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { @@ -129,7 +126,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -138,7 +134,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, Constants.userMessages.BUILD_FAILED); }); @@ -146,12 +142,10 @@ describe("build", () => { it("build is not null", () => { let build_message = "random message"; - let requestStub = sandbox - .stub(request, "post") - .yields( - null, - { statusCode: 401 }, - JSON.stringify({ message: build_message }) + let axiosStub = sandbox + .stub(axios, "post") + .resolves( + { status: 401, data: { message: build_message }} ); const build = proxyquire("../../../../bin/helpers/build", { @@ -162,7 +156,6 @@ describe("build", () => { "../helpers/capabilityHelper": { caps: capsStub, }, - request: { post: requestStub }, }); return build @@ -171,7 +164,7 @@ describe("build", () => { chai.assert.fail("Promise error"); }) .catch((error) => { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); chai.assert.equal(error, `${Constants.userMessages.BUILD_FAILED} Error: ${build_message}`); }); @@ -181,32 +174,32 @@ describe("build", () => { it("build created successfuly", () => { let build_id = "random build id"; let build_message = "success" - let requestData = { message: build_message, build_id: build_id }; - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 201 }, JSON.stringify(requestData)); + let axiosData = { message: build_message, build_id: build_id }; + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 201, data: axiosData }); let dashboardUrl = "dashboard-url"; const build = proxyquire("../../../../bin/helpers/build", { "../helpers/utils": { getUserAgent: getUserAgentStub, + formatRequest, }, "../helpers/capabilityHelper": { caps: capsStub, }, "./config": { dashboardUrl: dashboardUrl, - }, - request: { post: requestStub }, + }, }); return build .createBuild(bsConfig, "random_zip_file") .then(function (data) { - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); - chai.assert.equal(data, `${requestData}`); + chai.assert.equal(data, `${axiosData}`); }) .catch((error) => { chai.assert.isNotOk(error, "Promise error"); diff --git a/test/unit/bin/helpers/checkUploaded.js b/test/unit/bin/helpers/checkUploaded.js index 6d98a5ab..503ece9a 100644 --- a/test/unit/bin/helpers/checkUploaded.js +++ b/test/unit/bin/helpers/checkUploaded.js @@ -1,8 +1,8 @@ 'use strict'; +const { default: axios } = require("axios"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require("sinon"), - request = require("request"); + sinon = require("sinon"); const logger = require("../../../../bin/helpers/logger").winstonLogger, testObjects = require("../../support/fixtures/testObjects"); @@ -37,14 +37,14 @@ describe("checkUploaded", () => { } }); - it("resolves with zipUrlPresent false due to request error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + it("resolves with zipUrlPresent false due to axios error", () => { + let axiosStub = sandbox + .stub(axios, "post") + .resolves(new Error("random error")); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -53,7 +53,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zip_md5sum, 'random_md5sum'); chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -62,14 +62,14 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false due to checkSpecsMd5 error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("random error"), null, null); + let axiosStub = sandbox + .stub(axios, "post") + .resolves(new Error("random error")); let checkSpecsMd5ErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5ErrorStub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -78,7 +78,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); chai.assert.equal(data.error, "test error stack"); - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.calledOnce(checkSpecsMd5ErrorStub); }) .catch((_error) => { @@ -87,13 +87,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false due to parsing error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: '{"zipUrl":"bs://random_hashid}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); @@ -102,7 +102,7 @@ describe("checkUploaded", () => { chai.assert.equal(data.zipUrlPresent, false); chai.assert.equal(data.packageUrlPresent, false); chai.assert.equal(data.zip_md5sum, "random_md5sum"); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -111,20 +111,20 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent true and zip url", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: {"zipUrl":"bs://random_hashid"} }); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub }); let checkUploadedMd5rewire = checkUploaded.__get__('checkUploadedMd5'); return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', zipUrlPresent: true, packageUrlPresent: false, zipUrl: 'bs://random_hashid' }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -133,13 +133,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent true, packageUrlPresent true, zip url, and packge url", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"zipUrl":"bs://random_hashid", "npmPackageUrl":"bs://random_hashid2"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: {"zipUrl":"bs://random_hashid", "npmPackageUrl":"bs://random_hashid2"}}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -148,7 +148,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', npm_package_md5sum: 'random_md5sum', zipUrlPresent: true, packageUrlPresent: true, zipUrl: 'bs://random_hashid', npmPackageUrl: 'bs://random_hashid2' }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -157,13 +157,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false as not found in db", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 404 }, '{"message":"zip_url for md5sum random_md5sum not found."}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 404 , data: {"message":"zip_url for md5sum random_md5sum not found."}}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -172,7 +172,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zip_md5sum: 'random_md5sum', zipUrlPresent: false, packageUrlPresent: false, }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { @@ -181,13 +181,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent and packageUrlPresent false if force-upload enabled and cache_dependencies disabled", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 404 }, '{"message":"zip_url for md5sum random_md5sum not found."}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 404 , data: '{"message":"zip_url for md5sum random_md5sum not found."}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -196,7 +196,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {"force-upload": true}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.notCalled(checkSpecsMd5Stub); }) .catch((_error) => { @@ -205,13 +205,13 @@ describe("checkUploaded", () => { }); it("resolves with zipUrlPresent false and packageUrlPresent false if force-upload enabled and cache_dependencies enabled", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"npmPackageUrl":"bs://random_hashid2"}'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: '{"npmPackageUrl":"bs://random_hashid2"}'}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -220,23 +220,22 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {"force-upload": true}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.notCalled(requestStub); + sinon.assert.notCalled(axiosStub); sinon.assert.notCalled(checkSpecsMd5Stub); }) .catch((_error) => { - console.log(_error) chai.assert.fail("Promise error"); }); }); it("resolves with zipUrlPresent false and packageUrlPresent false if diabled from rails", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{"disableNpmSuiteCache": true, "disableTestSuiteCache": true }'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves( { status: 200 , data: {"disableNpmSuiteCache": true, "disableTestSuiteCache": true }}); const checkUploaded = rewire("../../../../bin/helpers/checkUploaded"); checkUploaded.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, checkSpecsMd5: checkSpecsMd5Stub, checkPackageMd5: checkPackageMd5Stub }); @@ -245,7 +244,7 @@ describe("checkUploaded", () => { return checkUploadedMd5rewire(bsConfig, {}, instrumentBlocks) .then((data) => { chai.assert.deepEqual(data, { zipUrlPresent: false, packageUrlPresent: false }) - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(checkSpecsMd5Stub); }) .catch((_error) => { diff --git a/test/unit/bin/helpers/getInitialDetails.js b/test/unit/bin/helpers/getInitialDetails.js index b506e150..617a9f7f 100644 --- a/test/unit/bin/helpers/getInitialDetails.js +++ b/test/unit/bin/helpers/getInitialDetails.js @@ -1,8 +1,8 @@ +const { default: axios } = require("axios"); const { expect } = require("chai"); const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), - sinon = require('sinon'), - request = require('request'); + sinon = require('sinon'); const Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -17,13 +17,13 @@ describe('#getInitialDetails', () => { let args = testObjects.buildInfoSampleArgs; let rawArgs = testObjects.buildInfoSampleRawArgs; let bsConfig = testObjects.sampleBsConfig; - let sendUsageReportStub = null, requestStub = null, formatRequestStub = null; + let sendUsageReportStub = null, axiosStub = null, formatRequestStub = null; let messageType = Constants.messageTypes.ERROR; let errorCode = 'get_initial_details_failed' beforeEach(() => { sendUsageReportStub = sinon.stub(utils, 'sendUsageReport'); - requestStub = sinon.stub(request, "get"); + axiosStub = sinon.stub(axios, "get"); formatRequestStub = sinon.stub(utils, "formatRequest"); }); @@ -33,8 +33,8 @@ describe('#getInitialDetails', () => { it('sends usage report if error occurred in getInitialDetails call', () => { let error_message = "error occurred"; - requestStub.yields(error_message, null, null); - formatRequestStub.returns({err: error_message, statusCode: null, body: null}) + axiosStub.resolves(error_message); + formatRequestStub.returns({err: error_message, status: null, body: null}) sendUsageReportStub.calledOnceWithExactly(bsConfig, args, error_message, messageType, errorCode, null, rawArgs); getInitialDetails(bsConfig, args, rawArgs).then((result) => { expect(result).to.eq({}); @@ -43,7 +43,7 @@ describe('#getInitialDetails', () => { it('resolves with data if getInitialDetails call is successful', () => { let body = {"user_id": 1234}; - requestStub.yields(null, { statusCode: 200 }, body); + axiosStub.resolves({ status: 200 , data: body}); formatRequestStub.notCalled; sendUsageReportStub.notCalled; getInitialDetails(bsConfig, args, rawArgs).then((result) => { @@ -53,7 +53,7 @@ describe('#getInitialDetails', () => { it('send usage report if error response from getInitialDetails call', () => { let body = {"error": 'user not found'}; - requestStub.yields(null, { statusCode: 422 }, body); + axiosStub.resolves({ status: 422 , data: body}); formatRequestStub.notCalled; sendUsageReportStub.calledOnceWithExactly(bsConfig, args, body["error"], messageType, errorCode, null, rawArgs); getInitialDetails(bsConfig, args, rawArgs).then((result) => { diff --git a/test/unit/bin/helpers/reporterHTML.js b/test/unit/bin/helpers/reporterHTML.js index 56276464..1a0043e6 100644 --- a/test/unit/bin/helpers/reporterHTML.js +++ b/test/unit/bin/helpers/reporterHTML.js @@ -1,12 +1,13 @@ -const { expect } = require("chai"); -const chai = require("chai"), - chaiAsPromised = require("chai-as-promised"), +const { default: axios } = require('axios'); +const chai = require('chai'), + chaiAsPromised = require('chai-as-promised'), sinon = require('sinon'), rewire = require('rewire'); const fs = require('fs'), path = require('path'), request = require('request'), + unzipper = require('unzipper'), decompress = require('decompress'); Constants = require("../../../../bin/helpers/constants"), logger = require("../../../../bin/helpers/logger").winstonLogger, @@ -15,222 +16,236 @@ const fs = require('fs'), const proxyquire = require("proxyquire").noCallThru(); +const utils = require('../../../../bin/helpers/utils'); +const reporterHTML = require('../../../../bin/helpers/reporterHTML'); chai.use(chaiAsPromised); -logger.transports["console.info"].silent = true; +logger.transports['console.info'].silent = true; -describe("getReportResponse", () => { - var sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - sinon.restore(); - }); - - it("retrieve response from report url", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, "abc"); - let reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - request: {get: requestStub} - }); - let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); - let getReportResponse = rewireReporterHTML.__get__("getReportResponse"); - let unzipFileStub = sinon.stub(); - let writerStub = sinon.stub(fs, "createWriteStream"); - let unlinkSyncStub = sinon.stub(fs, "unlinkSync"); - let pathStub = sinon.stub(path, 'join'); - unzipFileStub.returns(true); - rewireReporterHTML.__set__('unzipFile', unzipFileStub); - pathStub.returns("abc/efg"); - writerStub.returns(true); - unlinkSyncStub.returns(true); - getReportResponse("abc", "efg", "url"); - sinon.assert.calledOnce(requestStub); - }) -}); +describe('reporterHTML', () => { + let sendUsageReportStub = null, + axiosStub = null, + formatRequestStub = null, + getUserAgentStub = null; -describe("calls API to generate report", () => { - var sandbox,sendUsageReportStub,sendUsageReportStub; - let args = testObjects.generateReportInputArgs, - rawArgs = testObjects.generateReportInputRawArgs - buildId = 'buildId', - bsConfig = testObjects.sampleBsConfig; - beforeEach(() => { - sandbox = sinon.createSandbox(); - sendUsageReportStub = sandbox.stub().callsFake(function () { - return "end"; - }); - getUserAgentStub = sandbox.stub().returns("random user-agent"); + getUserAgentStub = sinon.stub(utils, 'getUserAgent'); + sendUsageReportStub = sinon.stub(utils, 'sendUsageReport'); + axiosStub = sinon.stub(axios, 'get'); + formatRequestStub = sinon.stub(utils, 'formatRequest'); }); afterEach(() => { - sandbox.restore(); sinon.restore(); }); - it("is deprecated, i.e. 299, does not have build message", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, null); - let message = Constants.userMessages.API_DEPRECATED; - let messageType = Constants.messageTypes.INFO; - let errorCode = "api_deprecated"; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + describe('getReportResponse', () => { + it('retrieve response from report url', () => { + axiosStub.resolves({ status: 200, data: 'abc' }); + let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); + let getReportResponse = rewireReporterHTML.__get__('getReportResponse'); + let unzipFileStub = sinon.stub(); + let writerStub = sinon.stub(fs, 'createWriteStream'); + let unlinkSyncStub = sinon.stub(fs, 'unlinkSync'); + let pathStub = sinon.stub(path, 'join'); + unzipFileStub.returns(true); + rewireReporterHTML.__set__('unzipFile', unzipFileStub); + pathStub.returns('abc/efg'); + writerStub.returns(true); + unlinkSyncStub.returns(true); + getReportResponse('abc', 'efg', 'url'); + sinon.assert.calledOnce(axiosStub); + pathStub.restore(); }); - - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); - it("is deprecated, i.e. 299", () => { - let build = { buildId: buildId, message: 'API has been deprecated', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 299 }, body); - let message = build.message; - let messageType = Constants.messageTypes.INFO; - let errorCode = "api_deprecated"; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + describe('calls API to generate report', () => { + let args = testObjects.generateReportInputArgs, + rawArgs = testObjects.generateReportInputRawArgs; + (buildId = 'buildId'), (bsConfig = testObjects.sampleBsConfig); + + it('is deprecated, i.e. 299, does not have build message', () => { + axiosStub.resolves({ status: 299 }); + let message = Constants.userMessages.API_DEPRECATED; + let messageType = Constants.messageTypes.INFO; + let errorCode = 'api_deprecated'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); - }); - - context("non 200 response", () => { - it("422 status, build available but running, cannot generate report", () => { - let build = { message: 'The report cannot be generated as the build is running' }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 422 }, body); + it('is deprecated, i.e. 299', () => { + let build = { + buildId: buildId, + message: 'API has been deprecated', + rows: [], + }; + axiosStub.resolves({ status: 299, data: build }); let message = build.message; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} + let messageType = Constants.messageTypes.INFO; + let errorCode = 'api_deprecated'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); }); - - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); - it("400 status, build available, cannot generate report", () => { - let build = { buildId: buildId, message: 'success', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 400 }, body); - let message = `${ - Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId) - } with error: \n${JSON.stringify(build, null, 2)}`; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} + context('non 200 response', () => { + it('422 status, build available but running, cannot generate report', () => { + let build = { + message: 'The report cannot be generated as the build is running', + }; + axiosStub.resolves({ status: 422, data: build }); + let message = build.message; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); - }); - - it("user is unauthorized", () => { - let build = { buildId: buildId, message: 'Unauthorized', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 401 }, body); - let message = `${ - Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId) - } with error: \n${JSON.stringify(build, null, 2)}`; - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_auth_failed'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + it('400 status, build available, cannot generate report', () => { + let build = { buildId: buildId, message: 'success', rows: [] }; + axiosStub.resolves({ status: 400, data: build }); + let message = `${Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace( + '', + buildId + )} with error: \n${JSON.stringify(build, null, 2)}`; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); }); - reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); + it('user is unauthorized', () => { + let build = { buildId: buildId, message: 'Unauthorized', rows: [] }; + let body = build; + axiosStub.resolves({ status: 401, data: body }); + let message = `${Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace( + '', + buildId + )} with error: \n${JSON.stringify(build, null, 2)}`; + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_auth_failed'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); + }); - sinon.assert.calledOnce(requestStub); - sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); + it('400 status, build not available, cannot generate report', () => { + axiosStub.resolves({ status: 400 }); + let message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); + let messageType = Constants.messageTypes.ERROR; + let errorCode = 'api_failed_build_generate_report'; + + reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}).then(() => { + sinon.assert.calledOnce(axiosStub); + sinon.assert.calledOnce(getUserAgentStub); + sinon.assert.calledOnceWithExactly( + sendUsageReportStub, + bsConfig, + args, + message, + messageType, + errorCode, + {}, + rawArgs + ); + }); + }); }); - it("400 status, build not available, cannot generate report", () => { - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 400 }, null); - let message = Constants.userMessages.BUILD_GENERATE_REPORT_FAILED.replace('', buildId); - let messageType = Constants.messageTypes.ERROR; - let errorCode = 'api_failed_build_generate_report'; - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub, - formatRequest - }, - request: {get: requestStub} - }); + it('200 response code', () => { + let build = { buildId: buildId, message: 'success', rows: [] }; + axiosStub.resolves({ status: 200, data: 'abc' }); + let message = `Report for build: ${buildId} was successfully created.`; + let messageType = Constants.messageTypes.SUCCESS; + let errorCode = null; + let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); + let generateCypressBuildReportStub = sinon.stub(); + generateCypressBuildReportStub.returns(true); + rewireReporterHTML.__set__('generateCypressBuildReport', generateCypressBuildReportStub); reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); - sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, {}, rawArgs); + sendUsageReportStub.calledOnceWithExactly(bsConfig, args, message, messageType, errorCode, {}, rawArgs); }); }); - it("200 response code", () => { - let build = { buildId: buildId, message: 'success', rows: [] }; - let body = JSON.stringify(build); - let requestStub = sandbox.stub(request, "get").yields(null, { statusCode: 200 }, "abc"); - let message = `Report for build: ${buildId} was successfully created.`; - let messageType = Constants.messageTypes.SUCCESS; - let errorCode = null; - let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); - let generateCypressBuildReportStub = sinon.stub(); - generateCypressBuildReportStub.returns(true); - rewireReporterHTML.__set__('generateCypressBuildReport', generateCypressBuildReportStub); - - const reporterHTML = proxyquire('../../../../bin/helpers/reporterHTML', { - './utils': { - sendUsageReport: sendUsageReportStub, - getUserAgent: getUserAgentStub - }, - request: {get: requestStub} + describe('generateCypressBuildReport', () => { + it('calls cypress build report with report download url', () => { + let pathStub = sinon.stub(path, 'join'); + let fileExistStub = sinon.stub(fs, 'existsSync'); + let rewireReporterHTML = rewire('../../../../bin/helpers/reporterHTML'); + let generateCypressBuildReport = rewireReporterHTML.__get__('generateCypressBuildReport'); + let getReportResponseStub = sinon.stub(); + getReportResponseStub.calledOnceWith('abc/efg', 'report.zip', 'url'); + rewireReporterHTML.__set__('getReportResponse', getReportResponseStub); + pathStub.returns('abc/efg'); + fileExistStub.returns(true); + generateCypressBuildReport({ report_data: 'url' }); + pathStub.restore(); }); reporterHTML.reportGenerator(bsConfig, buildId, args, rawArgs, {}); diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index e19a10c2..5f5ac969 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -1,22 +1,22 @@ -"use strict"; -const chai = require("chai"), +'use strict'; +const chai = require('chai'), expect = chai.expect, - rewire = require("rewire"), - chaiAsPromised = require("chai-as-promised"), - chalk = require('chalk'), - request = require("request"); + rewire = require('rewire'), + chaiAsPromised = require('chai-as-promised'), + chalk = require('chalk'); -const sinon = require("sinon"); +const { default: axios } = require('axios'); +const sinon = require('sinon'); chai.use(chaiAsPromised); -var syncSpecsLogs = rewire("../../../../../bin/helpers/sync/syncSpecsLogs.js"); -var logger = require("../../../../../bin/helpers/logger").syncCliLogger; -var Constants = require("../../../../../bin/helpers/constants.js"); -var config = require("../../../../../bin/helpers/config.js"); -var utils = require("../../../../../bin/helpers/utils"); +var syncSpecsLogs = rewire('../../../../../bin/helpers/sync/syncSpecsLogs.js'); +var logger = require('../../../../../bin/helpers/logger').syncCliLogger; +var Constants = require('../../../../../bin/helpers/constants.js'); +var config = require('../../../../../bin/helpers/config.js'); +var utils = require('../../../../../bin/helpers/utils'); -describe("syncSpecsLogs", () => { +describe('syncSpecsLogs', () => { var sandbox; beforeEach(() => { @@ -29,115 +29,131 @@ describe("syncSpecsLogs", () => { utils.sendUsageReport.restore(); }); - context("getCombinationName", () => { - const get_path = syncSpecsLogs.__get__("getCombinationName");; + context('getCombinationName', () => { + const get_path = syncSpecsLogs.__get__('getCombinationName'); let spec = { - "os": "Windows", - "osVersion": "10", - "browser": "chrome", - "browserVersion": "86" - } - it("returns combination name", () => { + os: 'Windows', + osVersion: '10', + browser: 'chrome', + browserVersion: '86', + }; + it('returns combination name', () => { let expectedCombination = `Chrome 86 (Windows 10)`; expect(get_path(spec)).to.equal(expectedCombination); }); }); - context("getStatus", () => { - const getStatus = syncSpecsLogs.__get__("getStatus");; + context('getStatus', () => { + const getStatus = syncSpecsLogs.__get__('getStatus'); - it("returns return ✔ in green when status is passes", () => { - expect(getStatus("passed")).to.equal(chalk.green("✔")); + it('returns return ✔ in green when status is passes', () => { + expect(getStatus('passed')).to.equal(chalk.green('✔')); }); - it("returns return ✘ in red when status is failed", () => { - expect(getStatus("failed")).to.equal(chalk.red("✘")); + it('returns return ✘ in red when status is failed', () => { + expect(getStatus('failed')).to.equal(chalk.red('✘')); }); - it("returns return [status] in yellow when status is skipped or ignored (anything else from pass/fail)", () => { - expect(getStatus("skipped")).to.equal(chalk.blue("[skipped]")); - expect(getStatus("ignored")).to.equal(chalk.blue("[ignored]")); + it('returns return [status] in yellow when status is skipped or ignored (anything else from pass/fail)', () => { + expect(getStatus('skipped')).to.equal(chalk.blue('[skipped]')); + expect(getStatus('ignored')).to.equal(chalk.blue('[ignored]')); }); }); - context("printInitialLog", () => { - const printInitialLog = syncSpecsLogs.__get__("printInitialLog"); + context('printInitialLog', () => { + const printInitialLog = syncSpecsLogs.__get__('printInitialLog'); - it("should print inital logs for specs in sync", () => { - - printInitialLog() - - expect(syncSpecsLogs.__get__("n")).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); - expect(syncSpecsLogs.__get__("startTime")).to.not.be.null; + it('should print inital logs for specs in sync', () => { + printInitialLog(); + expect(syncSpecsLogs.__get__('n')).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); + expect(syncSpecsLogs.__get__('startTime')).to.not.be.null; }); }); - context("getOptions", () => { - const getOptions = syncSpecsLogs.__get__("getOptions"); - let auth = {username: "cypress", access_key: "abcd"} - let build_id = "build1" + context('getOptions', () => { + const getOptions = syncSpecsLogs.__get__('getOptions'); + let auth = { username: 'cypress', access_key: 'abcd' }; + let build_id = 'build1'; - it('should return proper request option for polling', () => { + it('should return proper axios option for polling', () => { let options = getOptions(auth, build_id); expect(options.url).to.equal(`${config.buildUrlV2}${build_id}`); expect(options.auth.user).to.equal(auth.username); expect(options.auth.password).to.equal(auth.access_key); - expect(options.headers["Content-Type"]).to.equal("application/json"); + expect(options.headers['Content-Type']).to.equal('application/json'); }); }); - context("addCustomErrorToPrint", () => { - const addCustomErrorToPrint = syncSpecsLogs.__get__("addCustomErrorToPrint"); + context('addCustomErrorToPrint', () => { + const addCustomErrorToPrint = syncSpecsLogs.__get__('addCustomErrorToPrint'); let specSummary = { - "buildError": null, - "specs": [], - "duration": null, - "parallels": null, - "cliDuration": null, - "customErrorsToPrint": [ - { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" } - ] - } - const uniqueError = { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" }; - const notUniqueError = { id: "custom_error_2", type: "custom_errors_to_print", level: "warn", should_be_unique: false, message: "custom error message" }; + buildError: null, + specs: [], + duration: null, + parallels: null, + cliDuration: null, + customErrorsToPrint: [ + { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }, + ], + }; + const uniqueError = { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }; + const notUniqueError = { + id: 'custom_error_2', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: false, + message: 'custom error message', + }; it('should add a new Error if its meant to be unique and not added to the error list', () => { addCustomErrorToPrint(uniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should not add a new Error if its meant to be unique and already added to the error list', () => { addCustomErrorToPrint(uniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should add a new Error if its not meant to be unique and not added to the error list', () => { addCustomErrorToPrint(notUniqueError); specSummary.customErrorsToPrint.push(notUniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); it('should not add a new Error if its not meant to not be unique and already added to the error list', () => { addCustomErrorToPrint(notUniqueError); specSummary.customErrorsToPrint.push(notUniqueError); - expect(JSON.stringify(syncSpecsLogs.__get__("specSummary"))).to.equal(JSON.stringify(specSummary)); + expect(JSON.stringify(syncSpecsLogs.__get__('specSummary'))).to.equal(JSON.stringify(specSummary)); }); }); - context("getTableConfig", () => { - const getTableConfig = syncSpecsLogs.__get__("getTableConfig"); + context('getTableConfig', () => { + const getTableConfig = syncSpecsLogs.__get__('getTableConfig'); it('should return proper table config option for spec table', () => { var getBorderConfigStub = sandbox.stub(); syncSpecsLogs.__set__('getBorderConfig', getBorderConfigStub); - let options = getTableConfig((process.stdout.columns) * 0.9); - expect(options.columnDefault.width).to.equal(Math.floor(((process.stdout.columns) * 0.9) * 0.2)); + let options = getTableConfig(process.stdout.columns * 0.9); + expect(options.columnDefault.width).to.equal(Math.floor(process.stdout.columns * 0.9 * 0.2)); expect(options.columns[1].alignment).to.equal('center'); expect(options.columns[2].alignment).to.equal('left'); - expect(options.columns[1].width).to.equal(Math.ceil(((process.stdout.columns) * 0.9) * 0.01)); - expect(options.columns[2].width).to.equal(Math.floor(((process.stdout.columns) * 0.9) * 0.75)); + expect(options.columns[1].width).to.equal(Math.ceil(process.stdout.columns * 0.9 * 0.01)); + expect(options.columns[2].width).to.equal(Math.floor(process.stdout.columns * 0.9 * 0.75)); expect(options.columnCount).to.equal(3); expect(getBorderConfigStub.calledOnce).to.be.true; }); @@ -157,46 +173,54 @@ describe("syncSpecsLogs", () => { }); }); - context("getBorderConfig", () => { - const getBorderConfig = syncSpecsLogs.__get__("getBorderConfig"); + context('getBorderConfig', () => { + const getBorderConfig = syncSpecsLogs.__get__('getBorderConfig'); it('should return proper border option for spec table', () => { let options = getBorderConfig(); - expect(options.topBody).to.equal(""); - expect(options.bottomBody).to.equal(""); + expect(options.topBody).to.equal(''); + expect(options.bottomBody).to.equal(''); }); }); - context("writeToTable", () => { - const writeToTable = syncSpecsLogs.__get__("writeToTable"); + context('writeToTable', () => { + const writeToTable = syncSpecsLogs.__get__('writeToTable'); it('should print spec details to the table', () => { const stream = sandbox.stub(); stream.write = sandbox.stub(); syncSpecsLogs.__set__('stream', stream); - let combination = "Windows 10", path = "path", status = "passed", statusMark = "passed"; + let combination = 'Windows 10', + path = 'path', + status = 'passed', + statusMark = 'passed'; writeToTable(combination, path, status, statusMark); - sinon.assert.calledOnceWithExactly(stream.write, [combination , ":", `${path} ${statusMark} [${status}]`]); + sinon.assert.calledOnceWithExactly(stream.write, [combination, ':', `${path} ${statusMark} [${status}]`]); }); }); - context("addSpecToSummary", () => { - const addSpecToSummary = syncSpecsLogs.__get__("addSpecToSummary"); + context('addSpecToSummary', () => { + const addSpecToSummary = syncSpecsLogs.__get__('addSpecToSummary'); it('should add spec details to specSummary', () => { - let specSummary = { specs: [] } + let specSummary = { specs: [] }; syncSpecsLogs.__set__('specSummary', specSummary); - let specName = "spec", status = "status", combination = "combo", session_id = "id"; + let specName = 'spec', + status = 'status', + combination = 'combo', + session_id = 'id'; addSpecToSummary(specName, status, combination, session_id); - expect(specSummary.specs).deep.to.equal([{"specName": specName, "status": status, "combination": combination, "sessionId": session_id}]) + expect(specSummary.specs).deep.to.equal([ + { specName: specName, status: status, combination: combination, sessionId: session_id }, + ]); }); }); - context("printSpecData", () => { - const printSpecData = syncSpecsLogs.__get__("printSpecData"); + context('printSpecData', () => { + const printSpecData = syncSpecsLogs.__get__('printSpecData'); it('Should print combination and status to the spec table and add spec details to spec array', () => { - let data = { spec: { status: "passed" }, path: "path", session_id: "id" } + let data = { spec: { status: 'passed' }, path: 'path', session_id: 'id' }; var getCombinationName = sandbox.stub(); syncSpecsLogs.__set__('getCombinationName', getCombinationName); var getStatus = sandbox.stub(); @@ -206,76 +230,89 @@ describe("syncSpecsLogs", () => { var addSpecToSummary = sandbox.stub(); syncSpecsLogs.__set__('addSpecToSummary', addSpecToSummary); - printSpecData(data); - sinon.assert.calledOnceWithExactly(getCombinationName, data["spec"]); - sinon.assert.calledOnceWithExactly(getStatus, data["spec"]["status"]); + sinon.assert.calledOnceWithExactly(getCombinationName, data['spec']); + sinon.assert.calledOnceWithExactly(getStatus, data['spec']['status']); sinon.assert.calledOnce(writeToTable); sinon.assert.calledOnce(addSpecToSummary); }); }); - - context("showSpecsStatus", () => { - const showSpecsStatus = syncSpecsLogs.__get__("showSpecsStatus"); - const buildCreatedStatusCode = 202 - const buildRunningStatusCode = 204 - const buildCompletedStatusCode = 200 + context('showSpecsStatus', () => { + const showSpecsStatus = syncSpecsLogs.__get__('showSpecsStatus'); + const buildCreatedstatus = 202; + const buildRunningstatus = 204; + const buildCompletedstatus = 200; it('should not print initial log for running specs when it is the 1st polling response', () => { - let data = JSON.stringify({ "specData": ["created"], "buildData": {"duration": "NA", "parallels": "NA"}}) + let data = { specData: ['created'], buildData: { duration: 'NA', parallels: 'NA' } }; var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); - showSpecsStatus(data, buildCreatedStatusCode); + showSpecsStatus(data, buildCreatedstatus); expect(printInitialLog.calledOnce).to.be.false; }); it('should print spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - let data = JSON.stringify({ "specData": [specResult], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + let data = { specData: [specResult], buildData: { duration: 'NA', parallels: 'NA' } }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); - showSpecsStatus(data, buildRunningStatusCode); + showSpecsStatus(data, buildRunningstatus); expect(printSpecData.calledOnce).to.be.true; }); it('should print initial and spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - syncSpecsLogs.__set__('buildStarted', false) - let data = JSON.stringify({ "specData": [specResult], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + syncSpecsLogs.__set__('buildStarted', false); + let data = { specData: [specResult], buildData: { duration: 'NA', parallels: 'NA' } }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); - showSpecsStatus(data, buildCreatedStatusCode); + showSpecsStatus(data, buildCreatedstatus); expect(printSpecData.calledOnce).to.be.true; expect(printInitialLog.calledOnce).to.be.true; }); it('should add custom error, print initial and spec details when spec related data is sent in polling response', () => { - let specResult = JSON.stringify({"path": "path"}) - let customError = { id: "custom_error_1", type: "custom_errors_to_print", level: "warn", should_be_unique: true, message: "custom error message" } - syncSpecsLogs.__set__('buildStarted', false) - let data = JSON.stringify({ "specData": ["created", specResult, customError], "buildData": {"duration": "NA", "parallels": "NA"}}) + let specResult = JSON.stringify({ path: 'path' }); + let customError = { + id: 'custom_error_1', + type: 'custom_errors_to_print', + level: 'warn', + should_be_unique: true, + message: 'custom error message', + }; + syncSpecsLogs.__set__('buildStarted', false); + let data = { + specData: ['created', specResult, customError], + buildData: { duration: 'NA', parallels: 'NA' }, + }; var printSpecData = sandbox.stub(); syncSpecsLogs.__set__('printSpecData', printSpecData); var printInitialLog = sandbox.stub(); syncSpecsLogs.__set__('printInitialLog', printInitialLog); var addCustomErrorToPrint = sandbox.stub(); syncSpecsLogs.__set__('addCustomErrorToPrint', addCustomErrorToPrint); - showSpecsStatus(data, buildRunningStatusCode); + showSpecsStatus(data, buildRunningstatus); expect(printSpecData.calledOnce).to.be.true; expect(printInitialLog.calledOnce).to.be.true; expect(addCustomErrorToPrint.calledOnce).to.be.true; }); }); - context("printSpecsStatus", () => { - const printSpecsStatus = syncSpecsLogs.__get__("printSpecsStatus"); - let startTime = Date.now(), endTime = Date.now() + 10, counter = 0; - let specSummary = { specs: [] }, getOptions, getTableConfig, tableStream, whileProcess; + context('printSpecsStatus', () => { + const printSpecsStatus = syncSpecsLogs.__get__('printSpecsStatus'); + let startTime = Date.now(), + endTime = Date.now() + 10, + counter = 0; + let specSummary = { specs: [] }, + getOptions, + getTableConfig, + tableStream, + whileProcess; beforeEach(() => { counter = 0; @@ -290,11 +327,13 @@ describe("syncSpecsLogs", () => { syncSpecsLogs.__set__('tableStream', tableStream); whileProcess = sandbox.stub().callsFake(function (whilstCallback) { - counter++ - if(counter >= 3) { + counter++; + if (counter >= 3) { syncSpecsLogs.__set__('whileLoop', false); - whilstCallback(new Error("ggg"), {}); - } else {whileProcess(whilstCallback, 10, null)} + whilstCallback(new Error('ggg'), {}); + } else { + whileProcess(whilstCallback, 10, null); + } }); syncSpecsLogs.__set__('whileProcess', whileProcess); @@ -311,7 +350,7 @@ describe("syncSpecsLogs", () => { expect(getTableConfig.calledOnce).to.be.true; expect(tableStream.calledOnce).to.be.true; expect(whileProcess.calledOnce).to.be.false; - expect(specSummary.specs).deep.to.equal([]) + expect(specSummary.specs).deep.to.equal([]); expect(specSummary.cliDuration).to.eql(endTime - startTime); }); }); @@ -332,45 +371,44 @@ describe("syncSpecsLogs", () => { }); }); - context("whileProcess", () => { - const whileProcess = syncSpecsLogs.__get__("whileProcess"); + context('whileProcess', () => { + const whileProcess = syncSpecsLogs.__get__('whileProcess'); - it('Should retry when request fails with error', () => { - let delayed_n = 2, timeout = 3000, n = 1; - let error = new Error("error"); + it('Should retry when axios fails with error', () => { + let delayed_n = 2, + timeout = 3000, + n = 1; + let error = new Error('error'); - let requestStub = sandbox.stub(); + let axiosStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); + let postStub = sandbox.stub(axios, 'post').resolves({ status: 502, error: error, data: {} }); - requestStub.post = postStub; + axiosStub.post = postStub; let setTimeout = sandbox.stub(); syncSpecsLogs.__set__('setTimeout', setTimeout); syncSpecsLogs.__set__('n', n); syncSpecsLogs.__set__('timeout', timeout); - syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('axios', axiosStub); syncSpecsLogs.__set__('whileTries', 5); let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(4); + expect(syncSpecsLogs.__get__('whileTries')).to.equal(4); }); it('Should exit after defined number of retries in case of error', () => { - let error = new Error("error"), requestStub = sandbox.stub(); + let error = new Error('error'), + axiosStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); + let postStub = sandbox.stub(axios, 'post').resolves({ status: 502, error, data: {} }); - requestStub.post = postStub; + axiosStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('axios', axiosStub); syncSpecsLogs.__set__('whileTries', 1); syncSpecsLogs.__set__('specSummary', {}); syncSpecsLogs.__set__('whileLoop', true); @@ -378,20 +416,23 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - sinon.assert.calledWith(whilstCallback, { status: 504, message: "Tries limit reached" }); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(0); - expect(syncSpecsLogs.__get__("whileLoop")).to.equal(false); - expect(syncSpecsLogs.__get__("specSummary.exitCode")).to.equal(2); + sinon.assert.calledWith(whilstCallback, { status: 504, message: 'Tries limit reached' }); + expect(syncSpecsLogs.__get__('whileTries')).to.equal(0); + expect(syncSpecsLogs.__get__('whileLoop')).to.equal(false); + expect(syncSpecsLogs.__get__('specSummary.exitCode')).to.equal(2); }); it('Should print spec details when data is returned from server', () => { - let error = null, body={}, status = 202, n = 1, delayed_n = 2, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + let error = null, + body = {}, + status = 202, + n = 1, + delayed_n = 2, + timeout = 3000; + let axiosStub = sandbox.stub(); + let postStub = sandbox.stub(axios, 'post').resolves({ status: status, data: body }); + axiosStub.post = postStub; + syncSpecsLogs.__set__('axios', axiosStub); let showSpecsStatus = sandbox.stub(); syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); @@ -404,19 +445,21 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + expect(syncSpecsLogs.__get__('n')).to.equal(delayed_n); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); - sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); }); it('Should poll for data when server responds with no data available', () => { - let error = null, body={}, status = 204, n = 1, delayed_n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); + let error = null, + body = {}, + status = 204, + n = 1, + delayed_n = 2, + timeout = 3000; + let axiosStub = sandbox.stub(); + let postStub = sandbox.stub(axios, 'post').resolves({ status, data: body }); + axiosStub.post = postStub; + syncSpecsLogs.__set__('axios', axiosStub); let showSpecsStatus = sandbox.stub(); syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); @@ -429,51 +472,8 @@ describe("syncSpecsLogs", () => { let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + expect(syncSpecsLogs.__get__('n')).to.equal(delayed_n); sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); }); - - it('Should stop polling for data when server responds build is completed', () => { - let error = null, body={}, status = 200, n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); - - let showSpecsStatus = sandbox.stub(); - syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); - - syncSpecsLogs.__set__('whileLoop', true); - syncSpecsLogs.__set__('n', n); - syncSpecsLogs.__set__('timeout', timeout); - - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); - - expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; - sinon.assert.calledWith(whilstCallback, null, JSON.stringify(body)); - sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); - }); - - it('Should stop polling for data when server responds with error ', () => { - let error = null, body={}, status = 404, n = 1, timeout = 3000; - let requestStub = sandbox.stub(); - let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); - requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); - - let showSpecsStatus = sandbox.stub(); - syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); - - syncSpecsLogs.__set__('whileLoop', true); - syncSpecsLogs.__set__('n', n); - syncSpecsLogs.__set__('timeout', timeout); - - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); - - expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; - sinon.assert.calledWith(whilstCallback, {message: JSON.stringify(body), status: status}); - }); }); }); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index c863a3ae..58ae9dc1 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2,7 +2,7 @@ const path = require('path'); var sandbox = require('sinon').createSandbox(); -const request = require('request'); +const axios = require('axios'); const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), @@ -2900,22 +2900,25 @@ describe('utils', () => { }); describe('#checkLocalBinaryRunning', () => { + let axiosStub; afterEach(() => { sinon.restore(); + axiosStub.restore(); }); it('if the bsConfig localIdentifier is not present within the response body then function should resolve with false', () => { + const responseBody = { + "should_spawn_binary": true + }; const responseObject = { - statusCode: 200, + status: 200, headers: { 'content-type': 'application/json', }, + data: responseBody }; - const responseBody = { - "should_spawn_binary": true - }; - sinon - .stub(request, 'post') - .yields(undefined, responseObject, JSON.stringify(responseBody)); + axiosStub = sinon + .stub(axios, 'post') + .resolves(responseObject); let bsConfig = { auth: { @@ -2933,16 +2936,18 @@ describe('utils', () => { }); it('if the bsConfig localIdentifier is present within the response body', () => { + const responseBody = { "should_spawn_binary": false }; const responseObject = { - statusCode: 200, + status: 200, headers: { 'content-type': 'application/json', }, + data: responseBody }; - const responseBody = { "should_spawn_binary": false }; - sinon - .stub(request, 'post') - .yields(undefined, responseObject, JSON.stringify(responseBody)); + + axiosStub = sinon + .stub(axios, 'post') + .resolves(responseObject); let bsConfig = { auth: { @@ -2952,11 +2957,12 @@ describe('utils', () => { }; let localIdentifier = 'lmno'; - return utils + Promise.resolve(utils .checkLocalBinaryRunning(bsConfig, localIdentifier) .then((result) => { chai.assert.deepEqual(result, {"should_spawn_binary": false}) - }); + })) + return ; }); }); @@ -3442,56 +3448,57 @@ describe('utils', () => { it('message thrown if API deprecated', async () => { let api_deprecated_response = { - statusCode: 299 + status: 299 } message = constant.userMessages.API_DEPRECATED; messageType = constant.messageTypes.INFO; errorCode = 'api_deprecated'; - let requestStub = sinon.stub(request, 'post').yields(undefined, api_deprecated_response, null); + let axiosStub = sinon.stub(axios, 'post').resolves(api_deprecated_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); it('message thrown if build returned', async () => { let api_deprecated_response = { - statusCode: 299, + status: 299, + data: body } message = body.message; messageType = constant.messageTypes.INFO; errorCode = 'api_deprecated'; - let requestStub = sinon.stub(request, 'post').yields(undefined, api_deprecated_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(api_deprecated_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200', async () => { + it('message thrown if status != 200', async () => { let non_200_status_response = { - statusCode: 400 + status: 400 } message = constant.userMessages.BUILD_STOP_FAILED; messageType = constant.messageTypes.ERROR; errorCode = 'api_failed_build_stop'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, null); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200 and user unauthorized', async () => { + it('message thrown if status != 200 and user unauthorized', async () => { let body_with_message = { ...body, "message": "Unauthorized", }; let non_200_status_response = { - statusCode: 401, + status: 401, data: body_with_message } @@ -3500,17 +3507,18 @@ describe('utils', () => { } with error: \n${JSON.stringify(body_with_message, null, 2)}`; messageType = constant.messageTypes.ERROR; errorCode = 'api_auth_failed'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, JSON.stringify(body_with_message)); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); - it('message thrown if statusCode != 200 and build is present', async () => { + it('message thrown if status != 200 and build is present', async () => { let non_200_status_response = { - statusCode: 402, + status: 402, + data: body } message = `${ @@ -3518,28 +3526,29 @@ describe('utils', () => { } with error: \n${JSON.stringify(body, null, 2)}`; messageType = constant.messageTypes.ERROR; errorCode = 'api_failed_build_stop'; - let requestStub = sinon.stub(request, 'post').yields(undefined, non_200_status_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(non_200_status_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); it('message thrown if API success', async () => { let success_response = { - statusCode: 200, + status: 200, + data: body } message = `${JSON.stringify(body, null, 2)}`; messageType = constant.messageTypes.SUCCESS; errorCode = null; - let requestStub = sinon.stub(request, 'post').yields(undefined, success_response, JSON.stringify(body)); + let axiosStub = sinon.stub(axios, 'post').resolves(success_response); await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs); - sinon.assert.calledOnce(requestStub); + sinon.assert.calledOnce(axiosStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnceWithExactly(sendUsageReportStub, bsConfig, args, message, messageType, errorCode, null, rawArgs); - requestStub.restore(); + axiosStub.restore(); }); }); @@ -4238,10 +4247,10 @@ describe('utils', () => { it('should return correct JSON', () => { expect(utils.formatRequest('Something went wrong.', undefined, undefined)).to.be.eql({err: 'Something went wrong.', status: null, body: null}); const body = {message: "Something went wrong"}; - expect(utils.formatRequest(null, {statusCode: 400}, body)).to.be.eql({err: null, status: 400, body: JSON.stringify(body)}); + expect(utils.formatRequest(null, {status: 400}, body)).to.be.eql({err: null, status: 400, body: JSON.stringify(body)}); const cricularBody = {message: "Something went wrong"}; cricularBody.body = cricularBody; - expect(utils.formatRequest(null, {statusCode: 500}, cricularBody)).to.be.eql({err: null, status: 500, body: '[Circular]'}); + expect(utils.formatRequest(null, {status: 500}, cricularBody)).to.be.eql({err: null, status: 500, body: '[Circular]'}); }); }); diff --git a/test/unit/bin/helpers/zipUpload.js b/test/unit/bin/helpers/zipUpload.js index fa418b7d..32241964 100644 --- a/test/unit/bin/helpers/zipUpload.js +++ b/test/unit/bin/helpers/zipUpload.js @@ -2,8 +2,9 @@ const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), sinon = require("sinon"), - fs = require('fs'), - request = require("request"); + fs = require('fs'); + +const { default: axios } = require("axios"); const logger = require("../../../../bin/helpers/logger").winstonLogger, constant = require('../../../../bin/helpers/constants'), @@ -45,7 +46,15 @@ describe("zipUpload", () => { const zipUploader = rewire("../../../../bin/helpers/zipUpload"); beforeEach(() => { utilsStub = { - generateUploadParams: sinon.stub().returns({}), + generateUploadParams: sinon.stub().returns({ + auth: { + user: "someuser", + password: "someuser", + }, + headers: { + "someheader": "header" + } + }), formatRequest, }; loggerStub = { @@ -59,36 +68,6 @@ describe("zipUpload", () => { fs.lstatSync.restore(); }); - it("reject with error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(new Error("test error"), null, null); - - zipUploader.__set__({ - request: { post: requestStub }, - utils: utilsStub, - logger: loggerStub - }); - let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); - let opts = { - archivePresent: true, - messages: {} - } - let obj = { - bar1: null, - zipInterval: null, - size: 0, - startTime: null - } - return uploadSuitsrewire(bsConfig, filePath, opts, obj) - .then((_data) => { - chai.assert.fail("Promise error"); - }) - .catch((error) => { - chai.assert.equal(error.message.message, "test error"); - }); - }); - it("resolve with url if already present", () => { let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { @@ -133,12 +112,12 @@ describe("zipUpload", () => { }); it("resolve with nothing if parsing error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, '{ random: "test }'); + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200, data: { "random": "test" }}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -146,7 +125,12 @@ describe("zipUpload", () => { let opts = { cleanupMethod: sinon.stub().returns(null), archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + md5ReturnKey: "random" } let obj = { bar1: null, @@ -156,20 +140,20 @@ describe("zipUpload", () => { } return uploadSuitsrewire(bsConfig, filePath, opts, obj) .then((data) => { - chai.assert.hasAllKeys(data, ["time"]); + chai.assert.hasAllKeys(data, ["time", "random"]); }) .catch((_error) => { chai.assert.fail("Promise error"); }); }); - it("resolve with message if statusCode = 200", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 200 }, JSON.stringify({ zip_url: "zip_url" })); + it("resolve with message if status = 200", () => { + let axiosStub = sandbox + .stub(axios, "post") + .resolves({ status: 200 , data: { zip_url: "zip_url" }}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -177,7 +161,11 @@ describe("zipUpload", () => { let opts = { cleanupMethod: sinon.stub().returns(null), archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -195,19 +183,23 @@ describe("zipUpload", () => { }); it("reject with returned message if auth failed with message", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 401 }, JSON.stringify({ error: "auth failed" })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 401 , data: { error: "auth failed" }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -225,19 +217,23 @@ describe("zipUpload", () => { }); it("reject with predefined message if auth failed without message", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 401 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 401 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); let uploadSuitsrewire = zipUploader.__get__('uploadSuits'); let opts = { archivePresent: true, - messages: {} + messages: {}, + fileDetails: { + filetype: "zip", + filename: "abc" + }, } let obj = { bar1: null, @@ -254,13 +250,13 @@ describe("zipUpload", () => { }); }); - it("resolve with nothing if request error but no propogation", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 402 }, JSON.stringify({ })); + it("resolve with nothing if axios error but no propogation", () => { + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 402 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -268,7 +264,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: false + propogateError: false, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -285,13 +286,13 @@ describe("zipUpload", () => { }); }); - it("reject with error if request error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 402 }, JSON.stringify({ error: "test error" })); + it("reject with error if axios error", () => { + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 402 , data: { error: "test error" }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -299,7 +300,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -317,12 +323,12 @@ describe("zipUpload", () => { }); it("reject with limit exceeded error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 413 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response:{ status: 413 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -330,7 +336,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null, @@ -348,12 +359,12 @@ describe("zipUpload", () => { }); it("reject with not reachable error", () => { - let requestStub = sandbox - .stub(request, "post") - .yields(null, { statusCode: 414 }, JSON.stringify({ })); + let axiosStub = sandbox + .stub(axios, "post") + .rejects({response: { status: 414 , data: { }}}); zipUploader.__set__({ - request: { post: requestStub }, + axios: { post: axiosStub }, utils: utilsStub, logger: loggerStub }); @@ -361,7 +372,12 @@ describe("zipUpload", () => { let opts = { archivePresent: true, messages: {}, - propogateError: true + propogateError: true, + fileDetails: { + filetype: "zip", + filename: "abc" + }, + cleanupMethod: () => {} } let obj = { bar1: null,