How to share (depending) data between page objects?
See original GitHub issueI have been searching around for a while; I am not as proficient in Node and/or nightwatch, but I did work with the Page Object Pattern quite a lot using Geb (and its Page Object Pattern).
What I try to achieve is basically something like this:
- Go to web page 1
- Fetch data from page 1
- Go to page 2
- Use data from page 1 and fill in there
At the moment, without the Page Object Pattern, I solve it along the lines of:
client.url('/page1');
client.getText("#somefield", function(result) {
var value = result.value;
// do other stuff
client.url('/page2');
// etc... fill in form with value
}
I don’t like this callback chaining, especially when things get more complicated it requires me to have more callbacks. (any way to reduce this callback chain?). Perhaps there are other ways to do this?
Also the docs state getValue (and getText) return a string, but it doesn’t. (bug in docs?).
Regardless: my question remains: how do you do this with Page Objects? I would like to do something along the lines of:
var someFieldFromPage1 = client.page1.getSomefield();
client.page2.navigate();
client.page2.fillInForm(someFieldFromPage1);
I know that the first line is ‘impossible’ due the callback you have to execute. Assigning to someFieldFromPage1 will not work. I also know these things are all asynchronous. (On the other hand, integration tests usually are ‘sequential’ by nature (in the abstract sense)). How to deal with this?
FYI. My frame of reference is Geb, and there I could do such ‘sequential’ things. It basically meant ‘waiting’ for the driver. I would not mind having to ‘wait’ for a ‘resolved promise’. I did not see any promises within Nightwatch though.
Any ideas would be most appreciated. I don’t mind solving it in a non-sequential way or learning new things. I am just a bit confused how to approach this.
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:6 (2 by maintainers)

Top Related StackOverflow Question
[edit]Sorry, this ended up etirely too verbose, but I got on a rant…[/edit]
It can take a while to get your head wrapped around the command pattern Nightwatch uses. You’re right in both that its sequential and asynchronous. There are callbacks that are used to interact with data during the asynchronous execution of the commands, and these can cause for some potentially nasty nesting, but the effects of this can be reduced by what will become your new favorite command:
perform().Nightwatch command functions don’t immediately perform the command they represent when called. A
click()for example, when called, doesn’t actually click anything. It adds a click command to the command queue. The command queue is a list of commands that is built up when your test function first runs. Once the test function completes (synchronously) this queue is traversed, in sequence, running each command independently and asynchronously until complete before moving on to the next. Any command that may produce data you might have an interest in, returns that data usually in the form of a “response” packet within a callback having the value you’re looking for inresponse.value(more on this later). In these callbacks, you have the opportunity to add more commands to the command queue by calling command functions, having those commands inserted into the current location of the queue. When the callback is complete, the queue resumes, starting with any new commands that have been added. Using your first example, the test function runs and the queue contains:The queue is then traversed, loading the page1 url, waiting for the page to finish loading, then calls handles getText. That command actually calls two commands,
element()to find ‘#somefield’, thenelementIdText()to get its text. At the completion of those commands, the callback is called and aurl('/page2')is inserted into the command queue at the current location. When the callback is complete, and the queue traversal resumes, it starts withurl('/page2')and continues until the rest of the queue is complete.Now, the nice thing about this is, that as far as code being written is concerned, as long as you’re sticking to inline functions for your callbacks, the execution order of the commands matches the order in which they’re written in your code - not to be confused with the execution order. For example:
The written order here is:
And this is how it is executed in the queue, despite the execution order being:
All because the queue had not yet reached somethingElse when url(‘/page2’) was added, and url(‘/page2’) was inserted within the getText callback.
Not to go on too much about this, but what it does mean is that your code should read as it plays out, despite how it actually gets executed. This doesn’t exactly make it wait like you might expect it to, but I think its a step in the right direction.
Now this nesting is another issue. Since you can only get values in callbacks, that means any time you need a value, you essentially have to go one level deeper into another callback block, right? Not exactly. You will still need to have get into callbacks; there’s no getting around that. But you should pretty much never need to go deeper than 1 or two levels. Why? Because of the way the command queue works and because of a little command called
perform().The perform command is very basic. It really doesn’t do anything on its own. What it offers is a way for you to run code that gets executed within the current context (point in time) of the command queue - almost like a command callback but without a command.
Why this matters is because it allows you to break out of callback nesting by having a top-level perform that is in the appropriate location in the queue that is run after your callbacks complete. From the text example:
Because the perform operation is after everything else (despite the function being executed before the callbacks), it is able to have access to any callback values that get assigned to a variable that is accessible within its own callback - basically any variable declared in the test function scope.
Again, it doesn’t completely escape callbacks, but it does help prevent a potentially disastrous nesting scenario. And this same approach can be taken with your page objects. As long as your page objects expose
someFieldFromPage1that can be saved to a variable within the test or somehow otherwise be accessible to your other page, you’re good. I should note that while custom commands aren inherently processed within the queue, custom page object commands are not. They are more like “methods” than “commands” so they execute synchronously when called, and if you want something in a custom page object command to be called from within the queue, it should be done in a callback or aperform().Now, back to callbacks and what they return. The documentation is a little iffy about this. At the start of the commands section, theres a note about the format of callbacks used in commands: http://nightwatchjs.org/api#commands But this is not entirely accurate. Most commands do use this format, returning an object that not only has a value (for commands that return one) but also a status for checking the state of the command and whether or not it was successful. But there are some exceptions like
getTitle(). This one actually returns the title as a string.getValueandgetTextare not exceptions. These actually return objects with values within theresult.valueproperty. I think most of the “Returns” documentation on these kinds of commands actually refers to the value ofresult.valuerather than the actual value passed into the callback. But for the exceptions, its hard to tell. The best thing to do is look at the example if there is one. Otherwise try it out for yourself.The documentation certainly has some problems, but its been slowly improving over time. If you see something that’s wrong, you can log an issue either here or in https://github.com/nightwatchjs/nightwatch-docs depending on where its sourced.
@senocular thanks a lot for your elaborate answer. Couldn’t wish for more. Perhaps your comment can be used as a resource or be merged with the existing documentation. Especially since it explains things in a way from a ‘testing’ perspective. I.e. , as someone writing tests I’d like to know how to chain things and this is the right abstraction. (ie, the nightwatch docs are very much about ‘what can it do’ - versus your comment saying: ‘this is the idea when you want to do more complex tests’).