Skip to content

Commit 42e8cd6

Browse files
committed
Merge branch 'master' into deploy
2 parents a76de9f + 6459b5b commit 42e8cd6

20 files changed

+1396
-473
lines changed

bin/commands/generateDownloads.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const logger = require("../helpers/logger").winstonLogger,
4+
Constants = require("../helpers/constants"),
5+
utils = require("../helpers/utils"),
6+
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts;
7+
8+
9+
module.exports = async function generateDownloads(args) {
10+
let bsConfigPath = utils.getConfigPath(args.cf);
11+
12+
return utils.validateBstackJson(bsConfigPath).then(async function (bsConfig) {
13+
// setting setDefaults to {} if not present and set via env variables or via args.
14+
utils.setDefaults(bsConfig, args);
15+
16+
// accept the username from command line if provided
17+
utils.setUsername(bsConfig, args);
18+
19+
// accept the access key from command line if provided
20+
utils.setAccessKey(bsConfig, args);
21+
22+
utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting);
23+
24+
// set cypress config filename
25+
utils.setCypressConfigFilename(bsConfig, args);
26+
27+
let messageType = Constants.messageTypes.INFO;
28+
let errorCode = null;
29+
let buildId = args._[1];
30+
31+
await downloadBuildArtifacts(bsConfig, buildId, args);
32+
utils.sendUsageReport(bsConfig, args, Constants.usageReportingConstants.GENERATE_DOWNLOADS, messageType, errorCode);
33+
}).catch(function (err) {
34+
logger.error(err);
35+
utils.setUsageReportingFlag(null, args.disableUsageReporting);
36+
utils.sendUsageReport(null, args, err.message, Constants.messageTypes.ERROR, utils.getErrorCodeFromErr(err));
37+
});
38+
};

bin/commands/runs.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ const archiver = require("../helpers/archiver"),
1111
syncRunner = require("../helpers/syncRunner"),
1212
checkUploaded = require("../helpers/checkUploaded"),
1313
reportGenerator = require('../helpers/reporterHTML').reportGenerator,
14-
{initTimeComponents, markBlockStart, markBlockEnd, getTimeComponents} = require('../helpers/timeComponents');
14+
{initTimeComponents, instrumentEventTime, markBlockStart, markBlockEnd, getTimeComponents} = require('../helpers/timeComponents'),
15+
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts;
1516

