Repository: nightwatchjs/nightwatch
Pull request #3769 (2023-06-13 12:20:58)

Fix takeScreenshot command not returning anything.
Also, `takeScreenshot` command does not take any argument: https://github.com/SeleniumHQ/selenium/blob/4d70bd80f40592c12170ca8382d54064278add6f/javascript/node/selenium-webdriver/lib/webdriver.js#L3048-L3052


Patch:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 00000000..f75928af
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,42 @@
+name: Nightly
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: Nightly version
+        required: false
+        type: string
+        default: ''
+      ref:
+        description: Branch, tag, or SHA to release
+        required: false
+        type: string
+        default: ''
+
+jobs:
+  nightly:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          ref: ${{ inputs.ref }}
+      - name: Setup Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: '18.x'
+      - name: Install dependencies
+        run: npm install
+      - name: Build Nightwatch
+        run: npm run build
+      - name: Create publishable package 📦
+        run: npm pack
+      - name: Release Nightly
+        uses: marvinpinto/action-automatic-releases@latest
+        with:
+          repo_token: "${{ secrets.GITHUB_TOKEN }}"
+          automatic_release_tag: "nightly-${{ inputs.version }}"
+          title: "Nightly ${{ inputs.version }}"
+          prerelease: true
+          files: '*.tgz'
diff --git a/README.md b/README.md
index d406f308..a29ef713 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![Node.js CI](https://github.com/nightwatchjs/nightwatch/actions/workflows/build-node.yaml/badge.svg?branch=main)](https://github.com/nightwatchjs/nightwatch/actions/workflows/build-node.yaml)
 [![codecov](https://codecov.io/gh/nightwatchjs/nightwatch/branch/main/graph/badge.svg?token=MSObyfECEh)](https://codecov.io/gh/nightwatchjs/nightwatch)
 [![Discord][discord-badge]][discord]
-[![Node Support](https://img.shields.io/badge/node-%3E12.x-brightgreen.svg)](https://github.com/nightwatchjs/nightwatch/blob/27a855a2ec0c2008073708d5a2286c2819584fdc/.github/workflows/build-node.yaml#L19)
+
 
 
 <p align="center">
@@ -15,15 +15,26 @@
 
 Nightwatch is an integrated testing framework powered by Node.js and using the [W3C Webdriver API](https://www.w3.org/TR/webdriver/). It is a complete testing solution developed at [BrowserStack](https://www.browserstack.com/) and which can be used for: 
 
-☑️ end-to-end testing of web applications and websites<br>
-☑️ component testing in isolation (React / Vue)<br>
-☑️ Node.js unit and API testing
+☑️ end-to-end testing of web applications and websites
+
+☑️ component testing in isolation (React / Vue / Storybook / Angular)
+
+☑️ Node.js unit, visual regression testing, accessibility testing & API testing
+
+☑️ Native mobile app testing on Android & iOS
+
+## 🚀 Nightwatch v3
+[What's New](https://nightwatchjs.org/guide/overview/whats-new-in-v3.html) | [Release Notes](https://github.com/nightwatchjs/nightwatch/releases/tag/v3.0.1) | [Discussions](https://github.com/nightwatchjs/nightwatch/discussions)
+
+Nightwatch v3 is an all new generation that has been built around these three pillars:
+
+* **Developer Experience** : The entire experience from getting started, to writing and debugging tests, has been redesigned for speed, stability, and consistent non-flaky results.
 
-## 🚀 Nightwatch v2
+* **Mobile first**: Test your web or native, iOS and Android, mobile applications on simulators, real mobile devices or a cloud grid like BrowserStack.
 
-#### [What's New](https://nightwatchjs.org/guide/getting-started/whats-new-v2.html) | [Release Notes](https://github.com/nightwatchjs/nightwatch/releases/tag/v2.0.0) | [Discussions](https://github.com/nightwatchjs/nightwatch/discussions)
+* **One test automation framework**: Run all types of tests from unit, component, and E2E to API, visual, and accessibility with a single framework.
 
-[Nightwatch v2](https://github.com/nightwatchjs/nightwatch/releases/tag/v2.0.0) is now available in the public NPM channel. Read the [what's new](https://nightwatchjs.org/guide/getting-started/whats-new-v2.html) docs page for an overview of the new features, improvements, and important changes.
+The [Nightwatch v3](https://github.com/nightwatchjs/nightwatch/releases/tag/v3.0.1) is not just a new version, it’s the result of months of engineering effort to reimagine test automation for the future. Try it out in 60 seconds and see it in action.  
 
 ## ⚙️ Get started in 60 seconds
 
@@ -65,29 +76,67 @@ You can follow the instructions given at the end of the setup to run your first
 
 ---
 
-## Nightwatch unit tests
-The tests for Nightwatch are written using [Mocha](http://mochajs.org/).
+## Nightwatch mobile app testing
 
-#### 1. Clone the project
-```sh
+Nightwatch enables automation testing of native mobile applications via Appium. It combines the robustness of Appium with the enhanced developer experience provided by Nightwatch. It enables end-to-end functional testing of native mobile apps on Android and iOS devices. Try it now
+
+## Go beyond E2E
+### [Component testing](https://nightwatchjs.org/guide/component-testing/introduction.html)
+
+With Nightwatch you can test components in isolation by mounting them in the browser. Nightwatch 2 added support for component testing for popular web frameworks such as
+
+1. [React](https://nightwatchjs.org/guide/component-testing/testing-react-components.html)
+2. [VueJS](https://nightwatchjs.org/guide/component-testing/vite-plugin.html)
+3. [Angular](https://nightwatchjs.org/guide/component-testing/angular-component-testing.html)
+4. [Storybook](https://nightwatchjs.org/guide/component-testing/storybook-component-testing.html)
+
+### Nightwatch unit tests
+
+The tests for Nightwatch are written using Mocha.
+1. Clone the project
+```bash
 $ git clone https://github.com/nightwatchjs/nightwatch.git
 $ cd nightwatch
 $ npm install
 ```
+2. Run tests
 
-#### 2. Run tests
 To run the complete test suite:
-
-```sh
+```bash
 $ npm test
 ```
 
 To check test coverage, run the command:
-
-```sh
+```bash
 $ npm run mocha-coverage
 ```
-and then open the generated _coverage/index.html_ file in your browser.
+
+and then open the generated coverage/index.html file in your browser.
+
+[Unit testing guide](https://nightwatchjs.org/guide/writing-tests/write-nodejs-unit-integration-tests.html)
+
+### Other types of testing
+#### [Visual Regression Testing](https://nightwatchjs.org/guide/writing-tests/visual-regression-testing.html)
+
+Nightwatch v3 introduces visual regression testing as an in-house plugin. The plugin takes care of
+
+1. Capturing screenshots
+2. Comparison with baseline to highlight visual differences
+3. Report to review the differences
+4. Approve the changes
+
+VRT can be done on real desktop & mobile browsers. Also, VRT can be run on components as part of component testing as well.
+#### [API Testing](https://nightwatchjs.org/guide/writing-tests/api-testing.html)
+
+API testing is now available with Nightwatch v3. The following functionality can be achieved with API testing
+
+1. Request assertions
+2. Response assertions
+3. Viewing API tests in the HTML report
+4. Mock server
+
+#### [Accessibility Testing](https://nightwatchjs.org/guide/using-nightwatch/accessibility-testing.html)
+Nightwatch v3 packages the aXe-core package developed by Deque Systems as a plugin. It enables 90 different types of accessibility tests for WCAG compliance.
 
 ## 🦉 About Nightwatch
 Nightwatch was initially built by [@pineviewlabs](https://github.com/pineviewlabs/) - an independent software consultancy based in Oslo, Norway, with help from [contributors](https://github.com/nightwatchjs/nightwatch/graphs/contributors). In mid 2021, Nightwatch has become a part of the [@BrowserStack](https://github.com/browserstack) family and it is being developed further at the BrowserStack Open-source Program Office. Read more on [our blog](https://nightwatchjs.org/blog/nightwatch-has-joined-the-browserstack-family.html).
diff --git a/lib/api/web-element/commands/takeScreenshot.js b/lib/api/web-element/commands/takeScreenshot.js
index a6d217cb..5512e909 100644
--- a/lib/api/web-element/commands/takeScreenshot.js
+++ b/lib/api/web-element/commands/takeScreenshot.js
@@ -4,13 +4,15 @@
  * @example
  * export default {
  *   demoTest(browser: NightwatchAPI): void {
- *     const data = browser.element('#main').takeScreenshot();
- *     require('fs').writeFile('out.png', data, 'base64');
+ *     const screenshot = browser.element('#main').takeScreenshot();
+ *     screenshot.then((screenshotData) => {
+ *       require('fs').writeFile('out.png', screenshotData, 'base64');
+ *     });
  *   },
  *
  *   async demoTestAsync(browser: NightwatchAPI): Promise<void> {
- *     const data = await browser.element('#main').takeScreenshot();
- *     require('fs').writeFile('out.png', data, 'base64');
+ *     const screenshotData = await browser.element('#main').takeScreenshot();
+ *     require('fs').writeFile('out.png', screenshotData, 'base64');
  *   }
  * }
  *
@@ -23,5 +25,5 @@
  * @returns {ScopedValue<string>}
  */
 module.exports.command = function() {
-  return this.runQueuedCommand('takeElementScreenshot');
+  return this.runQueuedCommandScoped('takeElementScreenshot');
 };
\ No newline at end of file
diff --git a/lib/core/client.js b/lib/core/client.js
index 41ce5ea9..3be3fd12 100644
--- a/lib/core/client.js
+++ b/lib/core/client.js
@@ -620,7 +620,8 @@ class NightwatchClient extends EventEmitter {
       proxy,
       default_path_prefix,
       username,
-      access_key
+      access_key,
+      internal_server_error_retry_interval
     } = this.settings.webdriver;
 
     if (port) {
@@ -646,6 +647,10 @@ class NightwatchClient extends EventEmitter {
       this.httpOpts.setRetryAttempts(timeout_options.retry_attempts);
     }
 
+    if (isDefined(internal_server_error_retry_interval)) {
+      this.httpOpts.setInternalServerRetryIntervel(internal_server_error_retry_interval);
+    }
+
     if (isDefined(default_path_prefix)) {
       this.httpOpts.setDefaultPathPrefix(default_path_prefix);
     }
diff --git a/lib/http/options.js b/lib/http/options.js
index ac4441d4..50d2127f 100644
--- a/lib/http/options.js
+++ b/lib/http/options.js
@@ -7,7 +7,8 @@ const Settings = {
   KEEP_ALIVE: 'keep_alive',
   DEFAULT_PATH: 'default_path',
   TIMEOUT: 'timeout',
-  RETRY_ATTEMPTS: 'retry_attempts'
+  RETRY_ATTEMPTS: 'retry_attempts',
+  INTERNAL_SERVER_ERROR_RETRY_INTERVAL: 'internal_server_error_retry_interval'
 };
 
 class HttpOptions {
@@ -60,6 +61,10 @@ class HttpOptions {
   setRetryAttempts(retryAttempts) {
     this.updateSetting(Settings.RETRY_ATTEMPTS, retryAttempts);
   }
+
+  setInternalServerRetryIntervel(retryInterval) {
+    this.updateSetting(Settings.INTERNAL_SERVER_ERROR_RETRY_INTERVAL, retryInterval);
+  }
 }
 
 module.exports = {
diff --git a/lib/http/request.js b/lib/http/request.js
index 5f7a0a21..1bc00fe8 100644
--- a/lib/http/request.js
+++ b/lib/http/request.js
@@ -22,7 +22,8 @@ const __defaultSettings__ = {
   use_ssl: false,
   proxy: null,
   timeout: 60000,
-  retry_attempts: 0
+  retry_attempts: 0,
+  internal_server_error_retry_interval: 1000
 };
 
 let __globalSettings__ = null;
@@ -295,6 +296,16 @@ class HttpRequest extends EventEmitter {
       return;
     }
 
+    if (this.httpResponse.isInternalServerError && this.retryAttempts > 0) {
+      this.retryCount = this.retryCount + 1;
+      setTimeout(()=> {
+        this.retryAttempts = this.retryAttempts - 1;
+        this.send();
+      }, this.httpOpts.internal_server_error_retry_interval);
+
+      return;
+    }
+
     this.emit('complete', result);
   }
 
diff --git a/lib/http/response.js b/lib/http/response.js
index 2768651c..35c3fac8 100644
--- a/lib/http/response.js
+++ b/lib/http/response.js
@@ -18,6 +18,7 @@ class HttpResponse extends EventEmitter {
     this.request = request;
     this.res.setEncoding('utf8');
     this.isRedirect = HttpUtil.isRedirect(response.statusCode);
+    this.isInternalServerError = response.statusCode >= 500;
     this.handle();
   }
 
@@ -53,6 +54,10 @@ class HttpResponse extends EventEmitter {
         return;
       }
 
+      if (this.isInternalServerError && this.request.retryAttempts > 0) {
+        return;
+      }
+
       this.emit('success', this.responseData, this.res);
     });
   }
diff --git a/lib/runner/folder-walk.js b/lib/runner/folder-walk.js
index c447ff00..aec2f311 100644
--- a/lib/runner/folder-walk.js
+++ b/lib/runner/folder-walk.js
@@ -103,6 +103,10 @@ class Walker {
    * @param {Array} list
    */
   applyFilters(list) {
+    if (this.usingCucumber) {
+      return list;
+    }
+
     return list.sort().filter(filePath => {
       let matched = true;
 
@@ -125,7 +129,7 @@ class Walker {
   }
 
   async applyTagFilter(list) {
-    if (!this.tags.anyTagsDefined()) {
+    if (!this.tags.anyTagsDefined() || this.usingCucumber) {
       return list;
     }
 
@@ -177,11 +181,6 @@ class Walker {
           fullPath = [fullPath];
         }
 
-        // cucumber-runner manages it's own set of filters
-        if (this.usingCucumber) {
-          return resolve(fullPath);
-        }
-
         if (Array.isArray(fullPath)) {
           return this.applyFilters(fullPath);
         }
diff --git a/lib/transport/selenium-webdriver/httpclient.js b/lib/transport/selenium-webdriver/httpclient.js
index 0c5de344..d4084ecd 100644
--- a/lib/transport/selenium-webdriver/httpclient.js
+++ b/lib/transport/selenium-webdriver/httpclient.js
@@ -14,7 +14,7 @@ module.exports = function(settings, HttpResponse) {
       }
       this.proxyOptions_ = opt_proxy ? {} : null;
 
-      const {hostname: host, pathname: path, protocol} = options;           
+      const {hostname: host, pathname: path, protocol} = options;
       const {log_screenshot_data} = settings;
       let {port} = options;
       if (port) {
diff --git a/lib/transport/selenium-webdriver/safari.js b/lib/transport/selenium-webdriver/safari.js
index 8d653dcc..e7ca65aa 100644
--- a/lib/transport/selenium-webdriver/safari.js
+++ b/lib/transport/selenium-webdriver/safari.js
@@ -1,5 +1,5 @@
 const {WebDriver} = require('selenium-webdriver');
-const {Executor, HttpClient} = require('selenium-webdriver/http');
+const http = require('selenium-webdriver/http');
 const SeleniumWebdriver = require('./');
 
 module.exports = class SafariDriverTransport extends SeleniumWebdriver {
@@ -48,12 +48,12 @@ class SafariDriver extends WebDriver {
     let client;
 
     if (service) {
-      client = service.start().then(url => new HttpClient(url));
+      client = service.start().then(url => new http.HttpClient(url));
     } else {
-      client = Promise.resolve(new HttpClient(serverUrl));
+      client = Promise.resolve(new http.HttpClient(serverUrl));
     }
 
-    const executor = new Executor(client);
+    const executor = new http.Executor(client);
 
     return super.createSession(executor, options, () => {
       if (service) {
diff --git a/package-lock.json b/package-lock.json
index 820964ce..29976b66 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "nightwatch",
-  "version": "3.0.0-beta.1",
+  "version": "3.0.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "nightwatch",
-      "version": "3.0.0-beta.1",
+      "version": "3.0.1",
       "license": "MIT",
       "dependencies": {
         "@nightwatch/chai": "5.0.2",
@@ -41,7 +41,7 @@
         "open": "8.4.0",
         "ora": "5.4.1",
         "piscina": "^3.2.0",
-        "selenium-webdriver": "4.9.2",
+        "selenium-webdriver": "~4.10.0",
         "semver": "7.3.5",
         "stacktrace-parser": "0.1.10",
         "strip-ansi": "6.0.1",
@@ -7636,9 +7636,9 @@
       "dev": true
     },
     "node_modules/selenium-webdriver": {
-      "version": "4.9.2",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.9.2.tgz",
-      "integrity": "sha512-0gDswAgVn6qbCYckZetQQvQK9tWW1r7LaumhiqY1/Wl/7JEWG0JANsTbZKnmGc3+cUU76zAi5/p0P8LUweXlig==",
+      "version": "4.10.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz",
+      "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==",
       "dependencies": {
         "jszip": "^3.10.1",
         "tmp": "^0.2.1",
@@ -14977,9 +14977,9 @@
       "dev": true
     },
     "selenium-webdriver": {
-      "version": "4.9.2",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.9.2.tgz",
-      "integrity": "sha512-0gDswAgVn6qbCYckZetQQvQK9tWW1r7LaumhiqY1/Wl/7JEWG0JANsTbZKnmGc3+cUU76zAi5/p0P8LUweXlig==",
+      "version": "4.10.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz",
+      "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==",
       "requires": {
         "jszip": "^3.10.1",
         "tmp": "^0.2.1",
diff --git a/package.json b/package.json
index 32a6bdad..31723e11 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "nightwatch",
   "description": "Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.",
-  "version": "3.0.0-beta.1",
+  "version": "3.0.1",
   "author": "Andrei Rusu",
   "homepage": "https://nightwatchjs.org",
   "main": "./lib/index.js",
@@ -47,7 +47,7 @@
     "open": "8.4.0",
     "ora": "5.4.1",
     "piscina": "^3.2.0",
-    "selenium-webdriver": "4.9.2",
+    "selenium-webdriver": "~4.10.0",
     "semver": "7.3.5",
     "stacktrace-parser": "0.1.10",
     "strip-ansi": "6.0.1",
diff --git a/types/globals.d.ts b/types/globals.d.ts
index 0c183180..02d0aeb6 100644
--- a/types/globals.d.ts
+++ b/types/globals.d.ts
@@ -10,7 +10,7 @@ export interface NightwatchGlobals {
    * @example
    * myGlobalVar: "some value"
    */
-  [key: string]: unknown;
+  [key: string]: any;
 
   /**
    * This controls whether to abort the test execution when an assertion failed and skip the rest
diff --git a/types/index.d.ts b/types/index.d.ts
index 41c27c56..3c8286b9 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -515,6 +515,7 @@ export interface NamespacedApi<ReturnType = unknown> {
   alerts: AlertsNsCommands<ReturnType>;
   document: DocumentNsCommands<ReturnType>;
   window: WindowNsCommands<ReturnType>;
+  firefox: FirefoxNsCommands<ReturnType>;
 
   assert: Assert<ReturnType>;
   verify: Assert<ReturnType>;
@@ -5205,6 +5206,14 @@ export interface CookiesNsCommands<ReturnType = unknown> {
   ): Awaitable<IfUnknown<ReturnType, this>, null>;
 }
 
+type FirefoxContext = 'content' | 'chrome';
+export interface FirefoxNsCommands<ReturnType = unknown> {
+  getContext(): Awaitable<IfUnknown<ReturnType, this>, FirefoxContext>;
+  setContext(ctx: FirefoxContext | PromiseLike<FirefoxContext>): Awaitable<IfUnknown<ReturnType, this>, null>;
+  installAddon(path:string, temporary?: boolean): Awaitable<IfUnknown<ReturnType, this>, string>;
+  uninstallAddon(addonId: string | PromiseLike<string>): Awaitable<IfUnknown<ReturnType, this>, null>;
+} 
+
 export interface AlertsNsCommands<ReturnType = unknown> {
   /**
    * Accepts the currently displayed alert dialog. Usually, this is equivalent to clicking on the 'OK' button in the dialog.
@@ -7559,6 +7568,7 @@ export const cookies: CookiesNsCommands;
 export const alerts: AlertsNsCommands;
 export const document: DocumentNsCommands;
 export const window: WindowNsCommands;
+export const firefox: FirefoxNsCommands;
 
 export const assert: Assert;
 export const verify: Assert;
diff --git a/types/page-object.d.ts b/types/page-object.d.ts
index 8d06cd36..24326914 100644
--- a/types/page-object.d.ts
+++ b/types/page-object.d.ts
@@ -1,9 +1,14 @@
 import {MergeObjectsArray} from './utils';
 import {NightwatchCustomCommands} from './custom-command';
 import {
+  AlertsNsCommands,
+  Assert,
   ChromiumClientCommands,
+  CookiesNsCommands,
+  DocumentNsCommands,
   ElementCommands,
   ElementFunction,
+  Expect,
   LocateStrategy,
   NamespacedApi,
   NightwatchAPI,
@@ -167,8 +172,7 @@ export type EnhancedSectionInstance<
     | 'clickAndHold'
     | 'doubleClick'
     | 'rightClick'
-  > &
-  Pick<NamespacedApi<NightwatchAPI>, 'alerts' | 'cookies' | 'document' | 'assert' | 'verify' | 'expect'>;
+  >;
 
 interface PageObjectSection {
   commands?: Record<string, unknown>[];
@@ -254,6 +258,14 @@ interface EnhancedPageObjectSharedFields<
    * Nightwatch new element API.
    */
   element: ElementFunction;
+
+  // Namespaces directly available on page-objects and sections.
+  alerts: AlertsNsCommands<this>;
+  cookies: CookiesNsCommands<this>;
+  document: DocumentNsCommands<this>;
+  assert: Assert<this>;
+  verify: Assert<this>;
+  expect: Expect;
 }
 
 export interface ElementProperties {
@@ -457,7 +469,6 @@ export type EnhancedPageObject<
   URL = string
 > = SharedCommands &
   NightwatchCustomCommands &
-  Pick<NamespacedApi<NightwatchAPI>, 'alerts' | 'cookies' | 'document' | 'assert' | 'verify' | 'expect'> &
   EnhancedPageObjectSharedFields<
     Required<MergeObjectsArray<Commands>>,
     Required<MergeObjectsArray<Elements>>,
diff --git a/types/web-element.d.ts b/types/web-element.d.ts
index 058dced6..fe1bdb9a 100644
--- a/types/web-element.d.ts
+++ b/types/web-element.d.ts
@@ -159,7 +159,7 @@ export interface ScopedElement extends Element, PromiseLike<WebElement> {
 
   setAttribute(name: string, value: string | null): Promise<WebElement>;
 
-  takeScreenshot(shouldBeInView?: boolean): ElementValue<string>;
+  takeScreenshot(): ElementValue<string>;
 
   dragAndDrop(destination: DragAndDropDestination): Promise<WebElement>;
 


Test patch:
diff --git a/examples/tests/duckDuckGo.js b/examples/tests/duckDuckGo.js
index f148ca3c..24863a71 100644
--- a/examples/tests/duckDuckGo.js
+++ b/examples/tests/duckDuckGo.js
@@ -5,10 +5,11 @@ describe('duckduckgo example', function() {
   it('Search Nightwatch.js and check results', function(browser) {
     browser
       .navigateTo('https://duckduckgo.com')
-      .waitForElementVisible('#search_form_input_homepage')
-      .sendKeys('#search_form_input_homepage', ['Nightwatch.js'])
-      .click('#search_button_homepage')
-      .assert.visible('.results--main')
-      .assert.textContains('.results--main', 'Nightwatch.js');
+      .waitForElementVisible('body')
+      .assert.visible('input[name="q"]')
+      .sendKeys('input[name="q"]', ['Nightwatch.js'])
+      .assert.visible('button[type=submit]')
+      .click('button[type=submit]')
+      .assert.textContains('.react-results--main', 'Nightwatch.js');
   }); 
 });
diff --git a/test/src/api/commands/cookies/testCookiesErrors.js b/test/src/api/commands/cookies/testCookiesErrors.js
index 3acd0ee2..a6ec3c06 100644
--- a/test/src/api/commands/cookies/testCookiesErrors.js
+++ b/test/src/api/commands/cookies/testCookiesErrors.js
@@ -34,7 +34,10 @@ describe('getCookiesErrors', function() {
         report_command_errors: true,
         backwards_compatibility_mode: true,
         webdriver: {
-          start_process: false
+          start_process: false,
+          timeout_options: {
+            retry_attempts: 0
+          }
         },
         desiredCapabilities: {
           name: 'testSuite'
@@ -104,7 +107,10 @@ describe('getCookiesErrors', function() {
       const client = await Nightwatch.initClient({
         report_command_errors: true,
         webdriver: {
-          start_process: false
+          start_process: false,
+          timeout_options: {
+            retry_attempts: 0
+          }
         },
         desiredCapabilities: {
           name: 'testSuite'
diff --git a/test/src/api/commands/web-element/testTakeScreenshot.js b/test/src/api/commands/web-element/testTakeScreenshot.js
index a74da291..3e770f85 100644
--- a/test/src/api/commands/web-element/testTakeScreenshot.js
+++ b/test/src/api/commands/web-element/testTakeScreenshot.js
@@ -24,14 +24,18 @@ describe('element().takeScreenshot() command', function () {
     }, true);
 
     const resultPromise = this.client.api.element('#signupSection').takeScreenshot();
-    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
+
     assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
 
     const result = await resultPromise;
-    assert.strictEqual(result instanceof WebElement, true);
-    assert.strictEqual(await result.getId(), '0');
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
+
+    const resultValue = await resultPromise.value;
+    assert.strictEqual(resultValue, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
   });
 
   it('test .element().find().takeScreenshot()', async function() {
@@ -45,14 +49,18 @@ describe('element().takeScreenshot() command', function () {
     }, true);
 
     const resultPromise = this.client.api.element('#signupSection').find('#helpBtn').takeScreenshot();
-    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
+
     assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
 
     const result = await resultPromise;
-    assert.strictEqual(result instanceof WebElement, true);
-    assert.strictEqual(await result.getId(), '1');
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
+
+    const resultValue = await resultPromise.value;
+    assert.strictEqual(resultValue, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
   });
 
   it('test .element.find().takeScreenshot()', async function() {
@@ -66,13 +74,17 @@ describe('element().takeScreenshot() command', function () {
     }, true);
 
     const resultPromise = this.client.api.element.find('#signupSection').takeScreenshot();
-    // neither an instance of Element or Promise, but an instance of ScopedWebElement.
     assert.strictEqual(resultPromise instanceof Element, false);
     assert.strictEqual(typeof resultPromise.find, 'undefined');
+
     assert.strictEqual(resultPromise instanceof Promise, false);
+    assert.strictEqual(typeof resultPromise.then, 'function');
 
     const result = await resultPromise;
-    assert.strictEqual(result instanceof WebElement, true);
-    assert.strictEqual(await result.getId(), '0');
+    assert.strictEqual(result instanceof WebElement, false);
+    assert.strictEqual(result, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
+
+    const resultValue = await resultPromise.value;
+    assert.strictEqual(resultValue, 'iVBORw0KGgoAAAANSUhEUgAAAbsAAAAiCAYAAADGd3chAAABK');
   });
 });
diff --git a/test/src/api/expect/testExpectVisible.js b/test/src/api/expect/testExpectVisible.js
index 50afb93f..06437c0b 100644
--- a/test/src/api/expect/testExpectVisible.js
+++ b/test/src/api/expect/testExpectVisible.js
@@ -52,7 +52,12 @@ describe('expect.visible', function () {
     beforeEach(function (done) {
       ExpectGlobals.beforeEach.call(this, {
         output: false,
-        silent: false
+        silent: false,
+        webdriver: {
+          timeout_options: {
+            retry_attempts: 0
+          }
+        }
       }, () => {
         this.client.api.globals.abortOnAssertionFailure = false;
         done();
diff --git a/test/src/element/testElementCommands.js b/test/src/element/testElementCommands.js
index d1084c1f..9aa07d53 100644
--- a/test/src/element/testElementCommands.js
+++ b/test/src/element/testElementCommands.js
@@ -50,7 +50,12 @@ describe('element base commands', function() {
     const client = await Nightwatch.initClient({
       backwards_compatibility_mode: true,
       output: false,
-      silent: false
+      silent: false,
+      webdriver: {
+        timeout_options: {
+          retry_attempts: 0
+        }
+      }
     }, reporter);
 
     MockServer.addMock({
@@ -194,8 +199,9 @@ describe('element base commands', function() {
 <title>502 Bad Gateway</title>
 </head>
 <body></body>
-</html>`
-    }, true);
+</html>`,
+      times: 3
+    });
 
     await Nightwatch.initAsync({
       backwards_compatibility_mode: true,
@@ -203,6 +209,9 @@ describe('element base commands', function() {
       silent: false,
       selenium: {
         host: null
+      },
+      webdriver: {
+        internal_server_error_retry_interval: 0
       }
     });
 
diff --git a/test/src/index/testRequestTimeout.js b/test/src/index/testRequestTimeout.js
index f958c3f4..cb0bfa27 100644
--- a/test/src/index/testRequestTimeout.js
+++ b/test/src/index/testRequestTimeout.js
@@ -85,6 +85,7 @@ describe('test HttpRequestTimeout', function() {
     assert.strictEqual(request.retryAttempts, 1);
     assert.deepStrictEqual(request.httpOpts, {
       host: 'localhost',
+      internal_server_error_retry_interval: 1000,
       port: 10195,
       default_path: '/wd/hub',
       credentials: {
diff --git a/test/src/runner/cucumber-integration/testCucumberSampleTests.js b/test/src/runner/cucumber-integration/testCucumberSampleTests.js
index 08574c15..ade6c3db 100644
--- a/test/src/runner/cucumber-integration/testCucumberSampleTests.js
+++ b/test/src/runner/cucumber-integration/testCucumberSampleTests.js
@@ -16,7 +16,7 @@ describe('Cucumber integration', function() {
   });
 
   it('testCucumberSampleTests', function() {
-    const source = [path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/integration/testSample.js')];
+    const source = [path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/parallelWithMultipleDefinition/testExpect.js'), path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/integration/testSample.js')];
 
     return runTests({
       source,
diff --git a/test/src/runner/testRunWithCommandErrors.js b/test/src/runner/testRunWithCommandErrors.js
index 1c217731..43209e63 100644
--- a/test/src/runner/testRunWithCommandErrors.js
+++ b/test/src/runner/testRunWithCommandErrors.js
@@ -119,6 +119,11 @@ describe('testRunWithCommandErrors', function() {
         report_command_errors: true,
         skip_testcases_on_fail: false,
         disable_error_log: 0,
+        webdriver: {
+          timeout_options: {
+            retry_attempts: 0
+          }
+        },
         globals
       }));
     });
diff --git a/test/src/runner/testRunWithServerErrors.js b/test/src/runner/testRunWithServerErrors.js
index 77cb7cc9..0e11fc54 100644
--- a/test/src/runner/testRunWithServerErrors.js
+++ b/test/src/runner/testRunWithServerErrors.js
@@ -41,7 +41,8 @@ describe('testRunWithServerErrors', function() {
       },
       statusCode: 502,
       contentType: 'text/html',
-      response: '<html>\n<head>\n<title>502 Bad Gateway</title>\n</head>\n<body>\n</body></html>'
+      response: '<html>\n<head>\n<title>502 Bad Gateway</title>\n</head>\n<body>\n</body></html>',
+      times: 6
     });
 
     let testsPath = path.join(__dirname, '../../sampletests/withservererrors');
@@ -71,7 +72,10 @@ describe('testRunWithServerErrors', function() {
       skip_testcases_on_fail: false,
       report_command_errors: true,
       disable_error_log: 0,
-      globals
+      globals,
+      webdriver: {
+        internal_server_error_retry_interval: 100
+      }
     }));
   });
 
diff --git a/test/src/runner/testRunnerSessionCreate.js b/test/src/runner/testRunnerSessionCreate.js
index 735874fe..b8a886de 100644
--- a/test/src/runner/testRunnerSessionCreate.js
+++ b/test/src/runner/testRunnerSessionCreate.js
@@ -80,7 +80,10 @@ describe('testRunnerSessionCreate', function () {
       },
       selenium_host: null,
       webdriver: {
-        host: 'localhost'
+        host: 'localhost',
+        timeout_options: {
+          retry_attempts: 0
+        }
       },
       globals,
       output: false,
@@ -140,7 +143,10 @@ describe('testRunnerSessionCreate', function () {
       },
       selenium_host: null,
       webdriver: {
-        host: 'localhost'
+        host: 'localhost',
+        timeout_options: {
+          retry_attempts: 0
+        }
       },
       globals,
       output: false,
@@ -205,7 +211,10 @@ describe('testRunnerSessionCreate', function () {
       },
       selenium_host: null,
       webdriver: {
-        host: 'localhost'
+        host: 'localhost',
+        timeout_options: {
+          retry_attempts: 0
+        }
       },
       globals,
       output: false,
diff --git a/test/src/service-builders/testSafariDriver.js b/test/src/service-builders/testSafariDriver.js
index 2589fab1..e53e224d 100644
--- a/test/src/service-builders/testSafariDriver.js
+++ b/test/src/service-builders/testSafariDriver.js
@@ -4,8 +4,19 @@ const common = require('../../common.js');
 const NightwatchClient = common.require('index.js');
 const Settings = common.require('settings/settings.js');
 const path = require('path');
+const MockServer = require('../../lib/mockserver.js');
+const {settings} = common;
+const {runTests} = common.require('index.js');
 
 describe('SafariDriver Transport Tests', function () {
+  before(function(done) {
+    this.server = MockServer.init();
+
+    this.server.on('listening', () => {
+      done();
+    });
+  });
+
   beforeEach(function() {
     mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false});
   });
@@ -16,6 +27,12 @@ describe('SafariDriver Transport Tests', function () {
     mockery.resetCache();
   });
 
+  after(function(done) {
+    this.server.close(function() {
+      done();
+    });
+  });
+
   const fn = function() {};
   function deleteFromRequireCache(location) {
     const entry = Object.keys(require.cache).filter(item => {
@@ -244,4 +261,120 @@ describe('SafariDriver Transport Tests', function () {
     });
     assert.strictEqual(serverPath, '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver');
   });
+
+  it('session create should throw error after max retryAttempts', function() {
+
+    let testsPath = [
+      path.join(__dirname, '../../sampletests/async')
+    ];
+
+    MockServer.addMock({
+      url: '/session',
+      statusCode: 500,
+      postdata: JSON.stringify({
+        'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}}
+      }),
+      response: JSON.stringify({
+        value: {
+          error: 'session not created',
+          message: 'Could not create a session: Some devices were found, but could not be used:\n'
+        }
+      }),
+      times: 2
+    });
+
+    let globals = {
+      reporter(results) {
+        const sep = path.sep;
+        assert.strictEqual(results.errors, 1);
+        assert.strictEqual(Object.keys(results.modules).length, 1);
+        assert.ok(Object.keys(results.modules).includes(`test${sep}sample`));
+        assert.ok(results.lastError instanceof Error);
+
+        assert.strictEqual(results.lastError.message, 'An error occurred while creating a new SafariDriver session: [SessionNotCreatedError] Could not create a session: Some devices were found, but could not be used:\n');
+      }
+    };
+
+    return runTests({source: testsPath}, settings({
+      selenium_host: null,
+      desiredCapabilities: {
+        browserName: 'safari'
+      },
+      webdriver: {
+        host: 'localhost',
+        timeout_options: {
+          retry_attempts: 1
+        },
+        internal_server_error_retry_interval: 0
+      },
+      globals,
+      output: false,
+      output_folder: false
+    }));
+  });
+
+  it('session create should retry on internal server error (500)', function() {
+    let testsPath = [
+      path.join(__dirname, '../../sampletests/async')
+    ];
+
+    MockServer.addMock({
+      url: '/session',
+      statusCode: 500,
+      postdata: JSON.stringify({
+        'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}}
+      }),
+      response: JSON.stringify({
+        value: {
+          error: 'session not created',
+          message: 'Could not create a session: Some devices were found, but could not be used:\n'
+        }
+      }),
+      times: 3
+    });
+
+    MockServer.addMock({
+      url: '/session',
+      statusCode: 200,
+      postdata: JSON.stringify({
+        'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}}
+      }),
+      response: JSON.stringify({
+        value: {
+          sessionId: 'D5E59FB5-1DBA-4ED8-9402-459D9A4AA0D7',
+          capabilities: {
+            'safari:deviceName': 'Binayak\'s iPhone',
+            'safari:platformVersion': '16.2'
+          }
+        }
+      }),
+      times: 1
+    });
+
+    let globals = {
+      reporter(results) {
+        const sep = path.sep;
+        assert.strictEqual(results.errors, 0);
+        assert.strictEqual(Object.keys(results.modules).length, 1);
+        assert.ok(Object.keys(results.modules).includes(`test${sep}sample`));
+      }
+    };
+
+    return runTests({source: testsPath}, settings({
+      selenium_host: null,
+      desiredCapabilities: {
+        browserName: 'safari'
+      },
+      webdriver: {
+        host: 'localhost',
+        timeout_options: {
+          retry_attempts: 3
+        },
+        internal_server_error_retry_interval: 100
+      },
+      globals,
+      output: false,
+      output_folder: false
+    }));
+  });
 });
diff --git a/types/tests/index.test-d.ts b/types/tests/index.test-d.ts
index 0d319fd8..fbc23afc 100644
--- a/types/tests/index.test-d.ts
+++ b/types/tests/index.test-d.ts
@@ -1,4 +1,4 @@
-import { expectError, expectType } from 'tsd';
+import { expectAssignable, expectError, expectType } from 'tsd';
 import { EventEmitter } from 'events';
 import {
   EnhancedPageObject,
@@ -414,7 +414,7 @@ const menuSection = {
 } satisfies SectionProperties;
 
 const googleCommands = {
-  submit(this: EnhancedPageObject) {
+  submit(this: GooglePage) {
     this.api.pause(1000);
     return this.waitForElementVisible('@submitButton', 1000)
       .click('@submitButton')
@@ -481,11 +481,19 @@ declare module '..' {
 const testPage = {
   'Test commands': () => {
     const google = browser.page.google();
-    google.setValue('@searchBar', 'nightwatch').submit();
+    google.setValue('@searchBar', 'nightwatch').submit().assert.titleContains('nightwatch');
 
     expectType<NightwatchAPI>(google.api);
     expectType<NightwatchClient>(google.client);
 
+    const result = google
+      .setValue('@searchBar', 'nightwatch')
+      .assert.titleContains('Google');
+    
+    expectAssignable<GooglePage>(result);
+    expectAssignable<GooglePage>(result.submit());
+    expectAssignable<GooglePage>(result.cookies.getAll());
+
     // test new element api
     google.element('@searchBar');
     google.element.findAll('@searchBar');
@@ -496,19 +504,21 @@ const testPage = {
   'Test sections': () => {
     const google = browser.page.google();
 
-    expectType<Awaitable<NightwatchAPI, null>>(google.cookies.deleteAll());
+    expectAssignable<GooglePage>(google.cookies.deleteAll());
     expectError(googlePage.window.maximize());
 
     const menuSection = google.section.menu;
 
-    menuSection
+    const result = menuSection
       .assert.visible('@mail')
       .assert.visible('@images');
 
+    expectAssignable<typeof menuSection>(result);
+
     menuSection.expect.element('@mail').to.be.visible;
     menuSection.expect.element('@images').to.be.visible;
 
-    expectType<Awaitable<NightwatchAPI, null>>(menuSection.alerts.accept());
+    expectAssignable<typeof menuSection>(menuSection.alerts.accept());
     expectType<NightwatchAPI>(menuSection.api);
     expectType<NightwatchClient>(menuSection.client);
 
diff --git a/types/tests/webElement.test-d.ts b/types/tests/webElement.test-d.ts
index b0fee9ea..a8562795 100644
--- a/types/tests/webElement.test-d.ts
+++ b/types/tests/webElement.test-d.ts
@@ -147,6 +147,7 @@ describe('new element() api', function () {
     expectType<ElementValue<string>>(elem.getAccessibleName());
     expectType<ElementValue<string>>(elem.getAriaRole());
     expectType<ElementValue<string>>(elem.getCssProperty('height'));
+    expectType<ElementValue<string>>(elem.takeScreenshot());
 
     expectType<Promise<WebElement>>(elem.click());
     expectType<Promise<WebElement>>(elem.clear());