Nightwatch: Page object command callbacks using element references do not use API context

Created on 30 Jun 2016  ยท  6Comments  ยท  Source: nightwatchjs/nightwatch

_Note: this was originally mentioned in #1043 but I figured it deserved its own, independent issue._

Callbacks in page object element commands (those referencing an '@'-prefixed element):

https://github.com/nightwatchjs/nightwatch/blob/v0.9.5/lib/page-object/command-wrapper.js#L74

have their callbacks wrapped and re-called with a different context (the Nightwatch object) than normal callbacks:

https://github.com/nightwatchjs/nightwatch/blob/v0.9.5/lib/page-object/command-wrapper.js#L97

Example (using sample from http://nightwatchjs.org/guide#defining-elements ):

module.exports = {
  'Test': function (client) {

    function callback () {
      console.log('is client: ' + (this === client) + '; ctor: ' + this.constructor.name);
    }

    var google = client.page.google();

    google.navigate()
      .click('@searchBar', callback) // (1) page element
      .click('input[type=text]', callback) // (2) page selector
      .api.click('input[type=text]', callback) // (3) client/api selector

    client.end();
  }
};

Output:

is client: false; ctor: Nightwatch // (1)
is client: true; ctor: Object // (2)
is client: true; ctor: Object // (3)

Expected:

a) Context of element command to be consistent and be client/api.

or

b) However, I think it would be more appropriate for the context to be the page instance in the case where the commands are being called from a page object ((1), (2)). That context would allow it to retain access to page-specific commands while also having access to client/api through the api property:

    function callback () {
      console.log(this === google); // true
      console.log(this.api === client); // true
    }

Compatibility:

Changing the context like this is a breaking change that could affect existing projects if they're already depending on the Nightwatch context now. You might need a compatibility flag or whatever it is you do to handle this kind of situation to allow for backwards compatibility.

Most helpful comment

Hey @weixiaobo88,

Thanks for being thorough with your information ๐Ÿ˜ธ !

Your problem is in your nightwatch call. In your last argument you specify the test location of ./test/e2e which also includes the folder where your page object is defined. Because the page object is defined in a JS file, its being picked up by the test loader and run as a test. This is where your error is coming from, not the actual google.test.js test.

In your nightwatch.json file, you specify a correct src_folders value of "test/e2e/tests/". This will correctly point to just your test and ignore the page object JS in the run. Ditching the ./test/e2e from your call to nightwatch should fix the problem for you.

{"e2e": "./node_modules/.bin/webdriver-manager update && ./node_modules/.bin/nightwatch -c ./test/e2e/nightwatch.json"}

All 6 comments

Hi @senocular ,

I think I got a problem related to this, could you have a look and verify if it is the problem you are going to fix.

When I use v0.9.5 and run tests under official site example under Writing Commands(google search one), I got error like this TypeError: Cannot read property 'pause' of undefined.

When I test 0.8.18, it works, if I add url in elements in google.js and visit google.navigate() in google.test.js.

This really blocked me, to work around, I just write code directly in test.js instead of using page object commands.

Hi @weixiaobo88,

What you're describing doesn't _quite_ sound like what I'm fixing with this. This fix is specifically for callback functions.

Though the example under Writing Commands did need some tweaking for it to work for me in v0.9.5 (OSX 10.11.4), I saw no error like what you're describing. The pause() method in the page object command worked as expected.

In page commands, the context (this) should always be the page instance. My fix doesn't target page commands, only command callbacks, making sure the value of this in those callbacks is consistent.

Currently, the value of this in page callbacks would be different (at least in v0.9.5, the version I'm working with) depending on whether or not you used an @-element page selector or a normal selector (like css). In the element case the context would be the Nightwatch instance, and in the other case, it would be the client/browser/api object (it has so many different names in the docs) which is what this normally is for callbacks when not using page objects. My fix makes sure the element case also uses the api object and not the Nightwatch instance, though in my original post I did seem to favor using the page instance... but I think consistency is probably more important here.

I'm not sure what has changed since 0.8.18. If you want to provide me with your complete google page object and test code, I can check it out on my end.

Hi @senocular ,

Thanks for your reply. I will list my codes and expect your help to figure out what's wrong.

Environment

  • OSX EI Capitan 10.11.5
  • npm 2.14.7
  • devDependencies: webdriver-manager 10.2.1, nightwatch 0.9.5, selenium-server-standalone-jar 2.53.1
  • npm scripts (i am using npm run e2e in project)
{"e2e": "./node_modules/.bin/webdriver-manager update && ./node_modules/.bin/nightwatch -c ./test/e2e/nightwatch.json ./test/e2e"}

Folder Structure

test/
โ””โ”€โ”€ e2e
    โ”œโ”€โ”€ nightwatch.json
    โ”œโ”€โ”€ pages
    โ”‚ย ย  โ””โ”€โ”€ google.js
    โ””โ”€โ”€ tests
        โ””โ”€โ”€ google.test.js

Files
I compress the test folder and attach here.

test.zip

Hey @weixiaobo88,

Thanks for being thorough with your information ๐Ÿ˜ธ !

Your problem is in your nightwatch call. In your last argument you specify the test location of ./test/e2e which also includes the folder where your page object is defined. Because the page object is defined in a JS file, its being picked up by the test loader and run as a test. This is where your error is coming from, not the actual google.test.js test.

In your nightwatch.json file, you specify a correct src_folders value of "test/e2e/tests/". This will correctly point to just your test and ignore the page object JS in the run. Ditching the ./test/e2e from your call to nightwatch should fix the problem for you.

{"e2e": "./node_modules/.bin/webdriver-manager update && ./node_modules/.bin/nightwatch -c ./test/e2e/nightwatch.json"}

Hi @senocular,

Thanks for your patience, and correctly point out my error ๐Ÿ˜„ O(โˆฉ_โˆฉ)O~

Thanks for your help to drag me out. I didn't find it out and I think I didn't understand all stuff. It's a hard way for me to improve (๏ผ›โ€ฒโŒ’`)

So what is the solution for this? Why can I not use @photos inside certain callbacks? I notice It doesn't work with '.elements' or inside '.moveToElement'functions

/page/photos_page.js

var list = {
    deletePhoto: function () {
        var numPhotos = [];
        return this
            .api.elements('css selector','@photo', function (r) {
                // THROWS ERROR WHEN USING @photo
                numPhotos.push(r.value.length);
            })
    }
};
module.exports = {
    url: function() {
        // something
    },
    commands: [list],
    elements: {
        photo:{
            selector: 'div > div.item-wrapper > ol > li'
        }
    }
};
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Pieras2 picture Pieras2  ยท  3Comments

t00f picture t00f  ยท  3Comments

MateuszJeziorski picture MateuszJeziorski  ยท  3Comments

betweenbrain picture betweenbrain  ยท  4Comments

manjero picture manjero  ยท  4Comments