Skip to content

Commit 372e196

Browse files
authored
Merge pull request #6 from mattonem/browserstack-sdk
Browserstack sdk
2 parents 123d50f + 8ec97e6 commit 372e196

File tree

5 files changed

+170
-103
lines changed

5 files changed

+170
-103
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
package-lock.json

README.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,33 @@ This project is variant of the [selenium-side-runner](https://github.com/Seleniu
44

55
```sh
66
npm install @maxmattone/browserstack-side-runner
7-
npx @maxmattone/browserstack-side-runner -w 2 test.side
7+
npx @maxmattone/browserstack-side-runner test.side
88
```
9-
Don't forget to use the config file `.side.yml` like so:
9+
Don't forget to use the config file `browserstack.yml` like so:
1010
```yml
11-
# this is how your .side.yml should look like
12-
capabilities:
13-
browserName: "Chrome"
14-
'bstack:options':
15-
browserVersion: 'latest'
16-
os: "Windows"
17-
osVersion: '10'
18-
resolution: "3840x2160",
19-
projectName: 'My Selenium IDE Project'
20-
buildName: "My Seleenium IDE Test Suite"
21-
debug: true
22-
networkLogs: true
23-
consoleLogs: "verbose"
24-
server: "https://<bs_username>:<bs_accesskey>@hub-cloud.browserstack.com/wd/hub"
11+
# this is how your browserstack.yml should look like
12+
userName: username
13+
accessKey: access_key
14+
platforms:
15+
- os: Windows
16+
osVersion: 11
17+
browserName: Chrome
18+
browserVersion: 103.0
19+
- os: Windows
20+
osVersion: 10
21+
browserName: Firefox
22+
browserVersion: 102.0
23+
- os: OS X
24+
osVersion: Big Sur
25+
browserName: Safari
26+
browserVersion: 14.1
27+
parallelsPerPlatform: 3
28+
browserstackLocal: true
29+
buildName: bstack-demo
30+
projectName: BrowserStack Sample
31+
debug: true
32+
networkLogs: true
33+
consoleLogs: info
2534
```
2635
36+
You can use [this online tool](https://www.browserstack.com/docs/automate/selenium/sdk-config-generator) to generate your config file based on your needs.

browserstack-mocha-export.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import codeExport from '@seleniumhq/code-export-javascript-mocha';
2+
import { codeExport as exporter } from '@seleniumhq/side-utils'
3+
var emitters = codeExport.opts.emitter.emitters
4+
5+
async function emitSetWindowSize(size) {
6+
const [width, height] = size.split('x');
7+
8+
return Promise.resolve(`
9+
if(!(await driver.getCapabilities()).get("device") && !(await driver.getCapabilities()).get("deviceName"))
10+
{await driver.manage().window().setRect({width: ${width}, height: ${height}})}
11+
`);
12+
}
13+
14+
emitters['setWindowSize'] = emitSetWindowSize;
15+
16+
function beforeEach() {
17+
const params = {
18+
startingSyntax: ({ capabilities, gridUrl } = {}) => ({
19+
commands: [
20+
{ level: 0, statement: 'beforeEach(async function() {' },
21+
{
22+
level: 1,
23+
statement: `driver = await new Builder()
24+
.withCapabilities(${JSON.stringify(capabilities)})
25+
.usingServer('${gridUrl}')
26+
.build()`,
27+
},
28+
{
29+
level: 1, statement: `if(process.env.BS_A11Y_TEST_RUN_ID)
30+
{await driver.sleep(3000);}`
31+
},
32+
{ level: 1, statement: 'vars = {}' },
33+
],
34+
}),
35+
endingSyntax: {
36+
commands: [{ level: 0, statement: '})' }],
37+
},
38+
}
39+
return params
40+
}
41+
42+
codeExport.opts.hooks.beforeEach = new exporter.hook(beforeEach())
43+
44+
function declareVariables() {
45+
const params = {
46+
startingSyntax: {
47+
commands: [
48+
{
49+
level: 0,
50+
statement: `this.timeout(process.env.testTimeout)`,
51+
},
52+
{
53+
level: 0,
54+
statement: `let driver`,
55+
},
56+
{
57+
level: 0,
58+
statement: 'let vars',
59+
},
60+
],
61+
},
62+
}
63+
return params
64+
}
65+
66+
codeExport.opts.hooks.declareVariables = new exporter.hook(declareVariables())
67+
68+
function generateTestDeclaration(name) {
69+
return `it('test', async function() {`
70+
}
71+
72+
codeExport.opts.generateTestDeclaration = generateTestDeclaration
73+
export default codeExport

index.mjs

Lines changed: 60 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,32 @@
33
import fs from 'fs'
44
import rimraf from "rimraf";
55
import path from 'path'
6-
import { emitTest, emitSuite } from '@maxmattone/code-export-browserstack-mocha'
6+
import codeExport from './browserstack-mocha-export.mjs'
7+
import { project as projectProcessor } from '@seleniumhq/side-code-export'
78
import pkg from '@seleniumhq/side-utils';
89
import commander from 'commander';
910
import logger from 'cli-logger';
10-
import yaml from 'js-yaml';
11-
import Mocha from 'mocha';
1211
import glob from 'glob';
13-
import createClone from 'rfdc';
14-
import { fileURLToPath } from 'url';
15-
const __filename = fileURLToPath(import.meta.url);
16-
const __dirname = path.dirname(__filename);
17-
18-
19-
const clone = createClone();
20-
21-
const { project: projectProcessor } = pkg;
22-
import { exec } from "child_process";
23-
12+
import spawn from 'cross-spawn';
13+
import * as dotenv from 'dotenv';
14+
import { exit } from 'process';
15+
dotenv.config();
2416
commander
2517
.usage('[options] project.side [project.side] [*.side]')
2618
.option('-d, --debug', 'output extra debugging')
2719
.option('-f, --filter <grep regex>', 'Run tests matching name')
28-
.option('-w, --max-workers <number>', 'Maximum amount of workers that will run your tests, defaults to 1')
2920
.option('--base-url <url>', 'Override the base URL that was set in the IDE')
30-
.option('--config, --config-file <filepath>', 'Use specified YAML file for configuration. (default: .side.yml)')
31-
.option('--test-timeout <ms>', 'Timeout value for each tests. (default: 30000)')
32-
.option('--output-directory <directory>', 'Write test results to files, format is defined by --output-format')
33-
.option('--output-format <@mochajs/json-file-reporter|xunit>', 'Format for the output file. (default: @mochajs/json-file-reporter)')
21+
.option('--test-timeout <ms>', 'Timeout value for each tests (default: 30000)')
22+
.option('--browserstack.config <path>','path to browserstack config file, default to browserstack.yml')
23+
.option('--output-format <json|xunit>', 'Format for the output file.')
24+
.option('--output-file <path>','path for the report file. required if --output-format provided')
3425

3526
commander.parse(process.argv);
3627
const options = commander.opts();
37-
38-
options.maxWorkers = options.maxWorkers ? options.maxWorkers : 1
3928
options.testTimeout = options.testTimeout ? options.testTimeout : 30000
40-
options.configFile = options.configFile ? options.configFile : '.side.yml'
4129
options.filter = options.filter ? options.filter : ''
42-
options.outputFormat = options.outputFormat ? options.outputFormat : '@mochajs/json-file-reporter'
30+
options.browserstackConfig = options['browserstack.config'] ? options['browserstack.config'] : 'browserstack.yml'
4331
options.buildFolderPath = '_generated'
44-
4532
var conf = {level: options.debug ? logger.DEBUG :logger.INFO};
4633
var log = logger(conf);
4734

@@ -54,69 +41,60 @@ const sideFiles = [
5441
}, new Set()),
5542
];
5643

