How to stop execution of a callback?

195 views
Skip to first unread message

Sandeep Thukral

unread,
Mar 8, 2017, 11:33:48 AM3/8/17
to NightwatchJs
So, here is my situation.

The problem
A page has as many as 25 'topic titles' on it. A 'topic title' is a link to a topic. It has a css selector (a.qa-topic-title). I need to click on one of these that has a specific title.

My solution
I have a function in the test (might later migrate to a custom command or page object) which does this. 
Here is the code

   browser.elements('css selector', 'a.qa-topic-title', function (result) {
      result.value.forEach(function (value){
        var elementId = value.ELEMENT;
        browser.log('Checking element ID ' + elementId);
        browser.elementIdText(elementId, function (result) {
          if (result.value === 'Title by FB login') {
            browser.log('Clicking element ID ' + elementId);
            browser.elementIdClick(elementId);
          }
        });
      });
    });

    // browser.log is a wrapper around console.log. 
    // We enhance it with timestamp and, in the call structure, a custom command is executed immediately 
    // whereas a 'client' call is thrown to the end of the execution chain, so the custom command is a more timely output.

The challenge
The challenge is that the forEach loop continues even after it has clicked the element. 
I have tried to use some in place of forEach and I return true after I click the element. But no change in the output.
This is not such a big challenge in this scenario, but there are others where this does not desirable. 
I have a test which uses elementIdelement. This function takes almost no time if the element is found, but if it is not found, it takes almost one second. This wastes almost 2 minutes in my test (imagine going over such a 25 element list multiple times instead of exiting the loop once the desired element is found and clicked)

Here is the output of the code above. If you see, element ID 3 is already clicked, but the code continues to examine all the elements. I want to code to stop parsing the rest of the elements when the desired element is found.
   Log - 17:25:22 - Checking element ID 2
   Log - 17:25:22 - Checking element ID 3
   Log - 17:25:22 - Clicking element ID 3
   Log - 17:25:23 - Checking element ID 4
   Log - 17:25:24 - Checking element ID 5
   Log - 17:25:24 - Checking element ID 6
   Log - 17:25:25 - Checking element ID 7
   Log - 17:25:25 - Checking element ID 8
   Log - 17:25:26 - Checking element ID 9
   Log - 17:25:26 - Checking element ID 10
   Log - 17:25:26 - Checking element ID 11
   Log - 17:25:27 - Checking element ID 12
   Log - 17:25:27 - Checking element ID 13
   Log - 17:25:28 - Checking element ID 14
   Log - 17:25:28 - Checking element ID 15
   Log - 17:25:29 - Checking element ID 16
   Log - 17:25:29 - Checking element ID 17
   Log - 17:25:29 - Checking element ID 18
   Log - 17:25:30 - Checking element ID 19
   Log - 17:25:30 - Checking element ID 20
   Log - 17:25:30 - Checking element ID 21
   Log - 17:25:31 - Checking element ID 22
   Log - 17:25:31 - Checking element ID 23
   Log - 17:25:31 - Checking element ID 24
   Log - 17:25:32 - Checking element ID 25
   Log - 17:25:32 - Checking element ID 26

Eric Mumford

unread,
Mar 8, 2017, 10:25:55 PM3/8/17
to NightwatchJs
I solved a similar problem by converting the foreach to a classic for loop, and including a trigger variable in the for declaration. 

Example:

for (var i=0,trigger=false;i<=results.length & trigger==false;i++) {
  // set trigger=true when a condition is met and the loop will exit
  // if not the loop keeps going

Sandeep Thukral

unread,
Mar 9, 2017, 4:39:37 AM3/9/17
to NightwatchJs
Hello Eric,

Thanks for the input.

I tried to incorporate your suggestion, but it only made my situation worse.

Here is the updated code.

    browser.elements('css selector', 'a.qa-topic-title', function (result) {
      var isDone = false;
      for (var index = 0; index < result.value.length; index++) {
        browser.log('isDone in main loop is ' + isDone);
        if (isDone){
          break;
        }
        var value = result.value[index];
        var elementId = value.ELEMENT;
        browser.log('Checking element ID ' + elementId);
        browser.elementIdText(elementId, function (result) {
          if (result.value === 'Title by FB login') {
            browser.log('Clicking element ID ' + elementId);
            browser.elementIdClick(elementId);
            browser.log('Marking isDone true in callback');
            isDone = true;
          }
        });
      }
    }

Now, not only does it still continue to execute through the 25 entries, it also ALWAYS clicks on the last link, which is not the correct option.

The problem is that I update the 'trigger' variable in the callback of elementIdText call and the updated value of this variable is not available to the for loop.

Any help?

The output from the updated test

   Log - 10:37:26 - isDone in main loop is false
   Log - 10:37:26 - Checking element ID 2
   Log - 10:37:26 - isDone in main loop is false
   Log - 10:37:26 - Checking element ID 3
   Log - 10:37:26 - Clicking element ID 26
   Log - 10:37:26 - Marking isDone true in callback
   Log - 10:37:26 - isDone in main loop is false
   Log - 10:37:26 - Checking element ID 4
   Log - 10:37:28 - isDone in main loop is false
   Log - 10:37:28 - Checking element ID 5
   Log - 10:37:29 - isDone in main loop is false
   Log - 10:37:29 - Checking element ID 6
   Log - 10:37:29 - isDone in main loop is false
   Log - 10:37:29 - Checking element ID 7
   Log - 10:37:29 - isDone in main loop is false
   Log - 10:37:29 - Checking element ID 8
   Log - 10:37:30 - isDone in main loop is false
   Log - 10:37:30 - Checking element ID 9
   Log - 10:37:30 - isDone in main loop is false
   Log - 10:37:30 - Checking element ID 10
   Log - 10:37:31 - isDone in main loop is false
   Log - 10:37:31 - Checking element ID 11
   Log - 10:37:31 - isDone in main loop is false
   Log - 10:37:31 - Checking element ID 12
   Log - 10:37:32 - isDone in main loop is false
   Log - 10:37:32 - Checking element ID 13
   Log - 10:37:32 - isDone in main loop is false
   Log - 10:37:32 - Checking element ID 14
   Log - 10:37:33 - isDone in main loop is false
   Log - 10:37:33 - Checking element ID 15
   Log - 10:37:33 - isDone in main loop is false
   Log - 10:37:33 - Checking element ID 16
   Log - 10:37:34 - isDone in main loop is false
   Log - 10:37:34 - Checking element ID 17
   Log - 10:37:34 - isDone in main loop is false
   Log - 10:37:34 - Checking element ID 18
   Log - 10:37:34 - isDone in main loop is false
   Log - 10:37:34 - Checking element ID 19
   Log - 10:37:35 - isDone in main loop is false
   Log - 10:37:35 - Checking element ID 20
   Log - 10:37:35 - isDone in main loop is false
   Log - 10:37:35 - Checking element ID 21
   Log - 10:37:36 - isDone in main loop is false
   Log - 10:37:36 - Checking element ID 22
   Log - 10:37:36 - isDone in main loop is false
   Log - 10:37:36 - Checking element ID 23
   Log - 10:37:37 - isDone in main loop is false
   Log - 10:37:37 - Checking element ID 24
   Log - 10:37:37 - isDone in main loop is false
   Log - 10:37:37 - Checking element ID 25
   Log - 10:37:38 - isDone in main loop is false
   Log - 10:37:38 - Checking element ID 26

Eric Mumford

unread,
Mar 9, 2017, 5:52:30 AM3/9/17
to nightw...@googlegroups.com
Hi Sandeep,

You implemented a break to be evaluated in the loop.

for (var index = 0; index < result.value.length; index++) {
        browser.log('isDone in main loop is ' + isDone);
        if (isDone){
          break;
        }

What I suggested was using a trigger variable as a condition of the for loop, rather than a break.

for (var index = 0; index < result.value.length && !isDone; index++) {
        browser.log('isDone in main loop is ' + isDone);

--
You received this message because you are subscribed to a topic in the Google Groups "NightwatchJs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nightwatchjs/5tFvpFsIpr8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nightwatchjs+unsubscribe@googlegroups.com.
To post to this group, send email to nightw...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nightwatchjs/8e69f0bf-f38d-4833-9de9-68cd10557005%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Sandeep Thukral

unread,
Mar 9, 2017, 6:21:27 AM3/9/17
to NightwatchJs, eric.m...@gmail.com
Eric,

Thanks for being with me. I got your idea.

Here is the updated code

    browser.elements('css selector''a.qa-topic-title'function (result) {
      var isDone = false;
      for (var index = 0; index < result.value.length && !isDone; index++) {
        browser.log('isDone in main loop is ' + isDone);
        var value = result.value[index];
        var elementId = value.ELEMENT;
        browser.log('Checking element ID ' + elementId);
        browser.elementIdText(elementId, function (result) {
          if (result.value === 'Title by FB login') {
            browser.log('Clicking element ID ' + elementId);
            browser.elementIdClick(elementId);
            browser.log('Marking isDone true in callback');
            isDone = true;
          }
        });
      }
    });

I was not very sure this would work, because the update of the trigger variable is in the callback and is not updated in the synchronous code.

Here is the output of the execution.

   Log - 12:15:33 - isDone in main loop is false
   Log - 12:15:33 - Checking element ID 2
   Log - 12:15:33 - isDone in main loop is false
   Log - 12:15:33 - Checking element ID 3
   Log - 12:15:33 - Clicking element ID 26
   Log - 12:15:35 - Marking isDone true in callback
   Log - 12:15:35 - isDone in main loop is false
   Log - 12:15:35 - Checking element ID 4
   Log - 12:15:36 - isDone in main loop is false
   Log - 12:15:36 - Checking element ID 5
   Log - 12:15:36 - isDone in main loop is false
   Log - 12:15:36 - Checking element ID 6
   Log - 12:15:37 - isDone in main loop is false
   Log - 12:15:37 - Checking element ID 7
   Log - 12:15:37 - isDone in main loop is false
   Log - 12:15:37 - Checking element ID 8
   Log - 12:15:38 - isDone in main loop is false
   Log - 12:15:38 - Checking element ID 9
   Log - 12:15:38 - isDone in main loop is false
   Log - 12:15:38 - Checking element ID 10
   Log - 12:15:38 - isDone in main loop is false
   Log - 12:15:38 - Checking element ID 11
   Log - 12:15:39 - isDone in main loop is false
   Log - 12:15:39 - Checking element ID 12
   Log - 12:15:39 - isDone in main loop is false
   Log - 12:15:39 - Checking element ID 13
   Log - 12:15:40 - isDone in main loop is false
   Log - 12:15:40 - Checking element ID 14
   Log - 12:15:40 - isDone in main loop is false
   Log - 12:15:40 - Checking element ID 15
   Log - 12:15:41 - isDone in main loop is false
   Log - 12:15:41 - Checking element ID 16
   Log - 12:15:41 - isDone in main loop is false
   Log - 12:15:41 - Checking element ID 17
   Log - 12:15:42 - isDone in main loop is false
   Log - 12:15:42 - Checking element ID 18
   Log - 12:15:42 - isDone in main loop is false
   Log - 12:15:42 - Checking element ID 19
   Log - 12:15:42 - isDone in main loop is false
   Log - 12:15:42 - Checking element ID 20
   Log - 12:15:43 - isDone in main loop is false
   Log - 12:15:43 - Checking element ID 21
   Log - 12:15:43 - isDone in main loop is false
   Log - 12:15:43 - Checking element ID 22
   Log - 12:15:44 - isDone in main loop is false
   Log - 12:15:44 - Checking element ID 23
   Log - 12:15:44 - isDone in main loop is false
   Log - 12:15:44 - Checking element ID 24
   Log - 12:15:45 - isDone in main loop is false
   Log - 12:15:45 - Checking element ID 25
   Log - 12:15:45 - isDone in main loop is false
   Log - 12:15:45 - Checking element ID 26
To unsubscribe from this group and all its topics, send an email to nightwatchjs...@googlegroups.com.

To post to this group, send email to nightw...@googlegroups.com.
Message has been deleted
Message has been deleted

Akhilesh Rangade

unread,
Mar 10, 2017, 9:14:06 AM3/10/17
to NightwatchJs
Hi sandeep, 

use every() instead of forEach() and return true if want to continue loop else false;

ex - 

   browser.elements('css selector', 'a.qa-topic-title', function (result) {
      result.value.every(function (value){
        var elementId = value.ELEMENT;
        browser.log('Checking element ID ' + elementId);
        browser.elementIdText(elementId, function (result) {
          if (result.value == 'Title by FB login') {
            browser.log('Clicking element ID ' + elementId);
            browser.elementIdClick(elementId);
            return false;
          } else {
            return true;
          }
        });
      });
    })

Sandeep Thukral

unread,
Mar 10, 2017, 9:19:39 AM3/10/17
to NightwatchJs
Hello Abhilesh,

Unfortunately, this does not work.

Here is the output of the code. It shows that it ends the loop after checking the first iteration. I guess this is because the callback returns a 'true' because it encountered no error. (https://stackoverflow.com/questions/17337064/does-every-javascript-function-have-to-return-a-value)
   Log - 15:13:30 - Trying to click on the category link 'Mobile broadband'
   Log - 15:13:32 - Checking element ID 2

OK. 1 assertions passed. (5.481s)

Also, eslint says that the first callback needs to return true or false but it does not.

Akhilesh Rangade

unread,
Mar 10, 2017, 9:38:44 AM3/10/17
to NightwatchJs
Hi, 

if you want to continue loop return true else return false.

Sandeep Thukral

unread,
Mar 10, 2017, 10:00:10 AM3/10/17
to NightwatchJs
Hi,

I don't think every() is the correct solution here.

As per the definition,
The every() method tests whether all elements in the array pass the test implemented by the provided function.

I'll wait for another solution or think hard about moving to another framework or language. 

Eric Mumford

unread,
Mar 10, 2017, 5:55:23 PM3/10/17
to nightw...@googlegroups.com
To be clear, your issue has little to do with Nightwatch, but your programming in Javascript. Read the link I recommended and you'll be able to more precisely engineer a functional programming context that will achieve your goal.

--
You received this message because you are subscribed to a topic in the Google Groups "NightwatchJs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nightwatchjs/5tFvpFsIpr8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nightwatchjs+unsubscribe@googlegroups.com.

To post to this group, send email to nightw...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages