Skip to content

Commit a27917f

Browse files
committed
browserstack-cypress-cli v1.0.0 release
1 parent 2da6917 commit a27917f

17 files changed

+2729
-1
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.DS_Store
3+
browserstack.json
4+
specs
5+
tests.zip

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,16 @@
11
# browserstack-cypress-cli
2-
NPM package for the customers to run Cypress on Browserstack Infra
2+
NPM package to run Cypress on Browserstack Infra
3+
4+
# Setup
5+
6+
```bash
7+
# Install dependencies
8+
$ npm install
9+
$ npm link
10+
11+
# create a sample configuration file for configurations and capabiltiies
12+
$ browserstack-cypress init
13+
14+
# create a cypress build on cypress
15+
$ browserstack-cypress run
16+
```

bin/commands/delete.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
var config = require('../helpers/config');
3+
var request = require('request')
4+
var logger = require("../helpers/logger");
5+
6+
module.exports = function info(args) {
7+
return buildDelete(args)
8+
}
9+
10+
function buildDelete(args) {
11+
let bsConfigPath = process.cwd() + args.cf;
12+
logger.log(`Reading browserstack.json from ${args.cf}`);
13+
var bsConfig = require(bsConfigPath);
14+
15+
let buildId = args._[1]
16+
17+
let options = {
18+
url: config.buildUrl + buildId,
19+
method: 'DELETE',
20+
auth: {
21+
user: bsConfig.auth.username,
22+
password: bsConfig.auth.access_key
23+
}
24+
}
25+
26+
request(options, function (err, resp, body) {
27+
if (err) {
28+
logger.log("Failed to delete build");
29+
} else {
30+
let build = JSON.parse(body)
31+
if (resp.statusCode != 200) {
32+
logger.log(`Build deletion failed with error: \n ${JSON.stringify(build, null, 2)}`);
33+
} else {
34+
logger.log(`Build deleted with build id: \n ${JSON.stringify(build, null, 2)}`);
35+
}
36+
}
37+
})
38+
}

bin/commands/info.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
var config = require('../helpers/config');
3+
var request = require('request')
4+
var logger = require("../helpers/logger");
5+
6+
module.exports = function info(args) {
7+
return buildInfo(args)
8+
}
9+
10+
function buildInfo(args) {
11+
let bsConfigPath = process.cwd() + args.cf;
12+
logger.log(`Reading browserstack.json from ${args.cf}`);
13+
var bsConfig = require(bsConfigPath);
14+
15+
let buildId = args._[1]
16+
17+
let options = {
18+
url: config.buildUrl + buildId,
19+
method: 'GET',
20+
auth: {
21+
user: bsConfig.auth.username,
22+
password: bsConfig.auth.access_key
23+
}
24+
}
25+
26+
request(options, function (err, resp, body) {
27+
if (err) {
28+
logger.log("Failed to get build info");
29+
} else {
30+
let build = JSON.parse(body)
31+
if (resp.statusCode != 200) {
32+
logger.log(`Build info failed with error: \n ${JSON.stringify(build, null, 2)}`);
33+
} else {
34+
logger.log(`Build info for build id: \n ${JSON.stringify(build, null, 2)}`)
35+
}
36+
}
37+
})
38+
}

bin/commands/init.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
var fileHelpers = require('../helpers/fileHelpers');
3+
4+
module.exports = function init(args) {
5+
return createBrowserStackConfig(args)
6+
}
7+
8+
function createBrowserStackConfig(args) {
9+
10+
if (args.p) {
11+
var path_to_bsconf = args.p + "/browserstack.json";
12+
} else {
13+
var path_to_bsconf = "./browserstack.json";
14+
}
15+
16+
var config = {
17+
file: require('../templates/configTemplate')(),
18+
path: path_to_bsconf
19+
};
20+
21+
function allDone() {
22+
console.log('\n' +
23+
'BrowserStack Config File created, you can now run \n' +
24+
'browserstack-cypress --config-file run\n'
25+
);
26+
}
27+
28+
return fileHelpers.fileExists(config.path, function(exists){
29+
if (exists) {
30+
console.log('file already exists, delete the browserstack.json file manually. skipping...');
31+
} else {
32+
fileHelpers.write(config, null, allDone);
33+
}
34+
})
35+
}