57-
var mocha = new Mocha(
58-
{
59-
reporter: "mocha-multi-reporters",
60-
grep: options.filter,
61-
parallel: true,
62-
jobs: options.maxWorkers,
63-
timeout: options.testTimeout,
64-
reporterOptions: {
65-
"reporterEnabled": "spec" + (options.outputDirectory ? ', ' + options.outputFormat :''),
66-
"mochajsJsonFileReporterReporterOptions": {
67-
"output": path.join(options.outputDirectory || '', "test-output.json")
68-
},
69-
"xunitReporterOptions": {
70-
"output": path.join(options.outputDirectory || '', "test-output.xunit.xml")
71-
},
72-
}
73-
});
74-
75-
7644
rimraf.sync(options.buildFolderPath)
7745
fs.mkdirSync(options.buildFolderPath);
7846

79-
var config
80-
try {
81-
config = yaml.load(fs.readFileSync(options.configFile));
82-
} catch (e) {
83-
log.error(e);
47+
function readFile(filename) {
48+
return JSON.parse(
49+
fs.readFileSync(
50+
path.join(
51+
'.',
52+
filename
53+
)
54+
)
55+
)
56+
}
57+
58+
function normalizeProject(project) {
59+
let _project = { ...project }
60+
_project.suites.forEach(suite => {
61+
projectProcessor.normalizeTestsInSuite({ suite, tests: _project.tests })
62+
})
63+
_project.url = options.baseUrl ? options.baseUrl : project.url
64+
return _project
8465
}
8566

86-
const projects = sideFiles.map(name => JSON.parse(fs.readFileSync(name)))
87-
var testFileInc = 1;
88-
var promises = [];
89-
projects.forEach(project => {
90-
project.tests.forEach(test => {
91-
promises.push(new Promise(async (resolve, reject) => {
92-
var _config = clone(config);
93-
_config.capabilities['name'] = test.name
94-
if(_config.capabilities['bstack:options'] == undefined)
67+
for(const sideFileName of sideFiles)
68+
{
69+
const project = normalizeProject(readFile(sideFileName))
70+
for(const aSuite of project.suites)
71+
{
72+
for(const aTestCase of aSuite.tests)
9573
{
96-
_config.capabilities['bstack:options'] = []
74+
const test = project.tests.find(test => test.name === aTestCase);
75+
var results = await codeExport.default.emit.test({
76+
baseUrl: options.baseUrl ? options.baseUrl : project.url,
77+
test: test,
78+
tests: project.tests,
79+
project: project
80+
})
81+
fs.writeFileSync( path.join(
82+
options.buildFolderPath,
83+
results.filename
84+
), results.body);
9785
}
98-
_config.capabilities['bstack:options']['sessionName'] = test.name
99-
var packageJson = JSON.parse(fs.readFileSync(__dirname + '/package.json'));
100-
_config.capabilities['browserstack-side-runner-version'] = packageJson.version
101-
const result = await emitTest({
102-
baseUrl: options.baseUrl ? options.baseUrl : project.url,
103-
test: test,
104-
tests: project.tests,
105-
beforeEachOptions: {
106-
capabilities: _config.capabilities,
107-
gridUrl: _config.server,
108-
},});
109-
var filename = path.join(options.buildFolderPath, testFileInc + result.filename);
110-
testFileInc ++;
111-
fs.writeFileSync(filename, result.body);
112-
mocha.addFile(filename);
113-
resolve()}))
114-
})
115-
});
116-
Promise.all(promises).then(()=>{
117-
mocha.run(function(failures) {
118-
process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures
119-
if(!options.debug) rimraf.sync(options.buildFolderPath)
120-
});
86+
}
87+
88+
}
89+
90+
var reporter = []
91+
if(options.outputFormat && options.outputFile)
92+
reporter = [ '--reporter', options.outputFormat, '--reporter-options', 'output=' + options.outputFile]
93+
94+
const testSuiteProcess = spawn.sync('npx', ['browserstack-node-sdk', 'mocha', '_generated', '--timeouts', options.testTimeout, '-g', options.filter, '--browserstack.config', options.browserstackConfig, ...reporter], { stdio: 'inherit', env: { ...process.env, testTimeout: options.testTimeout } });
12195

122-
})
96+
if(!options.debug)
97+
{
98+
rimraf.sync(options.buildFolderPath)
99+
}
100+
exit(testSuiteProcess.status)

package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
{
22
"name": "@maxmattone/browserstack-side-runner",
3-
"repository": "git@github.com:mattonem/browserstack-side-runner.git",
4-
"version": "1.5.0",
5-
"main": "index.mjs",
6-
"homepage": "https://github.com/mattonem/browserstack-side-runner#readme",
73
"repository": {
84
"type": "git",
95
"url": "git+https://github.com/mattonem/browserstack-side-runner.git"
106
},
7+
"version": "2.0.0",
8+
"main": "index.mjs",
9+
"homepage": "https://github.com/mattonem/browserstack-side-runner#readme",
1110
"scripts": {
1211
"test": "echo \"Error: no test specified\" && exit 1"
1312
},
@@ -18,10 +17,15 @@
1817
"license": "ISC",
1918
"description": "",
2019
"dependencies": {
21-
"@maxmattone/code-export-browserstack-mocha": "^1.2.0",
2220
"@mochajs/json-file-reporter": "^1.3.0",
21+
"@seleniumhq/code-export-javascript-mocha": "^4.0.0-alpha.4",
22+
"@seleniumhq/side-code-export": "^4.0.0-alpha.3",
23+
"@seleniumhq/side-utils": "^3.17.2",
24+
"browserstack-node-sdk": "^1.25.2",
2325
"cli-logger": "^0.5.40",
2426
"commander": "^8.0.0",
27+
"cross-spawn": "^7.0.3",
28+
"dotenv": "^16.0.3",
2529
"glob": "^7.1.7",
2630
"js-yaml": "^4.1.0",
2731
"mocha": "^8.4.0",

0 commit comments

Comments
 (0)