Skip to content

Commit db135a3

Browse files
Ankit098francisf
authored andcommitted
init: browserstack service code
1 parent 6ee6018 commit db135a3

14 files changed

+1653
-0
lines changed

LICENSE-MIT

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) OpenJS Foundation and other contributors
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
'Software'), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

browserstack-service.d.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
declare module WebdriverIO {
2+
interface ServiceOption extends BrowserstackConfig {}
3+
4+
interface Suite {
5+
title: string;
6+
fullName: string;
7+
file: string;
8+
}
9+
10+
interface Test extends Suite {
11+
parent: string;
12+
passed: boolean;
13+
}
14+
15+
interface TestResult {
16+
exception: string;
17+
status: string;
18+
}
19+
}
20+
21+
interface BrowserstackConfig {
22+
/**
23+
* Set this to true to enable routing connections from Browserstack cloud through your computer.
24+
* You will also need to set `browserstack.local` to true in browser capabilities.
25+
*/
26+
browserstackLocal?: boolean;
27+
/**
28+
* Cucumber only. Set this to true to enable updating the session name to the Scenario name if only
29+
* a single Scenario was ran. Useful when running in parallel
30+
* with [wdio-cucumber-parallel-execution](https://github.com/SimitTomar/wdio-cucumber-parallel-execution).
31+
*/
32+
preferScenarioName?: boolean;
33+
/**
34+
* Set this to true to kill the browserstack process on complete, without waiting for the
35+
* browserstack stop callback to be called. This is experimental and should not be used by all.
36+
*/
37+
forcedStop?: boolean;
38+
/**
39+
* Specified optional will be passed down to BrowserstackLocal. See this list for details:
40+
* https://stackoverflow.com/questions/39040108/import-class-in-definition-file-d-ts
41+
*/
42+
opts?: Partial<import('browserstack-local').Options>
43+
}

src/@types/cucumber-framework.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
declare module WebdriverIO {
2+
interface Config extends CucumberOptsConfig {}
3+
}
4+
5+
interface CucumberOptsConfig {
6+
cucumberOpts?: CucumberOpts
7+
}
8+
9+
interface CucumberOpts {
10+
/**
11+
* Fail if there are any undefined or pending steps
12+
* @default false
13+
*/
14+
strict?: boolean
15+
}

src/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const BROWSER_DESCRIPTION = [
2+
'device',
3+
'os',
4+
'osVersion',
5+
'os_version',
6+
'browserName',
7+
'browser',
8+
'browserVersion',
9+
'browser_version'
10+
] as const

src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* istanbul ignore file */
2+
3+
import BrowserstackLauncher from './launcher'
4+
import BrowserstackService from './service'
5+
import type { BrowserstackConfig } from './types'
6+
7+
export default BrowserstackService
8+
export const launcher = BrowserstackLauncher
9+
export * from './types'
10+
11+
declare global {
12+
namespace WebdriverIO {
13+
interface ServiceOption extends BrowserstackConfig {}
14+
}
15+
}

src/launcher.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { promisify } from 'util'
2+
import { performance, PerformanceObserver } from 'perf_hooks'
3+
4+
import * as BrowserstackLocalLauncher from 'browserstack-local'
5+
import logger from '@wdio/logger'
6+
import type { Capabilities, Services, Options } from '@wdio/types'
7+
8+
import { BrowserstackConfig } from './types'
9+
10+
const log = logger('@wdio/browserstack-service')
11+
12+
type BrowserstackLocal = BrowserstackLocalLauncher.Local & {
13+
pid?: number;
14+
stop(callback: (err?: any) => void): void;
15+
}
16+
17+
export default class BrowserstackLauncherService implements Services.ServiceInstance {
18+
browserstackLocal?: BrowserstackLocal
19+
20+
constructor (
21+
private _options: BrowserstackConfig,
22+
capabilities: Capabilities.RemoteCapability,
23+
private _config: Options.Testrunner
24+
) {}
25+
26+
onPrepare (config?: Options.Testrunner, capabilities?: Capabilities.RemoteCapabilities) {
27+
if (!this._options.browserstackLocal) {
28+
return log.info('browserstackLocal is not enabled - skipping...')
29+
}
30+
31+
const opts = {
32+
key: this._config.key,
33+
forcelocal: true,
34+
onlyAutomate: true,
35+
...this._options.opts
36+
}
37+
38+
this.browserstackLocal = new BrowserstackLocalLauncher.Local()
39+
40+
if (Array.isArray(capabilities)) {
41+
capabilities.forEach((capability: Capabilities.DesiredCapabilities) => {
42+
if (!capability['bstack:options']) {
43+
capability['bstack:options'] = {}
44+
}
45+
capability['bstack:options'].local = true
46+
})
47+
} else if (typeof capabilities === 'object') {
48+
Object.entries(capabilities as Capabilities.MultiRemoteCapabilities).forEach(([, caps]) => {
49+
if (!(caps.capabilities as Capabilities.Capabilities)['bstack:options']) {
50+
(caps.capabilities as Capabilities.Capabilities)['bstack:options'] = {}
51+
}
52+
(caps.capabilities as Capabilities.Capabilities)['bstack:options']!.local = true
53+
})
54+
} else {
55+
throw TypeError('Capabilities should be an object or Array!')
56+
}
57+
58+
/**
59+
* measure TestingBot tunnel boot time
60+
*/
61+
const obs = new PerformanceObserver((list) => {
62+
const entry = list.getEntries()[0]
63+
log.info(`Browserstack Local successfully started after ${entry.duration}ms`)
64+
})
65+
66+
obs.observe({ entryTypes: ['measure'] })
67+
68+
let timer: NodeJS.Timeout
69+
performance.mark('tbTunnelStart')
70+
return Promise.race([
71+
promisify(this.browserstackLocal.start.bind(this.browserstackLocal))(opts),
72+
new Promise((resolve, reject) => {
73+
/* istanbul ignore next */
74+
timer = setTimeout(function () {
75+
reject('Browserstack Local failed to start within 60 seconds!')
76+
}, 60000)
77+
})]
78+
).then(function (result) {
79+
clearTimeout(timer)
80+
performance.mark('tbTunnelEnd')
81+
performance.measure('bootTime', 'tbTunnelStart', 'tbTunnelEnd')
82+
return Promise.resolve(result)
83+
}, function (err) {
84+
clearTimeout(timer)
85+
return Promise.reject(err)
86+
})
87+
}
88+
89+
onComplete () {
90+
if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
91+
return
92+
}
93+
94+
if (this._options.forcedStop) {
95+
return process.kill(this.browserstackLocal.pid as number)
96+
}
97+
98+
let timer: NodeJS.Timeout
99+
return Promise.race([
100+
new Promise<void>((resolve, reject) => {
101+
this.browserstackLocal?.stop((err: Error) => {
102+
if (err) {
103+
return reject(err)
104+
}
105+
resolve()
106+
})
107+
}),
108+
new Promise((resolve, reject) => {
109+
/* istanbul ignore next */
110+
timer = setTimeout(
111+
() => reject(new Error('Browserstack Local failed to stop within 60 seconds!')),
112+
60000
113+
)
114+
})]
115+
).then(function (result) {
116+
clearTimeout(timer)
117+
return Promise.resolve(result)
118+
}, function (err) {
119+
clearTimeout(timer)
120+
return Promise.reject(err)
121+
})
122+
}
123+
}

0 commit comments

Comments
 (0)