Repository: Codeception/CodeceptJS (js)

Issue:
Can not open more than one additional tab
#### What are you trying to achieve?
When I use I.openNewTab() for the second time, it does not open a new tab.
#### What do you get instead?
As you can see when running code below the 3rd assertion fails because I.grabNumberOfOpenTabs() returns 2 instead of 3 as expected

```js
    I.amOnPage('https://www.google.com');
    I.waitForElement('div#searchform');
    let tabs = await I.grabNumberOfOpenTabs();
    var assert = require('assert');
    assert.equal(tabs, 1);
    I.openNewTab();
    I.amOnPage('https://www.yahoo.com');
    tabs = await I.grabNumberOfOpenTabs();
    assert.equal(tabs, 2);
    I.openNewTab();
    I.amOnPage('https://www.codecept.io');
    tabs = await I.grabNumberOfOpenTabs();
    assert.equal(tabs, 3);
```

### Details

* CodeceptJS version: v2.3.0

#### Console output
```bash
Helpers: WebDriver
Plugins: screenshotOnFail

Dashboard test.js --
    [1] Starting recording promises
    Emitted | suite.before ([object Object])
  Open more than one tab
    Emitted | test.before ([object Object])
    Emitted | test.start ([object Object])
    Emitted | step.before (I am on page "https://www.google.com")
    Emitted | step.after (I am on page "https://www.google.com")
    Emitted | step.before (I wait for element "div#searchform")
    Emitted | step.after (I wait for element "div#searchform")
    Emitted | step.before (I grab number of open tabs )
    Emitted | step.after (I grab number of open tabs )
    Emitted | step.start (I am on page "https://www.google.com")
    I am on page "https://www.google.com"
    Emitted | step.passed (I am on page "https://www.google.com")
    Emitted | step.finish (I am on page "https://www.google.com")
    Emitted | step.start (I wait for element "div#searchform")
    I wait for element "div#searchform"
    Emitted | step.passed (I wait for element "div#searchform")
    Emitted | step.finish (I wait for element "div#searchform")
    Emitted | step.start (I grab number of open tabs )
    I grab number of open tabs 
    › [Tabs] Total 1
    Emitted | step.passed (I grab number of open tabs )
    Emitted | step.finish (I grab number of open tabs )
    Emitted | step.before (I open new tab )
    Emitted | step.after (I open new tab )
    Emitted | step.before (I am on page "https://www.yahoo.com")
    Emitted | step.after (I am on page "https://www.yahoo.com")
    Emitted | step.before (I grab number of open tabs )
    Emitted | step.after (I grab number of open tabs )
    Emitted | step.start (I open new tab )
    I open new tab 
    Emitted | step.passed (I open new tab )
    Emitted | step.finish (I open new tab )
    Emitted | step.start (I am on page "https://www.yahoo.com")
    I am on page "https://www.yahoo.com"
    Emitted | step.passed (I am on page "https://www.yahoo.com")
    Emitted | step.finish (I am on page "https://www.yahoo.com")
    Emitted | step.start (I grab number of open tabs )
    I grab number of open tabs 
    › [Tabs] Total 2
    Emitted | step.passed (I grab number of open tabs )
    Emitted | step.finish (I grab number of open tabs )
    Emitted | step.before (I open new tab )
    Emitted | step.after (I open new tab )
    Emitted | step.before (I am on page "https://www.codecept.io")
    Emitted | step.after (I am on page "https://www.codecept.io")
    Emitted | step.before (I grab number of open tabs )
    Emitted | step.after (I grab number of open tabs )
    Emitted | step.start (I open new tab )
    I open new tab 
    Emitted | step.passed (I open new tab )
    Emitted | step.finish (I open new tab )
    Emitted | step.start (I am on page "https://www.codecept.io")
    I am on page "https://www.codecept.io"
    Emitted | step.passed (I am on page "https://www.codecept.io")
    Emitted | step.finish (I am on page "https://www.codecept.io")
    Emitted | step.start (I grab number of open tabs )
    I grab number of open tabs 
    › [Tabs] Total 2
    Emitted | step.passed (I grab number of open tabs )
    Emitted | step.finish (I grab number of open tabs )
    [1] Error | AssertionError [ERR_ASSERTION]: 2 == 3
    [1] Starting <teardown> session
    Emitted | test.failed ([object Object])
    Emitted | test.finish ([object Object])
    [1] <teardown> Stopping recording promises
 › <screenshotOnFail> Test failed, saving screenshot
 › Screenshot has been saved to /Users/jplegoff/dev/tds-dashboard/output/Open_more_than_one_tab.failed.png
  ✖ FAILED in 5338ms

    [2] Starting recording promises
    Emitted | test.after ([object Object])
    Emitted | suite.after ([object Object])

-- FAILURES:

  1) Dashboard test.js
       Open more than one tab:

      2 == 3
      + expected - actual

      -2
      +3
      
  ssertionError [ERR_ASSERTION]: 2 == 3
      at Test.Scenario (dashboard_test.js:17:12)
      at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:160:7)```


