Repository: nightwatchjs/nightwatch
Pull request #3341 (2022-08-17 07:57:47)

Fix cli failure on Windows due to pause() tests.
~Increased the time in assertions to give a good margin to the `pause()` command to complete.~
Removed the upper limit of time for pause command.

Tested on Windows machine by running the tests multiple times.


Patch:
diff --git a/lib/api/expect/elements.js b/lib/api/expect/elements.js
index c2e69d4c..e92b6d8a 100644
--- a/lib/api/expect/elements.js
+++ b/lib/api/expect/elements.js
@@ -56,16 +56,20 @@ class ExpectElements extends BaseExpect {
     return this.elementLocator
       .findElement({element, returnSingleElement: false, cacheElementId: false})
       .then(result => {
-        const {value} = result;
+        const {value, error} = result;
 
+        let elements;
         if (value) {
-          const elements = this.transport.mapWebElementIds(value);
-          this.resultValue = elements;
-
-          return this.resolve(elements);
+          elements = this.transport.mapWebElementIds(value);
+        } else if (error instanceof Error && error.name === 'NoSuchElementError') {
+          elements = this.transport.mapWebElementIds([]);
+        } else {
+          throw result;
         }
 
-        throw result;
+        this.resultValue = elements;
+
+        return this.resolve(elements);
       });
   }
 
