Data-driven tests possible in nightwatchjs ?

1,546 views
Skip to first unread message

Lukáš Dziadkowiec

unread,
Jul 22, 2016, 9:07:32 AM7/22/16
to NightwatchJs
Hello,
I'm quite new in nightwatch. Started to rewrite our old tests written in selenium@java into nightwatch-cucuber. I have some difficulties about the whole nightwatch philosophy.
What I miss is comprehensive API to work with text, attributes, elements. The only way to do that is using selenium API, but those functions are async. I need some intelligence in the tests, not just pick a static section->element click() and expect some text to be present.

Let's simplify my app.
<head>
...
  <header class="actionbar">
...
  <div class="workspace">
    <ul class="listcontainer">
      <li class="model1">
        <span class="title">...
        <span class="details">...
        <span class="creationdate">...
      </li>
      <li class="model1">
        <span class="title">
        <span class="details">
        <span class="creationdate">
      </li>
....

My test is to check that all the models are listed according to the creationdate. I cannot imagine how to do that using async seleniumapi call browser.getText().
I have tried a project deasync to convert the async calls to sync. But using it in nightwatch hangs the execution on some pre-processing.
I'm quite confused if this is even possible to do it using nightwatch.

Thank you for any advice and please forgive my ignorance.


Trevor McCauley

unread,
Jul 22, 2016, 10:17:11 AM7/22/16
to NightwatchJs
Hi Lukáš,

I'm not sure I follow you exactly, but I can give you a very basic rundown of how conditional logic is applied in nightwatch.

Nightwatch is an API that sits on top of selenium, providing a set of commands that users can use in their tests to build a command queue that gets executed asynchronously during a test run.  This command queue is dynamic and additional commands can be added as tests are running.  A test run starts with the initial set of browser (the value passed into a test case function) commands as defined in the main body of a test case.  Once the run starts, additional commands are added within the current location of the command queue through callback functions when additional commands are called from within them.

'sample test': function (browser) {
    browser
       
.url('example.com', function (result) {
           
this.click('button');
       
})
       
.end();
}

In the above example, the test starts with the commands [url() and end()].  After the test is running, and the url() command completes, the click() command is added changing the command queue to be [url() <completed>, click(), and end()].  The execution of the js calls end() before click(), but in performing those commands in the test run, click() now comes before end().

Callbacks are what provide you with the ability for in-test conditionals.  First, they allow you to - while the test is running - be able to retrieve real-time data from the page in test.  Then, with that data you can make decisions about what additional commands to include within the test.  For example, maybe your site might use anchors <a> or <buttons> for navigation, but you can't be sure until the test runs when you need to click on one.  So what you can do something like:

browser.element('css selector', 'button', function (result) {
   
if (result.status === 0) {
       
this.click('button');
   
} else {
       
this.click('a');
   
}
});


What I'm not sure about for you is what you mean when talking about sync and async.  Things will need to be async since the test takes time to go through and run commands - this all happening well after the initial pass of your test function (no page data is available in that first, sync pass).  The callbacks (naturally async) are where you have an opportunity to make choices.

Depending on what you're after, there may not be a command that gives you what you need exactly.  But if thats the case, you have the opportunity to create a custom command that gets you what you need.

Kuba Mucha

unread,
Jul 22, 2016, 11:57:55 AM7/22/16
to NightwatchJs

Lukáš Dziadkowiec

unread,
Jul 22, 2016, 12:40:43 PM7/22/16
to NightwatchJs
Hi guys, at first thanks for your reply.
@Kuba I may abuse the term data-driven test which is not likely this example. But I will definitely use your links later, thanks.

@Trevor
you have basically got what I meant but your examples are just simle, thus looks great, clean and simple.

So I'll try to place exact example of one test-case.
I have a list of complex elements.
li>.title
li>.description
li>.open
li>.delete
li>.creationdate

It's basically bunch of widgets which present a saved workspace. I got them by calling a REST API, but it does not matter. I have to test that they are sorted according to the li>.creationdate.
I have tried:
this.elements('css selector', 'EXPRESSION', function(result){
  console.log(result.value);
});
the result
is:
[ { ELEMENT: '9' } ]
I was hoping that the result would be like element Object with result.value[0].text having my '5:30 AM'.

So I ended with:
this.getText('.Segmentations_container .SegmentationListCardLayout_container:nth-child(1) .SegmentationCard_footer span span', function (result) {
ok, but there is anything between 1 and 50 of those widgets. I see no way to get the count nicely. Well the this.elements will return exact number of elements even the values have no sense for me.

This is the best I was able to think out
this.elements('css selector', '.widget_container .widgetListCardLayout_container .widgetCard_footer .widgetCard_detail span span', function(result){
 
var count = result.value.length;

 
var values = [];

 
function getter (values, i, count, callback) {
   
this.getText(`.Segmentations_container .SegmentationListCardLayout_container:nth-child(${i}) .SegmentationCard_footer span span`, function (result) {
     
var valueString = result.value;
     
var dateTime = parseDate(valueString);

      values
.push(dateTime);

     
if (i < count) {
       
getter(values, i++, count, callback)
     
}
     
else{
        callback
(values);
     
}
   
});
 
}
 
 
getter(values, 0, count, function (values) {
   
// now check that the values in array are in ascending order
  })
});
But I really do not like the recursion, could be 2 or 100 levels.







Trevor McCauley

unread,
Jul 22, 2016, 2:43:27 PM7/22/16
to NightwatchJs
Yeah, it gets a little sketchy when you're trying to pull out a lot of values like that.  Your current approach is one way to go, though it doesn't have to be recursive. You can make all your getText() command calls sequentially in a loop, then follow that up with a perform which will give you a callback that happens after all the getTexts complete.

Alternatively, what you can do is make an execute() which runs some page code to get all the text you're looking for in an in-page javascript query.  This would likely be much simpler.  I do this for some of our tests - not the same thing you're doing, but the same idea.

Lukáš Dziadkowiec

unread,
Jul 26, 2016, 5:17:15 AM7/26/16
to NightwatchJs
Hello,
I think that  I got it and I have found a pattert which I'm happy with.

ie for texts I'll have getMultipleElementsTexts.js
export function command(selector, callback, callbackOnError) {
 
var self = this;

 
this.execute(
   
function (selector) {
     
var result = [];
     
$.each($(selector), function () {
       
result.push($(this).text());
     
});

     
return result;
   
}
   
,[selector],
   
function(result) {
     
if (result.state === 'success') {
       
if (typeof callback === 'function') {
          callback
.call(self, result.value);
       
}
     
} else {
       
if (typeof callbackOnError === 'function') {
          callbackOnError
.call(self, result);
       
}
       
else {
         
throw new Error(`Cannot retrieve texts from elements: "${selector}"`);
       
}
     
}
   
}
 
);

 
return this;
}

And I'll call it as follows:
this.getMultipleElementsTexts(
 
model.elements.modelsModifiedOn2.selector,
 
function (result) {
   
console.log('success callback');
   
console.log(result);
 
});

Now I can call this.assert.equal or whatever from the assert API.

Thanks for the support.


Reply all
Reply to author
Forward
0 new messages