---id: frameworkstitle: Frameworks---WebdriverIO Runner has built-in support for [Mocha](http://mochajs.org/), [Jasmine](http://jasmine.github.io/), and [Cucumber.js](https://cucumber.io/). You can also integrate it with 3rd-party open-source frameworks, such as [Serenity/JS](#using-serenityjs).:::tip Integrating WebdriverIO with test frameworksTo integrate WebdriverIO with a test framework, you need an adapter package available on NPM.Note that the adapter package must be installed in the same location where WebdriverIO is installed.So, if you installed WebdriverIO globally, be sure to install the adapter package globally, too.:::Integrating WebdriverIO with a test framework lets you access the WebDriver instance using the global `browser` variablein your spec files or step definitions.Note that WebdriverIO will also take care of instantiating and ending the Selenium session, so you don't have to do ityourself.## Using MochaFirst, install the adapter package from NPM:```bash npm2yarnnpm install @wdio/mocha-framework --save-dev```By default WebdriverIO provides an [assertion library](assertion) that is built-in which you can start right away:```jsdescribe('my awesome website', () => {it('should do some assertions', async () => {await browser.url('https://webdriver.io')await expect(browser).toHaveTitle('WebdriverIO ยท Next-gen browser and mobile automation test framework for Node.js | WebdriverIO')})})```WebdriverIO supports Mocha's `BDD` (default), `TDD`, and `QUnit` [interfaces](https://mochajs.org/#interfaces).If you like to write your specs in TDD style, set the `ui` property in your `mochaOpts` config to `tdd`. Now your test files should be written like this:```jssuite('my awesome website', () => {test('should do some assertions', async () => {await browser.url('https://webdriver.io')await expect(browser).toHaveTitle('WebdriverIO ยท Next-gen browser and mobile automation test framework for Node.js | WebdriverIO')})})```If you want to define other Mocha-specific settings, you can do it with the `mochaOpts` key in your configuration file. A list of all options can be found on the [Mocha project website](https://mochajs.org/api/mocha).__Note:__ WebdriverIO does not support the deprecated usage of `done` callbacks in Mocha:```jsit('should test something', (done) => {done() // throws "done is not a function"})```### Mocha OptionsThe following options can be applied in your `wdio.conf.js` to configure your Mocha environment. __Note:__ not all options are supported, e.g. applying the `parallel` option will cause an error as the WDIO testrunner has its own way to run tests in parallel. You can pass these framework options as arguments, e.g.:```shwdio run wdio.conf.ts --mochaOpts.grep "my test" --mochaOpts.bail --no-mochaOpts.checkLeaks```This will pass along the following Mocha options:```ts{grep: ['my-test'],bail: truecheckLeacks: false}```The following Mocha options are supported:#### requireThe `require` option is useful when you want to add or extend some basic functionality (WebdriverIO framework option).Type: `string|string[]`<br />Default: `[]`#### compilersUse the given module(s) to compile files. Compilers will be included before requires (WebdriverIO framework option).Type: `string[]`<br />Default: `[]`#### allowUncaughtPropagate uncaught errors.Type: `boolean`<br />Default: `false`#### bailBail after first test failure.Type: `boolean`<br />Default: `false`#### checkLeaksCheck for global variable leaks.Type: `boolean`<br />Default: `false`#### delayDelay root suite execution.Type: `boolean`<br />Default: `false`#### fgrepTest filter given string.Type: `string`<br />Default: `null`#### forbidOnlyTests marked `only` fail the suite.Type: `boolean`<br />Default: `false`#### forbidPendingPending tests fail the suite.Type: `boolean`<br />Default: `false`#### fullTraceFull stacktrace upon failure.Type: `boolean`<br />Default: `false`#### globalVariables expected in global scope.Type: `string[]`<br />Default: `[]`#### grepTest filter given regular expression.Type: `RegExp|string`<br />Default: `null`#### invertInvert test filter matches.Type: `boolean`<br />Default: `false`#### retriesNumber of times to retry failed tests.Type: `number`<br />Default: `0`#### timeoutTimeout threshold value (in ms).Type: `number`<br />Default: `30000`## Using JasmineFirst, install the adapter package from NPM:```bash npm2yarnnpm install @wdio/jasmine-framework --save-dev```You can then configure your Jasmine environment by setting a `jasmineOpts` property in your config. A list of all options can be found on the [Jasmine project website](https://jasmine.github.io/api/3.5/Configuration.html).### Jasmine OptionsThe following options can be applied in your `wdio.conf.js` to configure your Jasmine environment using the `jasmineOpts` property. For more information on these configuration options, check out the [Jasmine docs](https://jasmine.github.io/api/edge/Configuration). You can pass these framework options as arguments, e.g.:```shwdio run wdio.conf.ts --jasmineOpts.grep "my test" --jasmineOpts.failSpecWithNoExpectations --no-jasmineOpts.random```This will pass along the following Mocha options:```ts{grep: ['my-test'],bail: truecheckLeacks: false}```The following Jasmine options are supported:#### defaultTimeoutIntervalDefault Timeout Interval for Jasmine operations.Type: `number`<br />Default: `60000`#### helpersArray of filepaths (and globs) relative to spec_dir to include before jasmine specs.Type: `string[]`<br />Default: `[]`#### requiresThe `requires` option is useful when you want to add or extend some basic functionality.Type: `string[]`<br />Default: `[]`#### randomWhether to randomize spec execution order.Type: `boolean`<br />Default: `true`#### seedSeed to use as the basis of randomization. Null causes the seed to be determined randomly at the start of execution.Type: `Function`<br />Default: `null`#### failSpecWithNoExpectationsWhether to fail the spec if it ran no expectations. By default a spec that ran no expectations is reported as passed. Setting this to true will report such spec as a failure.Type: `boolean`<br />Default: `false`#### oneFailurePerSpecWhether to cause specs to only have one expectation failure.Type: `boolean`<br />Default: `false`#### specFilterFunction to use to filter specs.Type: `Function`<br />Default: `(spec) => true`#### grepOnly run tests matching this string or regexp. (Only applicable if no custom `specFilter` function is set)Type: `string|Regexp`<br />Default: `null`#### invertGrepIf true it inverts the matching tests and only runs tests that don't match with the expression used in `grep`. (Only applicable if no custom `specFilter` function is set)Type: `boolean`<br />Default: `false`## Using CucumberFirst, install the adapter package from NPM:```bash npm2yarnnpm install @wdio/cucumber-framework --save-dev```If you want to use Cucumber, set the `framework` property to `cucumber` by adding `framework: 'cucumber'` to the [config file](configurationfile) .Options for Cucumber can be given in the config file with `cucumberOpts`. Check out the whole list of options [here](https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-cucumber-framework#cucumberopts-options).To get up and running quickly with Cucumber, have a look on our [`cucumber-boilerplate`](https://github.com/webdriverio/cucumber-boilerplate) project that comes with all the step definitions you need to get stared, and you'll be writing feature files right away.### Cucumber OptionsThe following options can be applied in your `wdio.conf.js` to configure your Cucumber environment using the `cucumberOpts` property::::tip Adjusting options through the command lineThe `cucumberOpts`, such as custom `tags` for filtering tests, can be specified through the command line. This is accomplished by using the `cucumberOpts.{optionName}="value"` format.For example, if you want to run only the tests that are tagged with `@smoke`, you can use the following command:```sh# When you only want to run tests that hold the tag "@smoke"npx wdio run ./wdio.conf.js --cucumberOpts.tags="@smoke"npx wdio run ./wdio.conf.js --cucumberOpts.name="some scenario name" --cucumberOpts.failFast```This command sets the `tags` option in `cucumberOpts` to `@smoke`, ensuring that only tests with this tag are executed.:::#### backtraceShow full backtrace for errors.Type: `Boolean`<br />Default: `true`#### requireModuleRequire modules prior to requiring any support files.Type: `string[]`<br />Default: `[]`<br />Example:```jscucumberOpts: {requireModule: ['@babel/register']// orrequireModule: [['@babel/register',{rootMode: 'upward',ignore: ['node_modules']}]]}```#### failFastAbort the run on first failure.Type: `boolean`<br />Default: `false`#### nameOnly execute the scenarios with name matching the expression (repeatable).Type: `RegExp[]`<br />Default: `[]`#### requireRequire files containing your step definitions before executing features. You can also specify a glob to your step definitions.Type: `string[]`<br />Default: `[]`Example:```jscucumberOpts: {require: [path.join(__dirname, 'step-definitions', 'my-steps.js')]}```#### importPaths to where your support code is, for ESM.Type: `String[]`<br />Default: `[]`Example:```jscucumberOpts: {import: [path.join(__dirname, 'step-definitions', 'my-steps.js')]}```#### strictFail if there are any undefined or pending steps.Type: `boolean`<br />Default: `false`#### tagsOnly execute the features or scenarios with tags matching the expression.Please see the [Cucumber documentation](https://docs.cucumber.io/cucumber/api/#tag-expressions) for more details.Type: `String`<br />Default: ``#### timeoutTimeout in milliseconds for step definitions.Type: `Number`<br />Default: `30000`#### retrySpecify the number of times to retry failing test cases.Type: `Number`<br />Default: `0`#### retryTagFilterOnly retries the features or scenarios with tags matching the expression (repeatable). This option requires '--retry' to be specified.Type: `RegExp`#### languageDefault language for your feature filesType: `String`<br />Default: `en`#### orderRun tests in defined / random orderType: `String`<br />Default: `defined`#### formatName and output file path of formatter to use.WebdriverIO primarily supports only the [Formatters](https://github.com/cucumber/cucumber-js/blob/main/docs/formatters.md) that writes output to a file.Type: `string[]`<br />#### formatOptionsOptions to be provided to formattersType: `object`<br />#### tagsInTitleAdd cucumber tags to feature or scenario nameType: `Boolean`<br />Default: `false`***Please note that this is a @wdio/cucumber-framework specific option and not recognized by cucumber-js itself***<br/>#### ignoreUndefinedDefinitionsTreat undefined definitions as warnings.Type: `Boolean`<br />Default: `false`***Please note that this is a @wdio/cucumber-framework specific option and not recognized by cucumber-js itself***<br/>#### failAmbiguousDefinitionsTreat ambiguous definitions as errors.Type: `Boolean`<br />Default: `false`***Please note that this is a @wdio/cucumber-framework specific option and not recognized by cucumber-js itself***<br/>#### tagExpressionOnly execute the features or scenarios with tags matching the expression.Please see the [Cucumber documentation](https://docs.cucumber.io/cucumber/api/#tag-expressions) for more details.Type: `String`<br />Default: ``***Please note that this option would be deprecated in future. Use [`tags`](#tags) config property instead***#### profileSpecify the profile to use.Type: `string[]`<br />Default: `[]`***Kindly take note that only specific values (worldParameters, name, retryTagFilter) are supported within profiles, as `cucumberOpts` takes precedence. Additionally, when using a profile, make sure that the mentioned values are not declared within `cucumberOpts`.***### Skipping tests in cucumberNote that if you want to skip a test using regular cucumber test filtering capabilities available in `cucumberOpts`, you will do it for all the browsers and devices configured in the capabilities. In order to be able to skip scenarios only for specific capabilities combinations without having a session started if not necessary, webdriverio provides the following specific tag syntax for cucumber:`@skip([condition])`were condition is an optional combination of capabilities properties with their values that when **all** matched with cause the tagged scenario or feature to be skipped. Of course you can add several tags to scenarios and features to skip a tests under several different conditions.You can also use the '@skip' annotation to skip tests without changing `tagExpression'. In this case the skipped tests will be displayed in the test report.Here you have some examples of this syntax:- `@skip` or `@skip()`: will always skip the tagged item- `@skip(browserName="chrome")`: the test will not be executed against chrome browsers.- `@skip(browserName="firefox";platformName="linux")`: will skip the test in firefox over linux executions.- `@skip(browserName=["chrome","firefox"])`: tagged items will be skipped for both chrome and firefox browsers.- `@skip(browserName=/i.*explorer/)`: capabilities with browsers matching the regexp will be skipped (like `iexplorer`, `internet explorer`, `internet-explorer`, ...).### Import Step Definition HelperIn order to use step definition helper like `Given`, `When` or `Then` or hooks, you are suppose to import then from `@cucumber/cucumber`, e.g. like this:```jsimport { Given, When, Then } from '@cucumber/cucumber'```Now, if you use Cucumber already for other types of tests unrelated to WebdriverIO for which you use a specific version you need to import these helpers in your e2e tests from the WebdriverIO Cucumber package, e.g.:```jsimport { Given, When, Then, world, context } from '@wdio/cucumber-framework'```This ensures that you use the right helpers within the WebdriverIO framework and allows you to use an independent Cucumber version for other types of testing.### Publishing ReportCucumber provides a feature to publish your test run reports to `https://reports.cucumber.io/`, which can be controlled either by setting the `publish` flag in `cucumberOpts` or by configuring the `CUCUMBER_PUBLISH_TOKEN` environment variable. However, when you use `WebdriverIO` for test execution, there's a limitation with this approach. It updates the reports separately for each feature file, making it difficult to view a consolidated report.To overcome this limitation, we've introduced a promise-based method called `publishCucumberReport` within `@wdio/cucumber-framework`. This method should be called in the `onComplete` hook, which is the optimal place to invoke it. `publishCucumberReport` requires the input of the report directory where cucumber message reports are stored.You can generate `cucumber message` reports by configuring the `format` option in your `cucumberOpts`. It's highly recommended to provide a dynamic file name within the `cucumber message` format option to prevent overwriting reports and ensure that each test run is accurately recorded.Before using this function, make sure to set the following environment variables:- CUCUMBER_PUBLISH_REPORT_URL: The URL where you want to publish the Cucumber report. If not provided, the default URL 'https://messages.cucumber.io/api/reports' will be used.- CUCUMBER_PUBLISH_REPORT_TOKEN: The authorization token required to publish the report. If this token is not set, the function will exit without publishing the report.Here's an example of the necessary configurations and code samples for implementation:```javascriptimport { v4 as uuidv4 } from 'uuid'import { publishCucumberReport } from '@wdio/cucumber-framework';export const config = {// ... Other Configuration OptionscucumberOpts: {// ... Cucumber Options Configurationformat: [['message', `./reports/${uuidv4()}.ndjson`],['json', './reports/test-report.json']]},async onComplete() {await publishCucumberReport('./reports');}}```Please note that `./reports/` is the directory where `cucumber message` reports will be stored.## Using Serenity/JS[Serenity/JS](https://serenity-js.org?pk_campaign=wdio8&pk_source=webdriver.io) is an open-source framework designed to make acceptance and regression testing of complex software systems faster, more collaborative, and easier to scale.For WebdriverIO test suites, Serenity/JS offers:- [Enhanced Reporting](https://serenity-js.org/handbook/reporting/?pk_campaign=wdio8&pk_source=webdriver.io) - You can use Serenity/JSas a drop-in replacement of any built-in WebdriverIO framework to produce in-depth test execution reports and living documentation of your project.- [Screenplay Pattern APIs](https://serenity-js.org/handbook/design/screenplay-pattern/?pk_campaign=wdio8&pk_source=webdriver.io) - To make your test code portable and reusable across projects and teams,Serenity/JS gives you an optional [abstraction layer](https://serenity-js.org/api/webdriverio?pk_campaign=wdio8&pk_source=webdriver.io) on top of native WebdriverIO APIs.- [Integration Libraries](https://serenity-js.org/api/core/?pk_campaign=wdio8&pk_source=webdriver.io) - For test suites that follow the Screenplay Pattern,Serenity/JS also provides optional integration libraries to help you write [API tests](https://serenity-js.org/api/rest/?pk_campaign=wdio8&pk_source=webdriver.io),[manage local servers](https://serenity-js.org/api/local-server/?pk_campaign=wdio8&pk_source=webdriver.io), [perform assertions](https://serenity-js.org/api/assertions/?pk_campaign=wdio8&pk_source=webdriver.io), and more!### Installing Serenity/JSTo add Serenity/JS to an [existing WebdriverIO project](https://webdriver.io/docs/gettingstarted), install the following Serenity/JS modules from NPM:```sh npm2yarnnpm install @serenity-js/{core,web,webdriverio,assertions,console-reporter,serenity-bdd} --save-dev```Learn more about Serenity/JS modules:- [`@serenity-js/core`](https://serenity-js.org/api/core/?pk_campaign=wdio8&pk_source=webdriver.io)- [`@serenity-js/web`](https://serenity-js.org/api/web/?pk_campaign=wdio8&pk_source=webdriver.io)- [`@serenity-js/webdriverio`](https://serenity-js.org/api/webdriverio/?pk_campaign=wdio8&pk_source=webdriver.io)- [`@serenity-js/assertions`](https://serenity-js.org/api/assertions/?pk_campaign=wdio8&pk_source=webdriver.io)- [`@serenity-js/console-reporter`](https://serenity-js.org/api/console-reporter/?pk_campaign=wdio8&pk_source=webdriver.io)- [`@serenity-js/serenity-bdd`](https://serenity-js.org/api/serenity-bdd/?pk_campaign=wdio8&pk_source=webdriver.io)### Configuring Serenity/JSTo enable integration with Serenity/JS, configure WebdriverIO as follows:<Tabs><TabItem value="wdio-conf-typescript" label="TypeScript" default>```typescript title="wdio.conf.ts"import { WebdriverIOConfig } from '@serenity-js/webdriverio';export const config: WebdriverIOConfig = {// Tell WebdriverIO to use Serenity/JS frameworkframework: '@serenity-js/webdriverio',// Serenity/JS configurationserenity: {// Configure Serenity/JS to use the appropriate adapter for your test runnerrunner: 'cucumber',// runner: 'mocha',// runner: 'jasmine',// Register Serenity/JS reporting services, a.k.a. the "stage crew"crew: [// Optional, print test execution results to standard output'@serenity-js/console-reporter',// Optional, produce Serenity BDD reports and living documentation (HTML)'@serenity-js/serenity-bdd',[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],// Optional, automatically capture screenshots upon interaction failure[ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfFailures' } ],]},// Configure your Cucumber runnercucumberOpts: {// see Cucumber configuration options below},// ... or Jasmine runnerjasmineOpts: {// see Jasmine configuration options below},// ... or Mocha runnermochaOpts: {// see Mocha configuration options below},runner: 'local',// Any other WebdriverIO configuration};```</TabItem><TabItem value="wdio-conf-javascript" label="JavaScript">```typescript title="wdio.conf.js"export const config = {// Tell WebdriverIO to use Serenity/JS frameworkframework: '@serenity-js/webdriverio',// Serenity/JS configurationserenity: {// Configure Serenity/JS to use the appropriate adapter for your test runnerrunner: 'cucumber',// runner: 'mocha',// runner: 'jasmine',// Register Serenity/JS reporting services, a.k.a. the "stage crew"crew: ['@serenity-js/console-reporter','@serenity-js/serenity-bdd',[ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],[ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfFailures' } ],]},// Configure your Cucumber runnercucumberOpts: {// see Cucumber configuration options below},// ... or Jasmine runnerjasmineOpts: {// see Jasmine configuration options below},// ... or Mocha runnermochaOpts: {// see Mocha configuration options below},runner: 'local',// Any other WebdriverIO configuration};```</TabItem></Tabs>Learn more about:- [Serenity/JS Cucumber configuration options](https://serenity-js.org/api/cucumber-adapter/interface/CucumberConfig/?pk_campaign=wdio8&pk_source=webdriver.io)- [Serenity/JS Jasmine configuration options](https://serenity-js.org/api/jasmine-adapter/interface/JasmineConfig/?pk_campaign=wdio8&pk_source=webdriver.io)- [Serenity/JS Mocha configuration options](https://serenity-js.org/api/mocha-adapter/interface/MochaConfig/?pk_campaign=wdio8&pk_source=webdriver.io)- [WebdriverIO configuration file](configurationfile)### Producing Serenity BDD reports and living documentation[Serenity BDD reports and living documentation](https://serenity-bdd.github.io/docs/reporting/the_serenity_reports) are generated by [Serenity BDD CLI](https://github.com/serenity-bdd/serenity-core/tree/main/serenity-cli),a Java program downloaded and managed by the [`@serenity-js/serenity-bdd`](https://serenity-js.org/api/serenity-bdd/?pk_campaign=wdio8&pk_source=webdriver.io) module.To produce Serenity BDD reports, your test suite must:- download the Serenity BDD CLI, by calling `serenity-bdd update` which caches the CLI `jar` locally- produce intermediate Serenity BDD `.json` reports, by registering [`SerenityBDDReporter`](https://serenity-js.org/api/serenity-bdd/class/SerenityBDDReporter/?pk_campaign=wdio8&pk_source=webdriver.io) as per the [configuration instructions](#configuring-serenityjs)- invoke the Serenity BDD CLI when you want to produce the report, by calling `serenity-bdd run`The pattern used by all the [Serenity/JS Project Templates](https://serenity-js.org/handbook/project-templates/?pk_campaign=wdio8&pk_source=webdriver.io#webdriverio) relieson using:- a [`postinstall`](https://docs.npmjs.com/cli/v9/using-npm/scripts#life-cycle-operation-order) NPM script to download the Serenity BDD CLI- [`npm-failsafe`](https://www.npmjs.com/package/npm-failsafe) to run the reporting process even if the test suite itself has failed (which is precisely when you need test reports the most...).- [`rimraf`](https://www.npmjs.com/package/rimraf) as a convenience method to remove any test reports left over from the previous run```json title="package.json"{"scripts": {"postinstall": "serenity-bdd update","clean": "rimraf target","test": "failsafe clean test:execute test:report","test:execute": "wdio wdio.conf.ts","test:report": "serenity-bdd run"}}```To learn more about the `SerenityBDDReporter`, please consult:- installation instructions in [`@serenity-js/serenity-bdd` documentation](https://serenity-js.org/api/serenity-bdd/?pk_campaign=wdio8&pk_source=webdriver.io),- configuration examples in [`SerenityBDDReporter` API docs](https://serenity-js.org/api/serenity-bdd/class/SerenityBDDReporter/?pk_campaign=wdio8&pk_source=webdriver.io),- [Serenity/JS examples on GitHub](https://github.com/serenity-js/serenity-js/tree/main/examples).### Using Serenity/JS Screenplay Pattern APIsThe [Screenplay Pattern](https://serenity-js.org/handbook/design/screenplay-pattern/?pk_campaign=wdio8&pk_source=webdriver.io) is an innovative, user-centred approach to writing high-quality automated acceptance tests. It steers you towards an effective use of layers of abstraction,helps your test scenarios capture the business vernacular of your domain, and encourages good testing and software engineering habits on your team.By default, when you register `@serenity-js/webdriverio` as your WebdriverIO `framework`,Serenity/JS configures a default [cast](https://serenity-js.org/api/core/class/Cast/?pk_campaign=wdio8&pk_source=webdriver.io) of [actors](https://serenity-js.org/api/core/class/Actor/?pk_campaign=wdio8&pk_source=webdriver.io),where every actor can:- [`BrowseTheWebWithWebdriverIO`](https://serenity-js.org/api/webdriverio/class/BrowseTheWebWithWebdriverIO/?pk_campaign=wdio8&pk_source=webdriver.io)- [`TakeNotes.usingAnEmptyNotepad()`](https://serenity-js.org/api/core/class/TakeNotes/?pk_campaign=wdio8&pk_source=webdriver.io)This should be enough to help you get started with introducing test scenarios that follow the Screenplay Pattern even to an existing test suite, for example:```typescript title="specs/example.spec.ts"import { actorCalled } from '@serenity-js/core'import { Navigate, Page } from '@serenity-js/web'import { Ensure, equals } from '@serenity-js/assertions'describe('My awesome website', () => {it('can have test scenarios that follow the Screenplay Pattern', async () => {await actorCalled('Alice').attemptsTo(Navigate.to(`https://webdriver.io`),Ensure.that(Page.current().title(),equals(`WebdriverIO ยท Next-gen browser and mobile automation test framework for Node.js | WebdriverIO`)),)})it('can have non-Screenplay scenarios too', async () => {await browser.url('https://webdriver.io')await expect(browser).toHaveTitle('WebdriverIO ยท Next-gen browser and mobile automation test framework for Node.js | WebdriverIO')})})```To learn more about the Screenplay Pattern, check out:- [The Screenplay Pattern](https://serenity-js.org/handbook/design/screenplay-pattern/?pk_campaign=wdio8&pk_source=webdriver.io)- [Web testing with Serenity/JS](https://serenity-js.org/handbook/web-testing/?pk_campaign=wdio8&pk_source=webdriver.io)- ["BDD in Action, Second Edition"](https://www.manning.com/books/bdd-in-action-second-edition)