diff --git a/lib/transport/factory.js b/lib/transport/factory.js
index 08992bfd..6bd0164a 100644
--- a/lib/transport/factory.js
+++ b/lib/transport/factory.js
@@ -94,6 +94,13 @@ module.exports = class TransportFactory {
     if (usingSeleniumServer) {
       const Selenium = require('./selenium-webdriver/selenium.js');
 
+      // support for mobile native apps
+      if (browserName === null) {
+        const Mobile = require('./selenium-webdriver/mobile-webdriver.js');
+
+        return new Mobile(nightwatchInstance);
+      }
+
       return new Selenium(nightwatchInstance, browserName);
     }
 
diff --git a/lib/transport/selenium-webdriver/browserstack.js b/lib/transport/selenium-webdriver/browserstack.js
index fa8289de..56e87ea1 100644
--- a/lib/transport/selenium-webdriver/browserstack.js
+++ b/lib/transport/selenium-webdriver/browserstack.js
@@ -1,12 +1,13 @@
 const stripAnsi = require('strip-ansi');
-const Selenium = require('./selenium.js');
 const {Logger} = require('../../utils');
+const BaseDriver = require('./mobile-webdriver.js');
 
-class Browserstack extends Selenium {
-  static get ApiUrl() {
-    return 'https://api.browserstack.com/automate';
+class Browserstack extends BaseDriver {
+  get ApiUrl() {
+    return `https://api.browserstack.com/${this.isMobile ? 'app-automate' : 'automate'}`;
   }
 
+
   get accessKey() {
     return this.bstackOptions.accessKey;
   }
@@ -28,6 +29,7 @@ class Browserstack extends Selenium {
 
     this.adaptSettings();
     this.verifySettings();
+    this.isMobile = (typeof nightwatchInstance.client.api.isMobile === 'function' &&  nightwatchInstance.client.api.isMobile()) ? true : false;
     this.useLocal = false;
     this.nightwatchInstance.on('nightwatch:session.create', (data) => {
       this.sessionId = data.sessionId;
@@ -94,7 +96,7 @@ class Browserstack extends Selenium {
   async getBuildId() {
     try {
       const builds = await this.sendHttpRequest({
-        url: `${Browserstack.ApiUrl}/builds.json`,
+        url: `${this.ApiUrl}/builds.json`,
         method: 'GET',
         use_ssl: true,
         auth: {
@@ -116,7 +118,7 @@ class Browserstack extends Selenium {
   async sendReasonToBrowserstack(isFailure = false, reason = '') {
     reason = stripAnsi(reason);
     await this.sendHttpRequest({
-      url: `${Browserstack.ApiUrl}/sessions/${this.sessionId}.json`,
+      url: `${this.ApiUrl}/sessions/${this.sessionId}.json`,
       method: 'PUT',
       use_ssl: true,
       data: {
@@ -138,7 +140,7 @@ class Browserstack extends Selenium {
       await this.sendReasonToBrowserstack(!!failures, reason);
       // eslint-disable-next-line no-console
       console.log('\n  ' + 'See more info, video, & screenshots on Browserstack:\n' +
-        '  ' + Logger.colors.light_cyan(`https://automate.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`));
+        '  ' + Logger.colors.light_cyan(`https://${this.isMobile ? 'app-automate' : 'automate'}.browserstack.com/builds/${this.buildId}/sessions/${this.sessionId}`));
 
       this.sessionId = null;
 
diff --git a/lib/transport/selenium-webdriver/mobile-webdriver.js b/lib/transport/selenium-webdriver/mobile-webdriver.js
new file mode 100644
index 00000000..54408c7f
--- /dev/null
+++ b/lib/transport/selenium-webdriver/mobile-webdriver.js
@@ -0,0 +1,25 @@
+const {WebDriver} = require('selenium-webdriver');
+const {Executor} = require('selenium-webdriver/http');
+const Selenium = require('./selenium');
+const http = require('selenium-webdriver/http');
+
+
+class Mobile extends Selenium {
+
+  get defaultServerUrl() {
+    return 'http://127.0.0.1:4723';
+  }
+
+  get defaultPathPrefix() {
+    return '/wd/hub';
+  }
+
+  createDriver() {
+    const httpClient = new http.HttpClient(this.getServerUrl());
+
+    return WebDriver.createSession(new Executor(httpClient), this.initialCapabilities);
+  }
+
+}
+
+module.exports = Mobile;


Test patch:
diff --git a/test/sampletests/withuncaughterrors/demoTestWithError.js b/test/sampletests/withuncaughterrors/demoTestWithError.js
index fc5c1202..94f409d4 100644
--- a/test/sampletests/withuncaughterrors/demoTestWithError.js
+++ b/test/sampletests/withuncaughterrors/demoTestWithError.js
@@ -13,7 +13,7 @@ describe('test with uncaught error', function () {
 
     setTimeout(function () {
       throw new Error('Test Error Uncaught');
-    }, 25);
+    }, 100);
   });
 
 });
diff --git a/test/src/api/commands/client/testPause.js b/test/src/api/commands/client/testPause.js
index 1cd2e569..f9620a28 100644
--- a/test/src/api/commands/client/testPause.js
+++ b/test/src/api/commands/client/testPause.js
@@ -10,23 +10,21 @@ describe('.pause()', function() {
     CommandGlobals.afterEach.call(this, done);
   });
 
-  it('browser.pause(10)', function(done) {
+  it('browser.pause(10) pauses atleast 10ms and not more than 2000ms', function(done) {
     const startTime = new Date();
     this.client.api.pause(10, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 10);
-      assert.ok(timeElapsed <= 20);
     });
 
     this.client.start(done);
   });
 
-  it('browser.pause(200)', function(done) {
+  it('browser.pause(200) pauses for atleast 200ms and not more than 2000ms', function(done) {
     const startTime = new Date();
     this.client.api.pause(200, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 210);
     });
 
     this.client.start(done);
@@ -37,7 +35,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
     });
 
     setTimeout(() => {
@@ -52,7 +49,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 1000);
-      assert.ok(timeElapsed <= 1020);
     });
 
     setTimeout(() => {
@@ -71,7 +67,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(Debuggability.stepOverAndPause, true);
     });
 
@@ -91,7 +86,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(Debuggability.stepOverAndPause, true);
     });
 
@@ -120,7 +114,6 @@ describe('.pause()', function() {
     this.client.api.pause(undefined, function() {
       const timeElapsed = new Date() - startTime;
       assert.ok(timeElapsed >= 200);
-      assert.ok(timeElapsed <= 220);
       assert.strictEqual(apiEndCalledWithCallback, true);
       assert.strictEqual(callbackCalled.toString().includes('process.exit(0)'), true);
 
diff --git a/test/src/api/expect/testExpectCount.js b/test/src/api/expect/testExpectCount.js
index 8c961ff9..1d6494af 100644
--- a/test/src/api/expect/testExpectCount.js
+++ b/test/src/api/expect/testExpectCount.js
@@ -181,7 +181,7 @@ describe('expect.elements count', function() {
         assert.strictEqual(expect.assertion.passed, false);
         assert.ok(expect.assertion.messageParts.includes(' equal: "4"'));
         assert.ok(expect.assertion.message.startsWith('Expected elements <.classname> count to equal: "4"'));
-        assert.strictEqual(expect.assertion.resultValue, undefined);
+        assert.strictEqual(expect.assertion.resultValue, 0);
       });
     });
   });
diff --git a/test/src/core/testCreateSession.js b/test/src/core/testCreateSession.js
index a6ac54f3..2b871303 100644
--- a/test/src/core/testCreateSession.js
+++ b/test/src/core/testCreateSession.js
@@ -387,4 +387,49 @@ describe('test Request With Credentials', function () {
       });
     }, /Error: Unknown browser:/);
   });
+
+  it('Test create session with browsername null - mobile support', async function () {
+
+    nock('http://localhost:4723')
+      .post('/wd/hub/session')
+      .reply(201, {
+        value: {
+          capabilities: {
+            platformName: 'iOS',
+            platformVersion: '15.5',
+            deviceName: 'iPhone 13',
+            name: 'sample test goes here'
+          },
+          sessionId: '1352110219202'
+        }
+      });
+
+    const client = Nightwatch.createClient({
+      webdriver: {
+        start_process: false
+      },
+      selenium: {
+        start_process: false,
+        host: 'localhost',
+        port: '4723'
+      },
+      desiredCapabilities: {
+        browserName: null,
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13'
+      }
+    });
+
+    const result = await client.createSession();
+    assert.deepStrictEqual(result, {
+      capabilities: {
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13',
+        name: 'sample test goes here'
+      },
+      sessionId: '1352110219202'
+    });
+  });
 });
diff --git a/test/src/index/transport/testBrowserstackTransport.js b/test/src/index/transport/testBrowserstackTransport.js
index 3837dfe9..4df9aa4d 100644
--- a/test/src/index/transport/testBrowserstackTransport.js
+++ b/test/src/index/transport/testBrowserstackTransport.js
@@ -194,4 +194,68 @@ describe('BrowserstackTransport', function () {
     }, 100);
 
   });
+
+  it('test create Transport for Browserstack - App automate', function(done) {
+    const client = NightwatchClient.client({
+      webdriver: {
+        host: 'hub-cloud.browserstack.com',
+        port: 443,
+        start_process: false
+      },
+      desiredCapabilities: {
+        'browserstack.user': 'test-acess-user',
+        'browserstack.key': 'test-acess-key',
+        platformName: 'ios',
+        platfromVersion: '12',
+        deviceName: 'iPhone 12'
+      }
+    });
+    nock('https://api.browserstack.com')
+      .get('/app-automate/builds.json')
+      .reply(200, [{
+        automation_build: {
+          name: 'nightwatch-test-build',
+          hashed_id: '123-567-89'
+        } 
+      },
+      {
+        automation_build: {
+          name: 'test-build'
+        }
+      }
+      ]);
+
+    assert.ok(client.transport instanceof Browserstack);
+    assert.strictEqual(client.settings.webdriver.host, 'hub-cloud.browserstack.com');
+    assert.strictEqual(client.settings.webdriver.default_path_prefix, '/wd/hub');
+    assert.strictEqual(client.settings.webdriver.start_process, false);
+    assert.strictEqual(client.settings.webdriver.ssl, true);
+
+    const {transport} = client;
+
+    client.emit('nightwatch:session.create', {
+      sessionId: '1234567'
+    });
+    setTimeout(async function() {
+      assert.strictEqual(transport.buildId, '123-567-89');
+      
+      try {
+        let result;
+        nock('https://api.browserstack.com')
+          .put('/app-automate/sessions/1234567.json', {
+            status: 'passed',
+            reason: ''
+          })
+          .reply(200, {});
+
+        result = await transport.testSuiteFinished(false);
+        assert.strictEqual(result, true);
+        assert.strictEqual(transport.sessionId, null);
+
+        done();
+      } catch (e) {
+        done(e);
+      }
+    }, 100);
+  });
 });
diff --git a/test/src/index/transport/testMobileSupport.js b/test/src/index/transport/testMobileSupport.js
new file mode 100644
index 00000000..30581689
--- /dev/null
+++ b/test/src/index/transport/testMobileSupport.js
@@ -0,0 +1,35 @@
+const nock = require('nock');
+const assert = require('assert');
+const common = require('../../../common.js');
+const NightwatchClient = common.require('index.js');
+const Mobile = common.require('transport/selenium-webdriver/mobile-webdriver.js');
+
+
+describe('MobileTransport', function () {
+  it('test create Transport for Mobile',  function(){
+    const client = NightwatchClient.client({
+      webdriver: {
+        start_process: false
+      },
+      selenium: {
+        start_process: false,
+        host: 'localhost',
+        port: '4723'
+      },
+      output: true,
+      silent: false,
+      desiredCapabilities: {
+        browserName: null,
+        platformName: 'iOS',
+        platformVersion: '15.5',
+        deviceName: 'iPhone 13'
+      }
+    });
+
+    const {transport} = client;
+    assert.ok(transport instanceof Mobile);
+    assert.ok(client.settings.webdriver.host, 'localhost');
+    assert.ok(client.settings.webdriver.default_path_prefix, '/wd/hub');
+    assert.strictEqual(client.settings.webdriver.start_process, false);
+  });
+});
\ No newline at end of file
diff --git a/test/src/runner/testRunnerUncaughtErrors.js b/test/src/runner/testRunnerUncaughtErrors.js
index d0c15092..ccf38137 100644
--- a/test/src/runner/testRunnerUncaughtErrors.js
+++ b/test/src/runner/testRunnerUncaughtErrors.js
@@ -75,6 +75,6 @@ describe('testRunWithUncaughtErrors', function() {
       } catch (e) {
         done(e);
       }
-    }, 500);
+    }, 1500);
   });
 });