nightwatch: Page objects. Elements cannot have dynamic selectors.
I believe it would be great to have the possibility to have dynamic selectors in a page object, in the same way as we already have dynamic urls. I am sure this might be needed by many people, as some css selectors are set dynamically.
module.exports = {
url: function() {
return 'https://' + this.api.globals.host + '/#/products/';
},
elements: {
// What I can do currently
// productToSelect: {
// selector: ".product[data-product-name='T-Shirt']"
// }
// What I would like to do
// productToSelect: {
// selector: function() {
// return ".product[data-product-name='" + this.api.globals.productName + "']";
// }
}
};
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 27
- Comments: 27 (5 by maintainers)
Similar to what @ilyapalkin mentioned, you can create a page command to perform the selector mutation. The command doesn’t have to call other commands; it would basically just parameterize the element selector and give you back what you need.
command:
test:
Might not be the exact right code, you get the idea.
#1464 looks promising. Given it’s not likely it will get merged soon, we ended up implementing the same thing as a helper function and calling it from a section command. Most of the work was done by @federico-pellegatta, I thought it might be useful to post it here.
Helper function:
Page object:
Test:
@drptbl, @oubre I thought I’d give this a shot, and this is what I came up with. I don’t know how robust it is, but it seems to work for this simple case:
Usage:
So here we have a section command,
formatElwhich is dynamically “changing” the selector for the section. And by “changing” I mean creating a new, temporary section based off of the original that has a different selector. That selector is based off of a string in the original section’s props object so the original selector of that original section is able to function on its own sans-formatEl()by being just a normal selector unaffected by any changes.Edit: I guess “formatEl” isn’t the best name since we’re not working on an element, rather a section selector ;P
I’ve been using the selector member associated with the elements, like this:
@drptbl @senocular Another solution to this would be (inside PageObject):
And the test:
Thank you, also removed the unnecessary slice (which removed my class
.shorthand selector!) - silly - thought it was necessary for formatting. Also using%dwas a mistake, I think.Finished function in case people keen:
Now, before test execution, I can generate a random number to represent the length of the returned node list to randomly select an element.
Future custom command will attempt to do some async stuff before test execution and actually get the length from the page (instead of hard coding) using Selenium’s
elements()- which will make the test more resistant to simple page changes (like adding an image, etc). Thanks again@GrayedFox your problem is that you’re using an arrow function for
elwhich is breaking its context. Use a different function syntax instead.@drptbl my particular example wouldn’t work for your case because what it does is transforms the ‘@<name>’ selector used in the final call (e.g.
click()) into a string selector. The path leading up to that is obscured, handled internally.What you would need to do is go through and parse the section selector before making the call, something like:
Where
customFuncwould be some page command that went through and found a section with the name ‘firstSec’ and modified its selector for format in the second argument of ‘milk’. The problem with this approach is that after doing this, you’ve baked in the resulting value, replacing the format string. So you can only do it once until you reinstantiate another page object. Now, you could get around that by using the sections props to store the format string, but you’d still have to be careful of tracking the use of the selector since its value would persist past that initial call.I’m not entirely sure if there’s a clean way to do this now. Even adding some kind of intermediary section command would still require changing the underlying section for a persistent change
… unless that method completely rebuilt a temporary section from scratch in the background, which could be possible I suppose. The path up to ‘@testEl’ doesn’t resolve until
click()is called, and that would be from whatevercustomFunc()returned rather than the original section. Could be a little messy to implement I would guess.