Using Nightwatch to drive two different browsers in the same test

795 views
Skip to first unread message

Scott Moore

unread,
Sep 14, 2018, 1:16:17 PM9/14/18
to NightwatchJs
I have a test framework based on Nightwatch, but now I want to run tests that involve two different users logging into our application, taking an action as one user that affects a view that the other user is using, and verify that the second user's view updates correctly. 

So far, I get the sense that NightwatchJS was built with the assumption that you're only driving one browser within a test Scenario. You can test multiple user accounts within your app by logging out / logging in, but in my case, I want to test a feature where two users are logged in to different browser, and User A takes an action that affects User B's view automatically. So I need to be able to drive two browsers, on different Selenium nodes, within one test.

Two questions:

1) Does anyone know if this is possible in Nightwatch, or any tips on how I might create my own fork of Nightwatch and enhance it to do a test like this?

2) Webdriver.io can drive two browsers in one test, using their multiremote feature. Has anyone ever mixed Nightwatch and Webdriver.io in the same framework? 

Thanks,
Scott

Serhii Sinina

unread,
Sep 14, 2018, 1:41:01 PM9/14/18
to NightwatchJs
Hi. I used this article: http://nightwatchjs.org/blog/testing-webrtc-apps-with-nightwatch/

I launched test with 2 environments "nightwatch -c tests/e2e/nightwatch.conf.js  -e chrome_1,chrome_2"
As a result I had 2 browsers performing the same steps. Then I used process.env.__NIGHTWATCH_ENV_KEY inside my tests to make each browser perform it's own steps. e.g
switch(process.env.__NIGHTWATCH_ENV_KEY): {
 
case 'chrome_1':
   
//steps for browser1
 
case 'chrome_2':
   
//steps for browser2
}

Thus, each browser is performing it's own set of commands in parallel. But this solution has some drawbacks because browsers can't share variables and as a result it's hard to synchronize environments. For example browser1 logged into system faster than browser2 and started waiting for incoming message. Browser2 didn't load in time and wait in browser1 failed. I manage to partially solve this problem by using external memcached, where each environment can save variables, and other environment can read them. This solution looks ugly but it works:) I described my problem in the post https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!topic/nightwatchjs/Z3-iKD7S78s

пятница, 14 сентября 2018 г., 20:16:17 UTC+3 пользователь Scott Moore написал:

Mark Liberman

unread,
Sep 14, 2018, 3:01:37 PM9/14/18
to nightw...@googlegroups.com
To synch up the timing between two user sessions, you can use an external file locking mechanism:

#!/bin/bash

# lock-session.sh


CREATE=false

REMOVE=false

while getopts ":crt:l:w:" opt; do

  case ${opt} in

    c ) 

      CREATE=true

      ;;

    r ) # process option t

      REMOVE=true

      ;;

    t )

      MAXTRIES=$OPTARG

      ;;  

    l )

      LOCKFILE=$OPTARG

      ;; 

    w )

      FILETEXT=$OPTARG

      ;;            

    \? ) echo "Usage: cmd [-c] [-r] [-t tries] [-l lockfile] [-w text in file]"

      ;;

  esac

done


if [ -z $LOCKFILE ]; then

  LOCKFILE=tests/testfiles/autotestlock.txt

fi


if [ -z $FILETEXT ]; then

  FILETEXT=`date`

fi


if [ $CREATE = true ]; then

  if [ -e $LOCKFILE ]; then

    echo "A session already has lock on ${LOCKFILE}. Waiting ..."

    i=1

    if [ -z $MAXTRIES ]; then

      echo "Defaulting to 40 tries."

      MAXTRIES=40

    fi

    while [ -e $LOCKFILE ] && [ $i -lt $MAXTRIES ]

    do

      echo "Waiting for ${LOCKFILE} to free up. ${i} of ${MAXTRIES}"

      ((i++))

      sleep 5

    done

    if [ -e $LOCKFILE ]; then

      echo "Timed out waiting for ${LOCKFILE}"

      exit 1

    else

      echo "${LOCKFILE} free after ${i} tries. Creating now"

      echo $FILETEXT > $LOCKFILE 

    fi

  else

    echo "Creating lock file: ${LOCKFILE}"

    echo $FILETEXT > $LOCKFILE   

  fi

elif [ $REMOVE = true ]; then

  echo "Removing lock file: ${LOCKFILE}"

  rm -f $LOCKFILE

fi


... and wrap calls to that script in a custom function:


/** 

 * lockSession - Handles session lock management for multi-user tests

 * @param {object} lock

 * @param {boolean} lock.establish - true = establish lock, false = free up lock

 * @param {text} lock.text - text to write to log file (for debugging)

 * @param {boolean} [lock.skipPause=false] - will pause after freeing up lock file unless skipPause = true

 * @param {number} [lock.seconds=250] - How long to wait for lock before error

 */


