nightwatchjs / nightwatch

Integrated end-to-end testing framework written in Node.js and using W3C Webdriver API. Developed at @browserstack
https://nightwatchjs.org
MIT License
11.79k stars 1.31k forks source link

storing variable data on the fly in global variable for later use #1055

Closed MrActiv8 closed 8 years ago

MrActiv8 commented 8 years ago

Hello,

I am looking to store a variable data into a global file which is working fine within a function (mentioned below) but as soon as I come out of function this resets back to what has been defined in global variable

.getText('//div[@id="user-information"]/p', function(result) { browser.globals.capture.value_id = result.value; this .assert.containsText('//div[@id="user-information"]/p', browser.globals.capture.value_id) }) Assertion on above function working fine.

At this position the variable resets back to what it is from the global.js (mentioned below) .assert.containsText('//div[@id="user-information"]/p', browser.globals.capture.value_id)

Global.js var capture = { value_id: 'bbq', };

module.exports = { capture: capture }

The question is how do I store variable globally to be used later on.

AssadQ commented 8 years ago

I feel the best way to do this might be to write into the Global.js file using fs or something along those lines. Because the global.js file will be loaded a new each time and unless you write into that file, it will reset each time.

That's my understanding, but someone else might have more experience and prove me wrong :p

MrActiv8 commented 8 years ago

Thanks for your reply, but can you please elaborate more on how to do this? I can see you mentioned FS here but is it possible to write/Append on global.js on the fly ?

senocular commented 8 years ago

The note in http://nightwatchjs.org/guide#test-globals says:

By default, a deep object copy will be created for each test suite run. If you'd like to maintain the same object throughout the entire tests run, set the persist_globals option to true...

I personally have not used this, but it sounds like what you want.

MrActiv8 commented 8 years ago

@senocular I have tried this with no luck. Unless I am doing something wrong. I have used this in test settings (nightwatch.json) mentioned below:

