Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Browserstack service, improve Cucumber integration #5271

Merged
merged 12 commits into from
May 8, 2020
7 changes: 7 additions & 0 deletions packages/wdio-browserstack-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Instructions on how to install `WebdriverIO` can be found [here.](https://webdri
## Configuration

WebdriverIO has Browserstack support out of the box. You should simply set `user` and `key` in your `wdio.conf.js` file. This service plugin provides supports for [Browserstack Tunnel](https://www.browserstack.com/automate/node#setting-local-tunnel). Set `browserstackLocal: true` also to activate this feature.
Reporting of session status on BrowserStack will respect `strict` setting of Cucumber options.

```js
// wdio.conf.js
Expand All @@ -54,6 +55,12 @@ Set this to true to enable routing connections from Browserstack cloud through y
Type: `Boolean`<br>
Default: `false`

### preferScenarioName
Cucumber only. Set this to true to enable updating the session name to the Scenario name if only a single Scenario was ran. Useful when running in parallel with [wdio-cucumber-parallel-execution](https://github.com/SimitTomar/wdio-cucumber-parallel-execution).

Type: `Boolean`<br>
Default: `false`

### forcedStop
Set this to true to kill the browserstack process on complete, without waiting for the browserstack stop callback to be called. This is experimental and should not be used by all. Mostly necessary as a workaraound for [this issue](https://github.com/browserstack/browserstack-local-nodejs/issues/41).

Expand Down
6 changes: 6 additions & 0 deletions packages/wdio-browserstack-service/browserstack-service.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ interface BrowserstackConfig {
* You will also need to set `browserstack.local` to true in browser capabilities.
*/
browserstackLocal?: boolean;
/**
* Cucumber only. Set this to true to enable updating the session name to the Scenario name if only
* a single Scenario was ran. Useful when running in parallel
* with [wdio-cucumber-parallel-execution](https://github.com/SimitTomar/wdio-cucumber-parallel-execution).
*/
preferScenarioName?: boolean;
/**
* Set this to true to kill the browserstack process on complete, without waiting for the
* browserstack stop callback to be called. This is experimental and should not be used by all.
Expand Down
85 changes: 60 additions & 25 deletions packages/wdio-browserstack-service/src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ import { BROWSER_DESCRIPTION } from './constants'
const log = logger('@wdio/browserstack-service')

export default class BrowserstackService {
constructor (options, caps, config) {
constructor (options = {}, caps, config) {
this.config = config
this.failures = 0
this.sessionBaseUrl = 'https://api.browserstack.com/automate/sessions'
this.failReasons = []

// Cucumber specific
this.scenariosThatRan = []
this.preferScenarioName = Boolean(options.preferScenarioName)
this.strict = Boolean(config.cucumberOpts && config.cucumberOpts.strict)
// See https://github.com/cucumber/cucumber-js/blob/master/src/runtime/index.ts#L136
this.failureStatuses = ['failed', 'ambiguous', 'undefined', 'unknown']
this.strict && this.failureStatuses.push('pending')
}

/**
Expand All @@ -36,16 +44,24 @@ export default class BrowserstackService {
this.sessionBaseUrl = 'https://api-cloud.browserstack.com/app-automate/sessions'
}

this.scenariosThatRan = []

return this._printSessionURL()
}

afterSuite(suite) {
if (Object.prototype.hasOwnProperty.call(suite, 'error')) {
this.failures++
}
beforeSuite (suite) {
this.fullTitle = suite.title
this._update(this.sessionId, { name: this.fullTitle })
}

beforeFeature(uri, feature) {
this.fullTitle = feature.document.feature.name
this._update(this.sessionId, { name: this.fullTitle })
}

afterTest(test, context, results) {
const { error, passed } = results

this.fullTitle = (
/**
* Jasmine
Expand All @@ -57,32 +73,59 @@ export default class BrowserstackService {
`${test.parent} - ${test.title}`
)

if (!results.passed) {
this.failures++
this.failReason = (test.error && test.error.message ? test.error.message : 'Unknown Error')
if (!passed) {
this.failReasons.push((error && error.message) || 'Unknown Error')
}
}

after() {
return this._update(this.sessionId, this._getBody())
after(result) {
// For Cucumber: Checks scenarios that ran (i.e. not skipped) on the session
// Only 1 Scenario ran and option enabled => Redefine session name to Scenario's name
if(this.preferScenarioName && this.scenariosThatRan.length === 1){
this.fullTitle = this.scenariosThatRan.pop()
}

const hasReasons = Boolean(this.failReasons.filter(Boolean).length)

return this._update(this.sessionId, {
status: result === 0 ? 'passed' : 'failed',
name: this.fullTitle,
reason: hasReasons ? this.failReasons.join('\n') : undefined
})
}

/**
* For CucumberJS
*/

afterScenario(uri, feature, pickle, result) {
if (result.status === 'failed') {
++this.failures
afterScenario(uri, feature, pickle, results) {
let { exception, status } = results

if (status !== 'skipped') {
this.scenariosThatRan.push(pickle.name)
}

if (this.failureStatuses.includes(status)) {
exception = exception || (status === 'pending'
? `Some steps/hooks are pending for scenario "${pickle.name}"`
: 'Unknown Error')

this.failReasons.push(exception)
}
}

async onReload(oldSessionId, newSessionId) {
const hasReasons = Boolean(this.failReasons.filter(Boolean).length)

this.sessionId = newSessionId
await this._update(oldSessionId, this._getBody())
this.failures = 0
await this._update(oldSessionId, {
name: this.fullTitle,
status: hasReasons ? 'failed' : 'passed',
reason: hasReasons ? this.failReasons.join('\n') : undefined
})
this.scenariosThatRan = []
delete this.fullTitle
delete this.failReason
this.failReasons = []
this._printSessionURL()
}

Expand All @@ -94,14 +137,6 @@ export default class BrowserstackService {
})
}

_getBody() {
return {
status: this.failures === 0 ? 'passed' : 'failed',
name: this.fullTitle,
reason: this.failReason
}
}

async _printSessionURL() {
const capabilities = global.browser.capabilities

Expand Down
Loading