question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to share (depending) data between page objects?

See original GitHub issue

I 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:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

7reactions
senocularcommented, Oct 27, 2016

[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 in response.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:

url('/page1')
getText("#somefield")

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’, then elementIdText() to get its text. At the completion of those commands, the callback is called and a url('/page2') is inserted into the command queue at the current location. When the callback is complete, and the queue traversal resumes, it starts with url('/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:

client.url('/page1');
client.getText("#somefield", function(result) {
      client.url('/page2');
}
client.somethingElse();

The written order here is:

url('/page1')
getText("#somefield")
url('/page2')
somethingElse()

And this is how it is executed in the queue, despite the execution order being:

url('/page1')
getText("#somefield")
somethingElse()
// ... waiting for callback ...
url('/page2')

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.

client.click();
console.log('before click'); // click command in queue, hasn't clicked yet
client.perform(function() {
    console.log('after click!'); // queue already ran click command, click happened
})

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:

var nestedValue;

client.url('/page1');
client.getText("#somefield", function(result) {
    var value = result.value;
    client.getText(value, function(result) {
        nestedValue = result.value; 
        // could do more, but we can stop here and wait for perform
    });
});
client.perform(function() {
    // use nestedValue
});

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 someFieldFromPage1 that 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 a perform().

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. getValue and getText are not exceptions. These actually return objects with values within the result.value property. I think most of the “Returns” documentation on these kinds of commands actually refers to the value of result.value rather 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.

1reaction
stefanhendrikscommented, Oct 28, 2016

@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’).

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to model data shared by Page Objects?
It is possible to pass information across pages, since each page is a class we add methods or params that we want. But,...
Read more >
Sharing Page Object classes and Test scenarios between ...
1. Created a method that picks the right selector based on the platform using default types. We needed a method that would choose...
Read more >
Should I add a method for each action you can do on ...
Each class has a function that takes in test data and determines what to do with it (enter data on page, click something...
Read more >
Automation in Selenium: Page Object Model and ...
Page Factory will initialize every WebElement variable with a reference to a corresponding element on the actual webpage based on configured “locators.” This...
Read more >
Representing Complex Objects for Page Object Model?
1 - Simply create both at test and ask them to deal with each other;. 2 - Know almost always the points on...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found