10

How can I make a globally accessible variable in nightwatch.js? I'm using a variable to store a customized url (dependent on which store is loaded in our online product), but I need it to be accessible across several javascript functions. It appears the value of it resets after each function ends, despite it being declared outside of the function at the head of the file.

Huangism
  • 16,278
  • 7
  • 48
  • 74
tmacarthur
  • 141
  • 1
  • 1
  • 6
  • Sounds like you need to read up on [variable scope](http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/) in Javascript. – wersimmon Jul 31 '14 at 19:39
  • To me, the need for global variables signals bad software design. My experience is that global variables can usually be eliminated through proper encapsulation. For example, you say you need the same variables in multiple functions. Is there anything keeping you from placing those functions in an object, and use properties (state) on that object instead of globals? The exception would be constants, which are often perfectly fine as globals. See if your framework(s) can't help you out with encapsulation, for instance AngularJS has a dependency injection system (I don't know about nightwatch). – Emil Lundberg Jul 31 '14 at 19:43
  • 1
    @harbichidian I'm not above needing some refreshing, but are you familiar with using nightwatch for end-to-end testing? it exhibits some atypical behaviors that doesn't match typical javascript functionality, in this case regarding variable scope. – tmacarthur Aug 01 '14 at 14:07
  • @tmacarthur My apologies, then. May I suggest that you mention what solutions you've tried? This typically helps get useful answers. – wersimmon Aug 01 '14 at 17:10
  • @EmilLundberg for config globals are ok. – SuperUberDuper Dec 08 '15 at 13:51
  • @Huangism could you mark an answer to your question? That would be helpful to others, and myself and two others have attempted an answer already. – GrayedFox Sep 02 '16 at 16:12
  • @GrayedFox No I cannot because I did not ask it – Huangism Sep 04 '16 at 23:37
  • Oh silly me!!! Sorry Huangism. @tmacarthur see my above comment? (about marking an answer) – GrayedFox Sep 05 '16 at 10:37

6 Answers6

18

It's been some time since you asked your question and support for what you requested might not have been (natively) available before. Now it is.

In the developer guide two methods are provided for creating global variables accessible from any given test, depending on your needs. See here for good reading.

Method 1: For truly global globals, that is, for all tests and all environments. Define an object, or pass a file, at the "globals_path" section of your nightwatch.json file, i.e.

"globals_path": "./lib/globals.js",

You will need to export the variables, however, so brushing up on Node is a good idea. Here is a basic globals.js file example:

var userNames = {
  basicAuth: 'chicken',
  clientEmail: 'SaddenedSnail@domain.com',
  adminEmail: 'admin@domain.com',
};

module.exports = {
  userNames: userNames
}

This object/file will be used for all of your tests, no matter the environment, unless you specify a different file/object as seen in method 2 below.

To access the variables from your test suite, use the mandatory browser/client variable passed to every function (test), i.e:

 'Account Log In': function accLogin(client) {
    var user = client.globals.userNames.clientEmail;

    client
      .url(yourUrl)
      .waitForElementVisible('yourUserNameField', 1000)
      .setValue('yourUserNameField', user)
      .end();
}

Method 2: For environment based globals, which change depending on the environment you specify. Define an object, or pass a file, at the "globals" section of your nightwatch.json file, nested under your required environment. I.e.

"test_settings" : {
    "default" : {
      "launch_url" : "http://localhost",
      "selenium_port"  : 4444,
      "selenium_host"  : "localhost",
      "globals": {
        "myGlobal" : "some_required_global"
      }
    }
}

Please note that at the time of writing, there seems to be a bug in nightwatch and thus passing a file using Method 2 does not work (at least in my environment). More info about said bug can be found here.

Bill Criswell
  • 32,161
  • 7
  • 75
  • 66
GrayedFox
  • 2,350
  • 26
  • 44
4

To expand on Tricote's answer, Nightwatch has built-in support for this. See the documentation.

You can either specify it in the nightwatch.json file as "globals": {"myvar": "whatever"} or in a globals.js file that you reference within nightwatch.json with "globals": "path/to/globals.js". In the latter case, globals.js could have:

module.exports = {
  myvar: 'whatever'
};

In either case, you can access the variable within your tests as Tricote mentioned:

module.exports = {
  "test": function(browser) {
    console.log(browser.globals.myvar); // "whatever"
  }
};
imiric
  • 8,315
  • 4
  • 35
  • 39
1

I'll probably get down-voted for this, but another option that I have been using successfully to store and retrieve objects and data is to do a file write as key value pairs to an existing file.

This allows me to, at the end of a test run, see any data that was randomly created. I create this file in my first test script using all of the data I will use to create the various accounts for the test. In this way, if I see a whole lot of failures, I can take a look at the file and see what data was used, then say, log in as that user and go to that location manually.

In custom commands I have a file that exports the following function:

saveToFile : function(path, filename, data) {
    this.yfs = fs;
    buffer = new Buffer(data);

    console.log("Note: About to update the configuration with test data" )

    fs.open(path, 'w', function(err, fd) {
        if (err) {
            throw 'error opening file: ' + err;
        }

        fs.write(fd, buffer, 0, buffer.length, null, function(err) {
            if (err) throw 'error writing file: ' + err;
             return fs.close(fd, function() {
                console.log('File write: ' +  path + ' has been updated.' );
            })
        });
    })
},

In this file, 'data' is key value pairs like "username" : "Randy8989@mailinator.com". As a result I can use that data in later scripts, if so desired.

This being true, I'll be exploring GrayedFox's answer immediately.

QualiT
  • 1,934
  • 2
  • 18
  • 37
0

Not sure it's the best way, but here is how I do it : you can define a variable in the browser.globals and access it in your different tests

For instance :

 module.exports = {
  before: function(browser) {
    console.log("Setting up...");

    // initialize global variable state
    browser.globals.state = {};
  },

  "first test": function(browser) {
    var settings = browser.globals,
        state = browser.globals.state;

    state.my_shared_var = "something";

    browser.
      // ...
      // use a shared variable
      .setValue('input#id', state.my_shared_var)
      // ...

      // ...
      // save something from the page in a variable 
      .getText("#result", function(result) {
        state.my_shared_result = result.value;
      })
      // ...
  },

  "second test": function(browser) {
    var settings = browser.globals,
        state = browser.globals.state;

    browser.
      // ...
      // use the variables
      .url("http://example.com/" + state.my_shared_result + "/show")
      .assert.containsText('body', state.my_shared_var)
      // ...
  }
}
Tricote
  • 1,508
  • 1
  • 13
  • 21
0

An alternative of globals.json if you need read data with complex procedure, is just to create a function in the same test file.

In the following example, I needed simple values and data from csv.
So I created getData() function and I can invoke directly from inside:

let csvToJson = require('convert-csv-to-json');

function getData(){
  let users = csvToJson.getJsonFromCsv("/../users.csv");
  return {
    "users:": users,
    "wordToSearch":"JRichardsz"
  }
}

module.exports = {
    
    "login": function(browser) {

      //data is loading here
      var data = getData();

      browser
           .url('https://www.google.com')
           .waitForElementVisible('input[name="q"]', 4000)
           .setValue('input[name="q"]', data.wordToSearch)
           .keys(browser.Keys.ENTER)
           .waitForElementVisible('#result-stats', 4000)
           .end();
    }
};
JRichardsz
  • 14,356
  • 6
  • 59
  • 94
-3

generaly it is a bad practice, but you can assign it as field of window class.

window.someGlobalVar = 'http://example.org/'

and window object is accessible globally

vodolaz095
  • 6,680
  • 4
  • 27
  • 42