Pull request:
fix #1874 Can not open more than one tab with webdriver helper
When using client.newWindow function in openNewTab, windowName parameter is not set. This makes impossible to open a second tab.

Adding optional parameters to openNewTab allows to fix this issue without having a breaking change

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f1fdc2d..367e8b2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,24 @@
+## 2.3.1
+
+* [MockRequest] Polly helper was renamed to MockRequest.
+* [MockRequest][WebDriver] [Mocking requests](https://codecept.io/webdriver#mocking-requests) is now available in WebDriver. Thanks @radhey1851
+* [Puppeteer] Ensure configured user agent and/or window size is applied to all pages. See #1862 by @martomo
+* Improve handling of xpath locators with round brackets by @nitschSB. See #1870
+* Use WebDriver capabilities config in wdio plugin. #1869 by @quekshuy
+
 ## 2.3.0
 
-* [GraphQL][GraphQLDataFactory] **Helpers for data management over GraphQL** APIs added. By @radhey1851.
-  * Learn how to [use GraphQL helper](https://codecept.io/data#graphql) to access GarphQL API
-  * And how to combine it with [GraphQLDataFactory](https://codecept.io/data#graphql-data-factory) to generate and persist test data.
-* **Updated to use Mocha 6**. See #1802 by @elukoyanov
-* **Parallel testing by workers introduced** by @VikalpP and @davertmik. Use `run-workers` command as faster and simpler alternative to `run-multiple`. Requires NodeJS v12
+
+* **[Parallel testing by workers](https://codecept.io/parallel#parallel-execution-by-workers) introduced** by @VikalpP and @davertmik. Use `run-workers` command as faster and simpler alternative to `run-multiple`. Requires NodeJS v12
 
 ```
 # run all tests in parallel using 3 workers
 npx codeceptjs run-workers 3
 ```
-
+* [GraphQL][GraphQLDataFactory] **Helpers for data management over GraphQL** APIs added. By @radhey1851.
+  * Learn how to [use GraphQL helper](https://codecept.io/data#graphql) to access GarphQL API
+  * And how to combine it with [GraphQLDataFactory](https://codecept.io/data#graphql-data-factory) to generate and persist test data.
+* **Updated to use Mocha 6**. See #1802 by @elukoyanov
 * Added `dry-run` command to print steps of test scenarios without running them. Fails to execute scenarios with `grab*` methods or custom code. See #1825 for more details.
 
 ```
@@ -19,7 +27,7 @@ npx codeceptjs dry-run
 
 * [Appium] Optimization when clicking, searching for fields by accessibility id. See #1777 by @gagandeepsingh26
 * [TestCafe] Fixed `switchTo` by @KadoBOT
-* [WebDrover] Added geolocation actions by @PeterNgTr
+* [WebDriver] Added geolocation actions by @PeterNgTr
     * `grabGeoLocation()`
     * `setGeoLocation()`
 * [Polly] Check typeof arguments for mock requests by @VikalpP. Fixes #1815
diff --git a/docs/changelog.md b/docs/changelog.md
index 15b473f6..273d3bf3 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,12 +5,26 @@ title: Releases
 
 ## 2.3.0
 
-* [GraphQL][GraphQLDataFactory] Helpers introduced for **data management over GraphQL** APIs. By **[radhey1851](https://github.com/radhey1851)**.
-  * Learn how to [use GraphQL helper docs](https://codecept.io/data#graphql)
-  * And how to combine it with **[GraphQLDataFactory]** * **Updated to use Mocha 6**. See [#1802](https://github.com/Codeception/CodeceptJS/issues/1802) by **[elukoyanov](https://github.com/elukoyanov)**
+
+* **[Parallel testing by workers](https://codecept.io/parallel#parallel-execution-by-workers) introduced** by **[VikalpP](https://github.com/VikalpP)** and **[davertmik](https://github.com/davertmik)**. Use `run-workers` command as faster and simpler alternative to `run-multiple`. Requires NodeJS v12
+
+```
+# run all tests in parallel using 3 workers
+npx codeceptjs run-workers 3
+```
+* [GraphQL][GraphQLDataFactory] **Helpers for data management over GraphQL** APIs added. By **[radhey1851](https://github.com/radhey1851)**.
+  * Learn how to [use GraphQL helper](https://codecept.io/data#graphql) to access GarphQL API
+  * And how to combine it with [GraphQLDataFactory](https://codecept.io/data#graphql-data-factory) to generate and persist test data.
+* **Updated to use Mocha 6**. See [#1802](https://github.com/Codeception/CodeceptJS/issues/1802) by **[elukoyanov](https://github.com/elukoyanov)**
+* Added `dry-run` command to print steps of test scenarios without running them. Fails to execute scenarios with `grab*` methods or custom code. See [#1825](https://github.com/Codeception/CodeceptJS/issues/1825) for more details.
+
+```
+npx codeceptjs dry-run
+```
+
 * **[Appium]** Optimization when clicking, searching for fields by accessibility id. See [#1777](https://github.com/Codeception/CodeceptJS/issues/1777) by **[gagandeepsingh26](https://github.com/gagandeepsingh26)**
 * **[TestCafe]** Fixed `switchTo` by **[KadoBOT](https://github.com/KadoBOT)**
-* **[WebDrover]** Added geolocation actions by **[PeterNgTr](https://github.com/PeterNgTr)**
+* **[WebDriver]** Added geolocation actions by **[PeterNgTr](https://github.com/PeterNgTr)**
     * `grabGeoLocation()`
     * `setGeoLocation()`
 * **[Polly]** Check typeof arguments for mock requests by **[VikalpP](https://github.com/VikalpP)**. Fixes [#1815](https://github.com/Codeception/CodeceptJS/issues/1815)
@@ -18,6 +32,10 @@ title: Releases
   * `codeceptjs` command prints list of all available commands
   * added `codeceptjs -V` flag to print version information
   * warns on unknown command
+* Added TypeScript files support to `run-multiple` by **[z4o4z](https://github.com/z4o4z)**
+* Fixed element position bug in locator builder. See [#1829](https://github.com/Codeception/CodeceptJS/issues/1829) by **[AnotherAnkor](https://github.com/AnotherAnkor)**
+* Various TypeScript typings updates by **[elukoyanov](https://github.com/elukoyanov)** and **[Vorobeyko](https://github.com/Vorobeyko)**
+* Added `event.step.comment` event for all comment steps like `I.say` or gherking steps.
 
 ## 2.2.1
 
diff --git a/docs/helpers/MockRequest.md b/docs/helpers/MockRequest.md
new file mode 100644
index 00000000..20acbcdf
--- /dev/null
+++ b/docs/helpers/MockRequest.md
@@ -0,0 +1,114 @@
+---
+id: MockRequest
+title: MockRequest
+---
+
+<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
+
+## MockRequest
+
+Extends Helper
+
+This helper allows to mock requests while running tests in Puppeteer or WebDriver.
+For instance, you can block calls to 3rd-party services like Google Analytics, CDNs.
+Another way of using is to emulate requests from server by passing prepared data.
+
+### Installations
+
+Requires [Polly.js][1] library by Netflix installed
+
+    npm i @pollyjs/core @pollyjs/adapter-puppeteer --save-dev
+
+Requires Puppeteer helper or WebDriver helper enabled
+
+### Configuration
+
+Just enable helper in config file:
+
+```js
+helpers: {
+   Puppeteer: {
+     // regular Puppeteer config here
+   },
+   MockRequest: {}
+}
+```
+
+The same can be done when using WebDriver helper..
+
+### Usage
+
+Use `I.mockRequest` to intercept and mock requests.
+
+### Parameters
+
+-   `config`  
+
+### \_checkAndStartMocking
+
+Starts mocking if it's not started yet.
+
+### \_connectPuppeteer
+
+Creates a polly instance by registering puppeteer adapter with the instance
+
+#### Parameters
+
+-   `title` any 
+
+### \_connectWebDriver
+
+Creates polly object in the browser window context using xhr and fetch adapters,
+after loading PollyJs and adapter scripts.
+
+#### Parameters
+
+-   `title` any 
+
+### mockRequest
+
+Mock response status
+
+```js
+I.mockRequest('GET', '/api/users', 200);
+I.mockRequest('ANY', '/secretsRoutes/', 403);
+I.mockRequest('POST', '/secrets', { secrets: 'fakeSecrets' });
+I.mockRequest('GET', '/api/users/1', 404, 'User not found');
+```
+
+Multiple requests
+
+```js
+I.mockRequest('GET', ['/secrets', '/v2/secrets'], 403);
+```
+
+#### Parameters
+
+-   `method` [string][2] request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`.
+-   `oneOrMoreUrls` ([string][2] \| [array][3]) url(s) to mock. Can be exact URL, a pattern, or an array of URLs.
+-   `dataOrStatusCode` ([number][4] \| [string][2] \| [object][5]) status code when number provided. A response body otherwise
+-   `additionalData` ([string][2] \| [object][5]) response body when a status code is set by previous parameter. 
+
+### startMocking
+
+Starts mocking of http requests.
+Used by mockRequest method to automatically start
+mocking requests.
+
+#### Parameters
+
+-   `title` any  
+
+### stopMocking
+
+Stops mocking requests.
+
+[1]: https://netflix.github.io/pollyjs/#/
+
+[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
+
+[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
+
+[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
+
+[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
diff --git a/docs/helpers/Polly.md b/docs/helpers/Polly.md
index c1ebb94a..d1d8f3e2 100644
--- a/docs/helpers/Polly.md
+++ b/docs/helpers/Polly.md
@@ -7,11 +7,12 @@ title: Polly
 
 ## Polly
 
-Extends Helper
+Extends MockRequest
 
-This helper allows to mock requests while running tests in Puppeteer.
-For instance, you can block calls to 3rd-party services like Google Analytics, CDNs.
-Another way of using is to emulate requests from server by passing prepared data.
+This helper works the same as MockRequest helper. It has been included for backwards compatibility
+reasons. So use MockRequest helper instead of this.
+
+Please refer to MockRequest helper documentation for details.
 
 ### Installations
 
@@ -19,7 +20,7 @@ Requires [Polly.js][1] library by Netflix installed
 
     npm i @pollyjs/core @pollyjs/adapter-puppeteer --save-dev
 
-Requires Puppeteer helper enabled
+Requires Puppeteer helper or WebDriver helper enabled
 
 ### Configuration
 
@@ -34,52 +35,10 @@ helpers: {
 }
 ```
 
+The same can be done when using WebDriver helper..
+
 ### Usage
 
 Use `I.mockRequest` to intercept and mock requests.
 
-### Parameters
-
--   `config`  
-
-### \_checkAndStartMocking
-
-Starts mocking if it's not started yet.
-
-### mockRequest
-
-Mock response status
-
-```js
-I.mockRequest('GET', '/api/users', 200);
-I.mockRequest('ANY', '/secretsRoutes/', 403);
-I.mockRequest('POST', '/secrets', { secrets: 'fakeSecrets' });
-I.mockRequest('GET', '/api/users/1', 404, 'User not found');
-```
-
-Multiple requests
-
-```js
-I.mockRequest('GET', ['/secrets', '/v2/secrets'], 403);
-```
-
-#### Parameters
-
--   `method` [string][2] request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`.
--   `oneOrMoreUrls` ([string][2] \| [array][3]) url(s) to mock. Can be exact URL, a pattern, or an array of URLs.
--   `dataOrStatusCode` ([number][4] \| [string][2] \| [object][5]) status code when number provided. A response body otherwise
--   `additionalData` ([string][2] \| [object][5]) response body when a status code is set by previous parameter. 
-
-### stopMocking
-
-Stops mocking requests.
-
 [1]: https://netflix.github.io/pollyjs/#/
-
-[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
-
-[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
-
-[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
-
-[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
diff --git a/docs/puppeteer.md b/docs/puppeteer.md
index 5548d1ea..a6002a1c 100644
--- a/docs/puppeteer.md
+++ b/docs/puppeteer.md
@@ -181,14 +181,14 @@ When you run tests with Puppeteer you can control those requests by mocking them
 
 Also you can replace real request with a one explicitly defined. This is useful when you want to isolate application testing from a backend. For instance, if you don't want to save data to database, and you know the request which performs save, you can mock the request, so application will treat this as valid response, but no data will be actually saved.
 
-To mock requests enable additional helper [Polly](https://codecept.io/helpers/Polly) (which is based on Polly.js).
+To mock requests enable additional helper [MockRequest](https://codecept.io/helpers/MockRequest) (which is based on Polly.js).
 
 ```js
 helpers: {
    Puppeteer: {
      // regular Puppeteer config here
    },
-   Polly: {}
+   MockRequest: {}
 }
 ```
 
@@ -211,7 +211,7 @@ I.mockRequest('POST', '/api/users', { user: 'davert' });
 I.mockRequest('GET', '/api/users/1', 404, { error: 'User not found' });
 ```
 
-> See [`mockRequest` API](https://codecept.io/helpers/Polly#mockrequest)
+> See [`mockRequest` API](https://codecept.io/helpers/MockRequest#mockrequest)
 
 To see `mockRequest` method in intellisense auto completion don't forget to run `codeceptjs def` command:
 
diff --git a/docs/webdriver.md b/docs/webdriver.md
index dbed2c64..513c641e 100644
--- a/docs/webdriver.md
+++ b/docs/webdriver.md
@@ -378,22 +378,23 @@ Scenario('should open main page of configured site, open a popup, switch to main
 
 ## Mocking Requests
 
-Web application sends various requests to local services (Rest API, GraphQL) or to 3rd party services (CDNS, Google Analytics, etc).
-When you run tests with WebDriver you can control those requests by mocking them. For instance, you can speed up your tests by blocking trackers, Google Analytics, and other services you don't control.
+When testing web application you can disable some of external requests calls by enabling HTTP mocking.
+This is useful when you want to isolate application testing from a backend. For instance, if you don't want to save data to database, and you know the request which performs save, you can mock the request, so application will treat this as valid response, but no data will be actually saved.
 
-Also you can replace real request with a one explicitly defined. This is useful when you want to isolate application testing from a backend. For instance, if you don't want to save data to database, and you know the request which performs save, you can mock the request, so application will treat this as valid response, but no data will be actually saved.
+  > **WebDriver has limited ability to mock requests**, so you can only mock only requests performed after page is loaded. This means that you can't block Google Analytics, or CDN calls, but you can mock API requests performed on user action.
 
-To mock requests enable additional helper [Polly](https://codecept.io/helpers/Polly) (which is based on Polly.js).
+To mock requests enable additional helper [MockRequest](https://codecept.io/helpers/MockRequest) (which is based on Polly.js).
 
 ```js
 helpers: {
    WebDriver: {
      // regular WebDriver config here
    },
-   Polly: {}
+   MockRequest: {}
 }
 ```
 
+
 The function `mockRequest` will be added to `I` object. You can use it to explicitly define which requests to block and which response they should return instead:
 
 ```js
@@ -407,7 +408,7 @@ I.mockRequest('POST', '/api/users', { user: 'davert' });
 I.mockRequest('GET', '/api/users/1', 404, { error: 'User not found' });
 ```
 
-**Note: In WebDriver mocking is disabled every time a new page is loaded. Hence, `_startMocking` method should be called and the mocks should be updated, after navigating to a new page. This is a limitation of WebDriver.**
+> In WebDriver mocking is disabled every time a new page is loaded. Hence, `startMocking` method should be called and the mocks should be updated, after navigating to a new page. This is a limitation of WebDriver. Consider using Puppeteer with MockRequest instead.
 
 ```js
 I.amOnPage('/xyz');
@@ -417,7 +418,7 @@ I.click('Go to Next Page');
 // in WebDriver as we can't detect that the page was reloaded, so no mocking :(
 ```
 
-> See [`mockRequest` API](https://codecept.io/helpers/Polly#mockrequest)
+> See [`mockRequest` API](https://codecept.io/helpers/MockRequest#mockrequest)
 
 To see `mockRequest` method in intellisense auto completion don't forget to run `codeceptjs def` command:
 
diff --git a/lib/helper/MockRequest.js b/lib/helper/MockRequest.js
index 16a647fb..5cfb3c2e 100644
--- a/lib/helper/MockRequest.js
+++ b/lib/helper/MockRequest.js
@@ -1,7 +1,7 @@
 const Helper = require('../helper');
 const requireg = require('requireg');
 const { appendBaseUrl } = require('../utils');
-const pollyWebDriver = require('./PollyWebDriverExt');
+const pollyWebDriver = require('./clientscripts/PollyWebDriverExt');
 
 let PollyJS;
 
diff --git a/lib/helper/Polly.js b/lib/helper/Polly.js
index 3c77d68e..f92ea30b 100644
--- a/lib/helper/Polly.js
+++ b/lib/helper/Polly.js
@@ -37,4 +37,6 @@ const MockRequest = require('./MockRequest');
  */
 class Polly extends MockRequest {}
 
+console.log('Deprecation: Polly helper was renamed to MockRequest, please update your config!');
+
 module.exports = Polly;
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index 1b1ecd09..a54280ce 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -439,16 +439,7 @@ class Puppeteer extends Helper {
     await this._setPage(mainPage);
     await this.closeOtherTabs();
 
-    if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
-      const dimensions = this.options.windowSize.split('x');
-      await this.resizeWindow(parseInt(dimensions[0], 10), parseInt(dimensions[1], 10));
-    }
-
     this.isRunning = true;
-
-    if (this.options.userAgent) {
-      await this.page.setUserAgent(this.options.userAgent);
-    }
   }
 
   async _stopBrowser() {
@@ -2299,7 +2290,7 @@ function $XPath(element, selector) {
   return res;
 }
 
-function targetCreatedHandler(page) {
+async function targetCreatedHandler(page) {
   if (!page) return;
   this.withinLocator = null;
   page.on('load', (frame) => {
@@ -2311,4 +2302,14 @@ function targetCreatedHandler(page) {
     this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '));
     consoleLogStore.add(msg);
   });
+
+  if (this.options.userAgent) {
+    await page.setUserAgent(this.options.userAgent);
+  }
+  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
+    const dimensions = this.options.windowSize.split('x');
+    const width = parseInt(dimensions[0], 10);
+    const height = parseInt(dimensions[1], 10);
+    await page.setViewport({ width, height });
+  }
 }
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index 26787551..d2b57c64 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -1966,9 +1966,13 @@ class WebDriver extends Helper {
    * I.openNewTab();
    * ```
    */
-  async openNewTab() {
+  async openNewTab(url = 'about:blank', windowName = null) {
     const client = this.browser;
-    return client.newWindow('about:blank');
+    const crypto = require('crypto');
+    if (windowName == null) {
+      windowName = crypto.randomBytes(32).toString('hex');
+    }
+    return client.newWindow(url, windowName);
   }
 
   /**
diff --git a/lib/helper/PollyWebDriverExt.js b/lib/helper/clientscripts/PollyWebDriverExt.js
similarity index 100%
rename from lib/helper/PollyWebDriverExt.js
rename to lib/helper/clientscripts/PollyWebDriverExt.js
diff --git a/lib/locator.js b/lib/locator.js
index 9c427f20..c2cbc64b 100644
--- a/lib/locator.js
+++ b/lib/locator.js
@@ -111,17 +111,17 @@ class Locator {
   }
 
   find(locator) {
-    const xpath = sprintf('%s//%s', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s//%s', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 
   withChild(locator) {
-    const xpath = sprintf('%s[./child::%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s[./child::%s]', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 
   withDescendant(locator) {
-    const xpath = sprintf('%s[./descendant::%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s[./descendant::%s]', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 
@@ -166,17 +166,17 @@ class Locator {
   }
 
   inside(locator) {
-    const xpath = sprintf('%s[ancestor::%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s[ancestor::%s]', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 
   after(locator) {
-    const xpath = sprintf('%s[preceding-sibling::%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s[preceding-sibling::%s]', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 
   before(locator) {
-    const xpath = sprintf('%s[following-sibling::%s]', this.toXPath(), removePrefix((new Locator(locator, 'css')).toXPath()));
+    const xpath = sprintf('%s[following-sibling::%s]', this.toXPath(), convertToSubSelector(locator));
     return new Locator({ xpath });
   }
 }
@@ -269,10 +269,24 @@ function isAccessibility(locator) {
 }
 
 function isXPath(locator) {
-  return locator.substr(0, 2) === '//' || locator.substr(0, 2) === './';
+  const trimmed = locator.replace(/^\(+/, '').substr(0, 2);
+  return trimmed === '//' || trimmed === './';
+}
+
+function isXPathStartingWithRoundBrackets(locator) {
+  return isXPath(locator) && locator[0] === '(';
 }
 
 function removePrefix(xpath) {
   return xpath
     .replace(/^(\.|\/)+/, '');
 }
+
+function convertToSubSelector(locator) {
+  const xpath = (new Locator(locator, 'css')).toXPath();
+  if (isXPathStartingWithRoundBrackets(xpath)) {
+    throw new Error('XPath with round brackets is not possible here! '
+      + 'May be a nested locator with at() last() or first() causes this error.');
+  }
+  return removePrefix(xpath);
+}
diff --git a/lib/output.js b/lib/output.js
index 905452bb..4ea678fa 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -87,7 +87,7 @@ module.exports = {
     }
 
     let stepLine = step.toString();
-    if (step.metaStep && outputLevel >= 2) {
+    if (step.metaStep && outputLevel >= 1) {
       this.stepShift += 2;
       stepLine = colors.green(truncate(stepLine, this.spaceShift));
     }
diff --git a/lib/plugin/wdio.js b/lib/plugin/wdio.js
index 8a8083ca..53ed789b 100644
--- a/lib/plugin/wdio.js
+++ b/lib/plugin/wdio.js
@@ -203,7 +203,7 @@ module.exports = (config) => {
     if (launcher.onPrepare) {
       event.dispatcher.on(event.all.before, () => {
         recorder.add(`launcher ${name} start`, async () => {
-          await launcher.onPrepare(config);
+          await launcher.onPrepare(config, config.capabilities);
           output.debug(`Started ${name}`);
         });
       });
@@ -212,7 +212,7 @@ module.exports = (config) => {
     if (launcher.onComplete) {
       event.dispatcher.on(event.all.after, () => {
         recorder.add(`launcher ${name} start`, async () => {
-          await launcher.onComplete(process.exitCode, config);
+          await launcher.onComplete(process.exitCode, config, config.capabilities);
           output.debug(`Stopped ${name}`);
         });
       });
diff --git a/package.json b/package.json
index 1e44e7ef..9dd3dd9b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "codeceptjs",
-  "version": "2.3.0",
+  "version": "2.3.1",
   "description": "Modern Era Acceptance Testing Framework for NodeJS",
   "keywords": [
     "acceptance",
diff --git a/website/i18n/en.json b/website/i18n/en.json
index 85fa130d..3bf00909 100644
--- a/website/i18n/en.json
+++ b/website/i18n/en.json
@@ -83,6 +83,12 @@
       "helpers/FileSystem": {
         "title": "FileSystem"
       },
+      "helpers/GraphQL": {
+        "title": "GraphQL"
+      },
+      "helpers/GraphQLDataFactory": {
+        "title": "GraphQLDataFactory"
+      },
       "helpers/Mochawesome": {
         "title": "Mochawesome"
       },
diff --git a/website/pages/en/index.js b/website/pages/en/index.js
index 56b129c3..89860283 100644
--- a/website/pages/en/index.js
+++ b/website/pages/en/index.js
@@ -39,6 +39,9 @@ const quickstart = `
 class Index extends React.Component {
   render() {
     return (<div>
+      <div style={{backgroundColor: '#fff573', textAlign: 'center', padding: '10px'}}>
+      Training: Web test automation with CodeceptJS - 11.09.2019-12.09.2019 <a href="https://sdclabs.com/trainings/schedule">Buy now!</a>
+      </div>
     <div className="jumbotron">
   <div className="container">
     <h1 className="motto">Effective <span className="name">End 2 End</span> Testing <div className="small">for NodeJS</div></h1>
diff --git a/website/sidebars.json b/website/sidebars.json
index 919f6169..4088dc33 100755
--- a/website/sidebars.json
+++ b/website/sidebars.json
@@ -58,7 +58,7 @@
       "helpers/ApiDataFactory",
       "helpers/GraphQL",
       "helpers/GraphQLDataFactory",
-      "helpers/Polly",
+      "helpers/MockRequest",
       "helpers/FileSystem",
       "community-helpers"
     ]
diff --git a/website/siteConfig.js b/website/siteConfig.js
index f56b9c74..f968a9bf 100755
--- a/website/siteConfig.js
+++ b/website/siteConfig.js
@@ -135,6 +135,9 @@ const siteConfig = {
     algoliaOptions: {} // Optional, if provided by Algolia
   },
 
+  gaTrackingId: 'UA-30075781-3',
+  gaGtag: true,
+
   // Show documentation's last contributor's name.
   // enableUpdateBy: true,
 


Test patch:
diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js
index f068f5e2..1a095db6 100644
--- a/test/unit/locator_test.js
+++ b/test/unit/locator_test.js
@@ -156,7 +156,7 @@ describe('Locator', () => {
       .find('td')
       .first();
     const nodes = xpath.select(l.toXPath(), doc);
-    expect(nodes).to.have.length(1);
+    expect(nodes).to.have.length(1, l.toXPath());
     expect(nodes[0].firstChild.data).to.eql('Show');
   });
 
@@ -188,6 +188,18 @@ describe('Locator', () => {
     expect(nodes).to.have.length(1, l.toXPath());
   });
 
+  it('should throw an error when xpath with round brackets is nested', () => {
+    assert.throws(() => {
+      Locator.build('tr').find('(./td)[@id="id"]');
+    }, /round brackets/);
+  });
+
+  it('should throw an error when locator with specific position is nested', () => {
+    assert.throws(() => {
+      Locator.build('tr').withChild(Locator.build('td').first());
+    }, /round brackets/);
+  });
+
   it('should not select element by deep nested siblings', () => {
     const l = Locator.build('//table')
       .withChild('td');