1617
module.exports = function run(args) {
1718
let bsConfigPath = utils.getConfigPath(args.cf);
1819
//Delete build_results.txt from log folder if already present.
1920
initTimeComponents();
21+
instrumentEventTime("cliStart")
2022
markBlockStart('deleteOldResults');
2123
utils.deleteResults();
2224
markBlockEnd('deleteOldResults');
2325

2426
markBlockStart('validateBstackJson');
25-
return utils.validateBstackJson(bsConfigPath).then(function (bsConfig) {
27+
return utils.validateBstackJson(bsConfigPath).then(async function (bsConfig) {
2628
markBlockEnd('validateBstackJson');
2729
markBlockStart('setConfig');
2830
utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting);
@@ -67,6 +69,14 @@ module.exports = function run(args) {
6769

6870
// set the no-wrap
6971
utils.setNoWrap(bsConfig, args);
72+
73+
//set browsers
74+
await utils.setBrowsers(bsConfig, args);
75+
76+
//set config (--config)
77+
utils.setConfig(bsConfig, args);
78+
// set other cypress configs e.g. reporter and reporter-options
79+
utils.setOtherConfigs(bsConfig, args);
7080
markBlockEnd('setConfig');
7181

7282
// Validate browserstack.json values and parallels specified via arguments
@@ -84,7 +94,7 @@ module.exports = function run(args) {
8494
utils.warnSpecLimit(bsConfig, args, specFiles);
8595
markBlockEnd('preArchiveSteps');
8696
markBlockStart('checkAlreadyUploaded');
87-
return checkUploaded.checkUploadedMd5(bsConfig, args).then(function (md5data) {
97+
return checkUploaded.checkUploadedMd5(bsConfig, args, {markBlockStart, markBlockEnd}).then(function (md5data) {
8898
markBlockEnd('checkAlreadyUploaded');
8999

90100
// Archive the spec files
@@ -136,19 +146,30 @@ module.exports = function run(args) {
136146
// stop the Local instance
137147
await utils.stopLocalBinary(bsConfig, bs_local, args);
138148

149+
// waiting for 5 secs for upload to complete (as a safety measure)
150+
await new Promise(resolve => setTimeout(resolve, 5000));
151+
152+
// download build artifacts
153+
if (utils.nonEmptyArray(bsConfig.run_settings.downloads)) {
154+
await downloadBuildArtifacts(bsConfig, data.build_id, args);
155+
}
156+
139157
// Generate custom report!
140158
reportGenerator(bsConfig, data.build_id, args, function(){
141159
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null);
142160
utils.handleSyncExit(exitCode, data.dashboard_url);
143161
});
144162
});
163+
} else if (utils.nonEmptyArray(bsConfig.run_settings.downloads)) {
164+
logger.info(Constants.userMessages.ASYNC_DOWNLOADS.replace('<build-id>', data.build_id));
145165
}
146166

147167
logger.info(message);
148168
logger.info(dashboardLink);
149-
if(!args.sync) logger.info(Constants.userMessages.EXIT_SYNC_CLI_MESSAGE.replace("<build-id>",data.build_id));
169+
if(!args.sync) logger.info(Constants.userMessages.EXIT_SYNC_CLI_MESSAGE.replace("<build-id>", data.build_id));
150170
let dataToSend = {
151171
time_components: getTimeComponents(),
172+
unique_id: utils.generateUniqueHash(),
152173
build_id: data.build_id,
153174
};
154175
if (bsConfig && bsConfig.connection_settings) {

bin/helpers/buildArtifacts.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
'use strict';
2+
3+
const fs = require('fs'),
4+
path = require('path');
5+
6+
const axios = require('axios'),
7+
unzipper = require('unzipper');
8+
9+
const logger = require('./logger').winstonLogger,
10+
utils = require("./utils"),
11+
Constants = require("./constants"),
12+
config = require("./config");
13+
14+
15+
let BUILD_ARTIFACTS_TOTAL_COUNT = 0;
16+
let BUILD_ARTIFACTS_FAIL_COUNT = 0;
17+
18+
const parseAndDownloadArtifacts = async (buildId, data) => {
19+
return new Promise(async (resolve, reject) => {
20+
let all_promises = [];
21+
let combs = Object.keys(data);
22+
for(let i = 0; i < combs.length; i++) {
23+
let comb = combs[i];
24+
let sessions = Object.keys(data[comb]);
25+
for(let j = 0; j < sessions.length; j++) {
26+
let sessionId = sessions[j];
27+
let filePath = path.join('./', 'build_artifacts', buildId, comb, sessionId);
28+
let fileName = 'build_artifacts.zip';
29+
BUILD_ARTIFACTS_TOTAL_COUNT += 1;
30+
all_promises.push(downloadAndUnzip(filePath, fileName, data[comb][sessionId]).catch((error) => {
31+
BUILD_ARTIFACTS_FAIL_COUNT += 1;
32+
// delete malformed zip if present
33+
let tmpFilePath = path.join(filePath, fileName);
34+
if(fs.existsSync(tmpFilePath)){
35+
fs.unlinkSync(tmpFilePath);
36+
}
37+
}));
38+
}
39+
}
40+
await Promise.all(all_promises);
41+
resolve();
42+
});
43+
}
44+
45+
const createDirIfNotPresent = async (dir) => {
46+
return new Promise((resolve) => {
47+
if (!fs.existsSync(dir)){
48+
fs.mkdirSync(dir);
49+
}
50+
resolve();
51+
});
52+
}
53+
54+
const createDirectories = async (buildId, data) => {
55+
// create dir for build_artifacts if not already present
56+
let artifactsDir = path.join('./', 'build_artifacts');
57+
if (!fs.existsSync(artifactsDir)){
58+
fs.mkdirSync(artifactsDir);
59+
}
60+
61+
// create dir for buildId if not already present
62+
let buildDir = path.join('./', 'build_artifacts', buildId);
63+
if (fs.existsSync(buildDir)){
64+
// remove dir in case already exists
65+
fs.rmdirSync(buildDir, { recursive: true, force: true });
66+
}
67+
fs.mkdirSync(buildDir);
68+
69+
let combDirs = [];
70+
let sessionDirs = [];
71+
let combs = Object.keys(data);
72+
73+
for(let i = 0; i < combs.length; i++) {
74+
let comb = combs[i];
75+
let combDir = path.join('./', 'build_artifacts', buildId, comb);
76+
combDirs.push(createDirIfNotPresent(combDir));
77+
let sessions = Object.keys(data[comb]);
78+
for(let j = 0; j < sessions.length; j++) {
79+
let sessionId = sessions[j];
80+
let sessionDir = path.join('./', 'build_artifacts', buildId, comb, sessionId);
81+
sessionDirs.push(createDirIfNotPresent(sessionDir));
82+
}
83+
}
84+
85+
return new Promise(async (resolve) => {
86+
// create sub dirs for each combination in build
87+
await Promise.all(combDirs);
88+
// create sub dirs for each machine id in combination
89+
await Promise.all(sessionDirs);
90+
resolve();
91+
});
92+
}
93+
94+
const downloadAndUnzip = async (filePath, fileName, url) => {
95+
let tmpFilePath = path.join(filePath, fileName);
96+
const writer = fs.createWriteStream(tmpFilePath);
97+
98+
return axios({
99+
method: 'get',
100+
url: url,
101+
responseType: 'stream',
102+
}).then(response => {
103+
104+
//ensure that the user can call `then()` only when the file has
105+
//been downloaded entirely.
106+
107+
return new Promise(async (resolve, reject) => {
108+
response.data.pipe(writer);
109+
let error = null;
110+
writer.on('error', err => {
111+
error = err;
112+
writer.close();
113+
reject(err);
114+
});
115+
writer.on('close', async () => {
116+
if (!error) {
117+
await unzipFile(filePath, fileName);
118+
fs.unlinkSync(tmpFilePath);
119+
resolve(true);
120+
}
121+
//no need to call the reject here, as it will have been called in the
122+
//'error' stream;
123+
});
124+
});
125+
});
126+
}
127+
128+
const unzipFile = async (filePath, fileName) => {
129+
return new Promise( async (resolve, reject) => {
130+
await unzipper.Open.file(path.join(filePath, fileName))
131+
.then(d => d.extract({path: filePath, concurrency: 5}))
132+
.catch((err) => reject(err));
133+
resolve();
134+
});
135+
}
136+
137+
const sendUpdatesToBstack = async (bsConfig, buildId, args, options) => {
138+
let url = `${config.buildUrl}${buildId}/build_artifacts/status`;
139+
140+
let cypressJSON = utils.getCypressJSON(bsConfig);
141+
142+
let reporter = null;
143+
if(!utils.isUndefined(args.reporter)) {
144+
reporter = args.reporter;
145+
} else if(cypressJSON !== undefined){
146+
reporter = cypressJSON.reporter;
147+
}
148+
149+
let data = {
150+
feature_usage: {
151+
downloads: {
152+
eligible_download_folders: BUILD_ARTIFACTS_TOTAL_COUNT,
153+
successfully_downloaded_folders: BUILD_ARTIFACTS_TOTAL_COUNT - BUILD_ARTIFACTS_FAIL_COUNT
154+
},
155+
reporter: reporter
156+
}
157+
}
158+
159+
try {
160+
await axios.post(url, data, options);
161+
} catch (err) {
162+
utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'api_failed_build_artifacts_status_update');
163+
}
164+
}
165+
166+
exports.downloadBuildArtifacts = async (bsConfig, buildId, args) => {
167+
BUILD_ARTIFACTS_FAIL_COUNT = 0;
168+
BUILD_ARTIFACTS_TOTAL_COUNT = 0;
169+
170+
let url = `${config.buildUrl}${buildId}/build_artifacts`;
171+
let options = {
172+
auth: {
173+
username: bsConfig.auth.username,
174+
password: bsConfig.auth.access_key,
175+
},
176+
headers: {
177+
'User-Agent': utils.getUserAgent(),
178+
},
179+
};
180+
181+
let message = null;
182+
let messageType = null;
183+
let errorCode = null;
184+
185+
try {
186+
const res = await axios.get(url, options);
187+
let buildDetails = res.data;
188+
189+
await createDirectories(buildId, buildDetails);
190+
await parseAndDownloadArtifacts(buildId, buildDetails);
191+
192+
if (BUILD_ARTIFACTS_FAIL_COUNT > 0) {
193+
messageType = Constants.messageTypes.ERROR;
194+
message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('<build-id>', buildId).replace('<machine-count>', BUILD_ARTIFACTS_FAIL_COUNT);
195+
logger.error(message);
196+
} else {
197+
messageType = Constants.messageTypes.SUCCESS;
198+
message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_SUCCESS.replace('<build-id>', buildId).replace('<user-path>', process.cwd());
199+
logger.info(message);
200+
}
201+
202+
await sendUpdatesToBstack(bsConfig, buildId, args, options);
203+
utils.sendUsageReport(bsConfig, args, message, messageType, null);
204+
} catch (err) {
205+
messageType = Constants.messageTypes.ERROR;
206+
errorCode = 'api_failed_build_artifacts';
207+
208+
if (BUILD_ARTIFACTS_FAIL_COUNT > 0) {
209+
messageType = Constants.messageTypes.ERROR;
210+
message = Constants.userMessages.DOWNLOAD_BUILD_ARTIFACTS_FAILED.replace('<build-id>', buildId).replace('<machine-count>', BUILD_ARTIFACTS_FAIL_COUNT);
211+
logger.error(message);
212+
} else {
213+
logger.error('Downloading the build artifacts failed.');
214+
}
215+
216+
utils.sendUsageReport(bsConfig, args, err, messageType, errorCode);
217+
}
218+
};

bin/helpers/capabilityHelper.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const logger = require("./logger").winstonLogger,
22
Constants = require("./constants"),
33
Utils = require("./utils"),
4-
fs = require('fs'),
5-
path = require('path');
4+
fs = require('fs');
65

76
const caps = (bsConfig, zip) => {
87
return new Promise(function (resolve, reject) {

0 commit comments

Comments
 (0)