bin/commands/runs.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
var archiver = require("../helpers/archiver");
3+
var zipUploader = require("../helpers/zipUpload");
4+
var build = require("../helpers/build");
5+
var logger = require("../helpers/logger");
6+
var config = require('../helpers/config');
7+
var capabilityHelper = require("../helpers/capabilityHelper");
8+
9+
module.exports = function run(args) {
10+
return runCypress(args);
11+
}
12+
13+
function runCypress(args) {
14+
let bsConfigPath = process.cwd() + args.cf;
15+
logger.log(`Reading browserstack.json from ${args.cf}`);
16+
var bsConfig = require(bsConfigPath);
17+
18+
// Validate browserstack.json
19+
capabilityHelper.validate(bsConfig).then(function (validated) {
20+
logger.log(validated);
21+
// Archive the spec files
22+
archiver.archive(bsConfig.run_settings.specs, config.fileName).then(function (data) {
23+
// Uploaded zip file
24+
zipUploader.zipUpload(bsConfig, config.fileName).then(function (zip) {
25+
// Create build
26+
build.createBuild(bsConfig, zip).then(function (data) {
27+
return;
28+
}).catch(function (err) {
29+
// Build creation failed
30+
logger.error("Build creation failed. Please contact Browserstack support")
31+
});
32+
}).catch(function (err) {
33+
// Zip Upload failed
34+
logger.error("Zip Upload failed. Please contact Browserstack support")
35+
});
36+
}).catch(function (err) {
37+
// Zipping failed
38+
logger.error("Failed to zip files. Please contact Browserstack support")
39+
});
40+
}).catch(function (err) {
41+
// Browerstack.json is not valid
42+
logger.error("browerstack.json is not valid. Please contact Browserstack support")
43+
});
44+
}

bin/helpers/archiver.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
var fs = require('fs');
3+
var archiver = require('archiver');
4+
var request = require('request')
5+
var config = require('./config');
6+
var logger = require("./logger")
7+
8+
9+
const archiveSpecs = (specs, filePath) => {
10+
return new Promise(function (resolve, reject) {
11+
var output = fs.createWriteStream(filePath);
12+
13+
var archive = archiver('zip', {
14+
zlib: { level: 9 } // Sets the compression level.
15+
});
16+
17+
archive.on('warning', function (err) {
18+
if (err.code === 'ENOENT') {
19+
logger.log(err)
20+
} else {
21+
reject(err)
22+
}
23+
});
24+
25+
output.on('close', function () {
26+
resolve("Zipping completed")
27+
});
28+
29+
output.on('end', function () {
30+
logger.log('Data has been drained');
31+
});
32+
33+
archive.on('error', function (err) {
34+
reject(err)
35+
});
36+
37+
archive.pipe(output);
38+
39+
specs.forEach(function (item, index) {
40+
logger.log("Adding " + item + " to zip");
41+
archive.glob(item);
42+
});
43+
44+
archive.finalize();
45+
});
46+
}
47+
48+
exports.archive = archiveSpecs

bin/helpers/build.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var request = require('request')
2+
var logger = require("./logger")
3+
var config = require('./config');
4+
var capabilityHelper = require("../helpers/capabilityHelper");
5+
6+
const createBuild = (bsConfig, zip) => {
7+
return new Promise(function (resolve, reject) {
8+
capabilityHelper.caps(bsConfig, zip).then(function(data){
9+
let options = {
10+
url: config.buildUrl,
11+
auth: {
12+
user: bsConfig.auth.username,
13+
password: bsConfig.auth.access_key
14+
},
15+
headers: {
16+
'Content-Type': 'application/json'
17+
},
18+
body: data
19+
}
20+
21+
request.post(options, function (err, resp, body) {
22+
if (err) {
23+
logger.log("Failed to create the build");
24+
reject(err)
25+
} else {
26+
build = JSON.parse(body)
27+
if (resp.statusCode != 201) {
28+
logger.log(`Build creation failed with build error: ${build.message}`);
29+
} else {
30+
logger.log(`Build created with build id: ${build.build_id}`);
31+
}
32+
resolve(build);
33+
}
34+
})
35+
}).catch(function(err){
36+
reject(err);
37+
});
38+
});
39+
}
40+
41+
exports.createBuild = createBuild