"test_settings" : { "default" : { "selenium_port" : 4444, "selenium_host" : "localhost", "silent": true, "persist_globals" : true

And i am using same function mentioned above in my original query. Any help would be much appreaciated.

I am using Nightwatch version V0.9.5

senocular commented 8 years ago

Looking back at your original question, this may be an order of operations issue.

A little background: When you write a series of commands for nightwatch, what you end up with is a collection of code which gets immediately executed -- or "read" may be a better way of describing it -- when the containing test function runs. Though test command operations run asynchronously, the test function itself is run synchronously, building a queue of commands that eventually get executed over time.

Once that queue is built, nightwatch will go back and start running through the actual operation of each of those commands in the queue in a background task that is running independent of the test function, following the instructions defined by it.

Nightwatch also allows you to specify callback functions which get called asynchronously while the test commands are being executed. These callbacks are tied to specific commands and get executed after the command operation completes during this background task.

Despite the fact that these callbacks get called after the test commands have been defined, they still allow you to insert additional commands into the queue as its running rather than you being stuck with what was in the original test pass (otherwise they might not be all that useful)...

... but getting to the matter at hand. It sounds like maybe you're trying to use a variable set in a callback in the main test body which gets run before the callback has a chance to define your variable:

browser.getText('//div[@id="user-information"]/p', function(result) {
   browser.globals.capture.value_id = result.value;
   this.assert.containsText('//div[@id="user-information"]/p', browser.globals.capture.value_id)
})

// are you attempting this here outside of a callback
// expecting the updated value for value_id?
browser.assert.containsText('//div[@id="user-information"]/p', browser.globals.capture.value_id)

If this is what you are trying to do, then it won't work because the value of value_id in the last containsText() call will be the original value because that code gets executed before the callback. The order of operations (as far as code execution goes) is something like:

(1)
browser.getText(..., function(result) {
   (3)
   ...
})

(2)
browser.assert.containsText(...)

Anything directly in the test function gets called immediately (1, 2) and the callbacks (3) get called after. If you want anything to use values defined in a callback, then it too must either be in that callback or used in a callback that follows the callback where they're defined.

There's a perform() command whose sole purpose is to allow you to run code in a callback, giving you the opportunity to perform tasks in the context of the test run rather than the initial setup phase before any callbacks have been called. The example in the docs is pretty much in line with what I'm talking about here (that is assuming I'm even right about your problem to begin with):

this.demoTest = function (browser) {
  var elementValue;
  browser
    .getValue('.some-element', function(result) {
      elementValue = result.value;
    })
    // other stuff going on ...
    .perform(function(client, done) {
      console.log('elementValue', elementValue);
      // potentially other async stuff going on
      // on finished, call the done callback
      done();
    });
};

http://nightwatchjs.org/api#perform

MrActiv8 commented 8 years ago

Thanks @senocular

I understand that Step 1 & 2 get called in first and then step 3 but waht I am looking for is to store the variable which I got in Step 3 for later use, may be same script or any other.

I am giving you some example of what i am doing below>:

There are 3 scripts in total, Submitting the order, Amending the order and cancelling the order.

Submit Order: I filled in some details and say buy 2 products and after successful payment I get an unique order no to confirm that my order is placed. What i would like to do is to store the Order no. to be used in my next script which is Amending the order below.

Amend Order: I will use my stored global variable to retrive my order and then I will cancel one of the products and submit it again. once I submit it I will get another unique submission order no. which I would like to store it again in global variables where I would be using this in my next script which is cancellation.

Cancel Order: Again I will use my stored global variable from my Amend order script to retrive my order and this time I will cancel my order. Here I would like to store the confirmation message that my order has been cancelled.

By looking at what you said it seems not possible to store variables outside a callback and if that's the case then is there any other solution to my requirements?

thanks for your help and patience.

senocular commented 8 years ago

When you say "script", do you mean test case (function) or test suite (file)?

Values can persist within the same file (and/or function for that matter), global or otherwise. But when you talk about persisting between multiple files, that's you would need to use a test global and turn on that persist_globals flag (or store it somewhere else in a persistent JS structure like node's global which wouldn't require the flag).

What I was explaining was not that its not possible to store/access variables outside the callback. Rather, that when you use the variable in step (2), it hasn't been set yet - at least not to the value set in step (3). Its about timing, not access - 2 happening before 3.

Between test cases in the same suite, it shouldn't matter. They're run sequentially. If you set a value in one, it will be available in the next case no matter where you are (callback or otherwise). In the same test case you have to watch out for order. If you set a value directly within the test case function, its only available with that value later in that function. If you set a value within a callback, its only available in callbacks later in that function.

If you're setting a value in Submit Order, Amend Order and Cancel Order should have no problem accessing it with its new value. This should be automatic without the need for the persist_globals flag (since they're in the same file, presumably).

I am using nightwatch v0.9.5 and this is the behavior I see.

module.exports = {
  'Submit Order': function (browser) {

    console.log('1 ' + browser.globals.capture.value_id); // 'bbq'

    browser.perform(function() {
      console.log('4 ' + browser.globals.capture.value_id); // 'bbq'
      browser.globals.capture.value_id = 'salad';
      console.log('5 ' + browser.globals.capture.value_id); // 'salad'
    });

    console.log('2 ' + browser.globals.capture.value_id); // 'bbq'

    browser.perform(function() {
      console.log('6 ' + browser.globals.capture.value_id); // 'salad'
    });

    console.log('3 ' + browser.globals.capture.value_id); // 'bbq'
  },

  'Amend Order': function (browser) {
    console.log('7 ' + browser.globals.capture.value_id); // 'salad'
  },

  'Cancel Order': function (browser) {
    console.log('8 ' + browser.globals.capture.value_id); // 'salad'
  }
};
MrActiv8 commented 8 years ago

I meant Test Suite as an file.

I can confirm that this is now working all ok sequentially, I had to turn Parallel testing off and that was the reason it wasn't working.

Thanks for clearing my understanding @senocular your help was much appreciated.

I can say this issue is now or can be closed.

senocular commented 8 years ago

👍 @MrActiv8 You should be able to close this issue yourself using the close button at the bottom of https://github.com/nightwatchjs/nightwatch/issues/1055

senocular commented 8 years ago

I finally hit something like this issue on my end. It has to do with global values set in a global before()

My team set up the foundation around running tests which includes some helper functions and setting up global variables dynamically at runtime, etc. Developers, such as myself, hook into this framework to make creating tests a little easier out of the gate.

With how they have it set up now, global properties are being dynamically defined in a global before() method. This works fine for a normal run, but when you introduce parallel runs with test workers, the child processes of the workers don't see the edits made in the before() - a call which occurs, but called from the original process outside the context of the workers. The workers end up with the default global values without and before() edits.

I still haven't decided if this is a bug or the nature of the beast. I guess when you're using workers you just have to be conscious of the global state barriers.

Note: multiple environment runs work fine (each of those processes get their own global before() so their expected globals are set).

beatfactor commented 8 years ago

@senocular yes, that is a limitation of using test workers - they cannot use the same external globals object, as the child process doesn't have access to the parent globals object. It could be done via messaging, like it's done here: https://github.com/nightwatchjs/nightwatch/blob/master/lib/runner/run.js#L145.

nedelcurzvn commented 4 years ago

I am trying to reach same thing, run tests in parallel and pass variable through the globals object. @beatfactor can you please provide a valid link for the example? How can I reach the parent globals object from each child process?

robert-cui-tile commented 4 years ago

I got the same problem. I think it should be categorized as an issue, as changes made through global before hook should be taken into account for all workers. The repo link seems not work.