exports.command = function(lock) {

  var waitIterations = (lock.seconds > 0) ? Math.floor(lock.seconds / 5) : 30;

  var fileTextSwitch = (lock.text) ? ` -w ${lock.text}` : '';

    

  if (lock.establish === true) {

    // Establishing a lock.  Will wait to get it

    this

      .executeShellCommand(`./tests/scripts/lock-session.sh -c -t ${waitIterations} ${fileTextSwitch}`, "Error establishing session lock")

      .consoleLog('Lock created')

  } else {

    // Removing lock

    this

      .executeShellCommand("./tests/scripts/lock-session.sh -r", "Error removing session lock")

      .consoleLog('Lock removed')

      

    //Pause to give other side a chance to grab the lock.  Can skip for cleanup at beginning, end.

    if (lock.skipPause === undefined || lock.skipPause === false) {

      this

        .consoleLog('Pausing after removing lock so other side can get lock')

        .pause(this.globals.defaultLongPause)

    }

  }

  

  return this;

};


And then make calls to lockSession as appropriate in your test script:


var primaryUser = (process.env.__NIGHTWATCH_ENV_KEY === `${process.env.__NIGHTWATCH_ENV}_1` || process.env.__NIGHTWATCH_ENV_KEY === undefined)


module.exports = {


  "Step 1": browser => {

      if (primaryUser) {

        browser

          // Remove lock to start test

          .lockSession({establish: false, skipPause: true})

          .lockSession({establish: true, text: 'primary', seconds: 120})


          <DO YOUR TEST STEPS FOR primaryClient>



          // Free up lock

          .lockSession({establish: false})

      } else {

        // Pause so secondUser does not grab lock first 

        browser

          .pause(browser.globals.defaultLongPause)

          // My turn for lock

          .lockSession({establish: true, text: 'secondary', seconds: 200})


          <DO YOUR TEST STEPS for secondUser>


          // Free up lock

          .lockSession({establish: false})

      }

  },


--
You received this message because you are subscribed to the Google Groups "NightwatchJs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nightwatchjs...@googlegroups.com.
To post to this group, send email to nightw...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nightwatchjs/ff634fb7-e9a9-4a2d-b5be-0189ac3d8d63%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--

Mark Liberman

Software Engineer - Test Automation

atscale.com

Scott Moore

unread,
Sep 17, 2018, 11:42:17 AM9/17/18
to NightwatchJs
Serhii, 

Thanks for the tip and the link to the article.

Reading the article made me realize something important: given that it is by the developer who founded & leads Nightwatch, and it is (in my opinion) a somewhat awkward way to use Nightwatch to drive two browsers, it confirms what I was suspecting: Nightwatch seems to be built around the assumption that your test is only driving one browser at a time.

I think your solution is a creative approach, but I really want to figure out a way to drive two browsers using the same client process. The tests I want to run are actually not just straightforward Nighwatch tests, but also use Cucumber, and the nightwatch-cucumber project to integrate them. 

I want to be able to write a test like this (expressed as Gherkin code here):

Given I am running a multi-user test with users "User A, User B"
When I am in User A's browser
 And I log in as "admin@mywebapp
.com"
 And I create an account for "us...@mywebapp.com"
Then the account list shows an active account for "us...@somesite.com"

When I am in User B'
s browser
 
And I log in as "us...@somesite.com"
Then the message "Welcome to your account!" appears

When I am in User B's browser
 And I disable the account for "us...@mywebapp.com"
 And I create an account for "us...@somesite.com"Then the account list shows an account for "us...@somesite.com"
Then the account list shows a disabled account for "us...@somesite.com"

When I am in User B'
s browser
Then a dialog appears with the warning "Someone has disabled your account. You will be redirected to the login screen."

When I wait for the warning dialog to go away
Then the login page is shown




Trying to get a test like that working but using two different node processes to run Nightwatch, one for each user's browser, seems very complicated.

I think it's possible to drive two Selenium sessions within one process because Webdriver.io can do it, and as far as I can tell, looking "under the hood" at what is going on in the WebDriver request/response cycles, if you are using two browsers of the same type, it is a simple as assigning an "applicationName" property to your Selenium node in the configurations for those nodes, then establishing a Selenium session using desiredCapabilities.applicationName to identify the specific node, and then making sure that all Selenium protocol requests that you want running on that node use the right session ID in the URL. (Alternatively, if I were testing on two different browser types, I could differentiate them using desiredCapabilities.browserName).

So now I'm digging around in the Nightwatch code to see if I can modify it to set up two different Selenium sessions instead of just one.

Thanks,
Scott

Scott Moore

unread,
Sep 17, 2018, 11:47:54 AM9/17/18
to NightwatchJs
Libermaniac,

Thanks for the suggestion and the detailed code example. It seems like the article that Serhii pointed me to ( http://nightwatchjs.org/blog/testing-webrtc-apps-with-nightwatch/ ), but with the added sophistication of using the filesystem to coordinate between the two tests, instead of relying on timing, waiting, etc. 

I really want to try to get one test driving two clients from one node process, though, for the reasons I mentioned in my reply to Serhii. I'm wading through the Nightwatch code now, trying to figure out how it works, and seeing if I can find a way to fool it into driving two different browsers by switching back and forth between two Selenium session IDs. If that doesn't work, though, I may try the approach you mention.

Thanks,
Scott
Reply all
Reply to author
Forward
0 new messages