bin/helpers/capabilityHelper.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
var logger = require("./logger");
2+
3+
const caps = (bsConfig, zip) => {
4+
return new Promise(function (resolve, reject) {
5+
let user = bsConfig.auth.username
6+
let password = bsConfig.auth.access_key
7+
8+
if (!user || !password) reject("Incorrect auth params.");
9+
10+
var obj = new Object();
11+
12+
// Browser list
13+
let osBrowserArray = [];
14+
bsConfig.browsers.forEach(element => {
15+
osBrowser = element.os + "-" + element.browser
16+
element.versions.forEach(version => {
17+
osBrowserArray.push(osBrowser + version);
18+
});
19+
});
20+
obj.devices = osBrowserArray
21+
if (obj.devices.length == 0) reject("Browser list is empty");
22+
logger.log(`Browser list: ${osBrowserArray.toString()}`);
23+
24+
// Test suite
25+
obj.test_suite = zip.zip_url.split("://")[1]
26+
if (!obj.test_suite || 0 === obj.test_suite.length) reject("Test suite is empty");
27+
logger.log(`Test suite: bs://${obj.test_suite}`);
28+
29+
// Local
30+
obj.local = bsConfig.connection_settings.local;
31+
if (!obj.local) obj.local = false;
32+
logger.log(`Local is set to: ${obj.local}`);
33+
34+
// Project name
35+
obj.project = bsConfig.run_settings.project
36+
if (!obj.project) logger.log(`Project name is: ${obj.project}`);
37+
38+
// Base url
39+
obj.base_url = bsConfig.run_settings.baseUrl
40+
if (obj.base_url) logger.log(`Base url is : ${obj.base_url}`);
41+
42+
// Build name
43+
obj.customBuildName = bsConfig.run_settings.customBuildName
44+
if (obj.customBuildName) logger.log(`Build name is: ${obj.customBuildName}`);
45+
46+
//callback url
47+
obj.callbackURL = bsConfig.run_settings.callback_url
48+
if (obj.callbackURL) logger.log(`callback url is : ${obj.callbackUrl}`);
49+
50+
//projectNotifyURL
51+
obj.projectNotifyURL = bsConfig.run_settings.project_notify_URL
52+
if (obj.projectNotifyURL) logger.log(`Project notify URL is: ${obj.projectNotifyURL}`);
53+
54+
var data = JSON.stringify(obj);
55+
resolve(data);
56+
})
57+
}
58+
59+
const validate = (bsConfig) => {
60+
return new Promise(function(resolve, reject){
61+
if (!bsConfig) reject("Empty browserstack.json");
62+
63+
if (!bsConfig.auth) reject("Invalid auth in browserstack.json");
64+
65+
if (!bsConfig.browsers || bsConfig.browsers.length === 0) reject("No browsers specified");
66+
67+
if (!bsConfig.run_settings) reject("Empty run_settings");
68+
69+
if(!bsConfig.run_settings.specs) reject("No spec files specified in run_settings");
70+
71+
resolve("browserstack.json file is validated");
72+
});
73+
}
74+
75+
module.exports = {
76+
caps,
77+
validate
78+
}

bin/helpers/config.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var config = {};
2+
config.env = "prod";
3+
var hosts = {
4+
prod: {
5+
uploadUrl: `https://api-cloud.browserstack.com/automate-frameworks/cypress/upload`,
6+
rails_host: `https://api.browserstack.com`
7+
}
8+
};
9+
config.uploadUrl = hosts[config.env].uploadUrl;
10+
config.rails_host = hosts[config.env].rails_host;
11+
config.cypress_v1 = `${config.rails_host}/automate/cypress/v1`;
12+
config.buildUrl = `${config.cypress_v1}/builds/`;
13+
config.fileName = "tests.zip";
14+
module.exports = config;

0 commit comments

Comments
 (0)