Custom command issues

1,258 views
Skip to first unread message

Sébastien Moran

unread,
Feb 18, 2014, 3:38:00 PM2/18/14
to nightw...@googlegroups.com
Hi, 

I'm trying to add a custom command that sets a cookie.
I took the example you provide as starting point, encountered several issues with selenium "add cookie" to eventually add my cookie :)

Only issue, my command is not chained and the process is stopped right after it...

I then look at the built-in commands and took the "pause" command as template to use "self.emit('complete')" hoping to solve my chaining issue. But now I get the following error:

Error while running setCookie command: Cannot call method 'apply' of undefined
FAILED:  1

I even tried to duplicate the pause command and I still get that same error.

Help me Obi Wan Kenobi your my only hope !

Thanks for you help.

Sebz.

steve.calvert

unread,
Feb 18, 2014, 3:49:41 PM2/18/14
to nightw...@googlegroups.com
I also find I'm getting confused by the custom commands. The pause, for instance, confuses me as to how it's actually pausing when not using a callback. In the examples, pause is invoked in a chain, but looking at the pause command, the settimeout wraps the callback, therefore when executing this command when not using a callback it won't pause. Am I missing something?

Sébastien Moran

unread,
Feb 18, 2014, 4:07:17 PM2/18/14
to nightw...@googlegroups.com
At first I thought that the callback was the right way to say "I'm done", but discovered that no callback was provided.
I then saw the use of emit.

Looking at the pause code it should work even if no callback is provided. Did actually encountered the bug you describe?

Andrei Rusu

unread,
Feb 18, 2014, 4:55:20 PM2/18/14
to nightw...@googlegroups.com
Hi there,

I agree that the "custom commands" docs and examples could be improved. Commands in general are designed to be wrappers or convenience methods around the selenium protocol actions. So, usually they will call one or more existing protocol actions (e.g. the click command calls protocol.element and protocol.elementIdClick). 

What you're trying to accomplish is actually build a protocol action. So in your command you should simply return a POST request call which is an event emitter instance so you don't have to call emit manually. You can use runProtocolCommand which is available on the main instance. I apologize that the docs don't mention anything about this, I'm still working on the api docs. Your code would look something like this:

exports.command = function(cookies, callback) {
  var self = this;
   return this.runProtocolCommand({
     path : '/session/' + this.sessionId + '/cookie/',
     method : "POST",
     data : '  ... '
   }, function() {
     if (typeof callback == 'function') {
        callback.call(self);
     }
  });
};

Hope this helps.

Andrei Rusu

unread,
Feb 18, 2014, 4:56:28 PM2/18/14
to nightw...@googlegroups.com
I am actually planning to implement cookie support in the next release.


On Tuesday, February 18, 2014 10:07:17 PM UTC+1, Sébastien Moran wrote:

Andrei Rusu

unread,
Feb 18, 2014, 5:07:17 PM2/18/14
to nightw...@googlegroups.com
About pause, the way it works is that it postpones that complete event for a given number of milliseconds. All the other commands in the chain will wait for this event to be emitted, so everything will be paused. The callback doesn't matter in the sense that will not affect the pausing of the test in any way. It's just a way of allowing one to execute some code right before the script continues. Maybe a more appropriate name would be sleep or suspend

If you don't pass any callback is fine. Another thing which is not documented is that if you don't pass any arguments at all, it will pause indefinitely. Also, pause works better if selenium is managed by nightwatch.

Cheers,
Andrei


On Tuesday, February 18, 2014 10:07:17 PM UTC+1, Sébastien Moran wrote:

Sébastien Moran

unread,
Feb 18, 2014, 5:08:27 PM2/18/14
to nightw...@googlegroups.com
Hi Andrei, don't be sorry! 
Thanks for the explanation. I was using request to make the POST call.

I'll test your code.

Thanks again.

Sébastien Moran

unread,
Feb 18, 2014, 5:09:19 PM2/18/14
to nightw...@googlegroups.com
Okay, thanks for the info. I'll try anyway, for the challenge :P

Keith Bingman

unread,
Feb 19, 2014, 2:03:25 AM2/19/14
to nightw...@googlegroups.com
I have a similar issue. I have a custom command that needs to pause until some scripts in the browser are completed. Using the event emitter with Nightwatch itself works, but as a custom command I am getting the same error: Error while running setCookie command: Cannot call method 'apply' of undefined

Any help appreciated!

Keith


On Tuesday, February 18, 2014 12:38:00 PM UTC-8, Sébastien Moran wrote:

Sébastien Moran

unread,
Feb 19, 2014, 9:48:38 AM2/19/14
to nightw...@googlegroups.com
So :)

I've tested and it works!
The only thing that was missing in your example was a call to .send() on the this.runProtocolCommand(..).

The working piece of code is:

exports.command function(cookies, callback) {
  var self this;
   return this.runProtocolCommand({
     path : '/session/' this.sessionId + '/cookie/',
     method : "POST",
     data :  {
            cookie: {
                "name": "yourCookieName",
                "value": "yourCookieValue",
                "domain": "yourDomain",
                "path": "/",
                "secure": false
            }
        }
   }, function() {
     if (typeof callback == 'function') {
        callback.call(self);
     }
  }).send(); // << don't forget that last call otherwise nothing will happen :)
};

An extra tip regarding the setCookie. 
You'll have to make a first call to .url("http://yourDomain.com/dummy.html") to be in the "yourDomain" context to be able to create a cookie on that specific domain.
Otherwise, you'll get the following error : 
org.openqa.selenium.InvalidCookieDomainException: You may only set cookies for the current domain

Thanks again for your help !

steve.calvert

unread,
Feb 19, 2014, 2:33:25 PM2/19/14
to nightw...@googlegroups.com
We found a bug in the custom commands code. If the command is a simple function assigned to module.exports, it works fine. If it's a CommandAction then it was returning the CommandAction constructor as the module.exports, so when loading the custom commands this command action had to be newed up in order to correctly assign command.command when adding a command.

We've fixed the issue, and are opening a PR.


On Tuesday, February 18, 2014 12:38:00 PM UTC-8, Sébastien Moran wrote:

Andrei Rusu

unread,
Feb 19, 2014, 4:58:44 PM2/19/14
to nightw...@googlegroups.com
I'd say it's more a limitation. The behaviour you are describing exists for the nightwatch commands. The thing is that these "custom commands" were thought of as small bits of code that you want to execute in the context of the application using execute, so I haven't gone too far with them. It looks like they should allow to properly extend the framework.

Andrei Rusu

unread,
Feb 19, 2014, 5:16:21 PM2/19/14
to nightw...@googlegroups.com
Actually you shouldn't need to call .send(). That should be handled by the async queue. Can you show me how your test looks like?

Sébastien Moran

unread,
Feb 20, 2014, 2:14:33 AM2/20/14
to Andrei Rusu, nightw...@googlegroups.com
OK, here it is. The setToken() command is my custom command.

Thanks!

var request = require("request"),
    querystring = require("querystring"),
    async = require("async");

function avAuthenticate(callback) {
    var authParams = {
        grant_type: 'password',
        client_id: AV_CLIENT_ID,
        client_secret: AV_CLIENT_SECRET,
        username: AV_EMAIL,
        password: AV_PASSWORD
    };

    request.get({
        url: AV_BASE_URL + AV_AUTH_URL + "?" + querystring.stringify(authParams),
        json: true
    }, function(error, response, body) {
        avToken = body.access_token;
        callback(null, avToken);
    });
}

function launchTests(options, callback) {
    var url = AV_BASE_URL + "/monitor/systems";

    options.browser
        .url(AV_BASE_URL + "/login")
        .setToken(options.token)
        .url(url)
        .waitForElementVisible('a#nav_section_link_systems', 5000)
        .click("#system_actions_container button.dropdown-toggle")
        .assert.cssClassPresent("#system_retrieveData", "disabled")
        .click("#system_datatable td:nth-child(1) > input")
        .click("#system_actions_container button.dropdown-toggle")
        .assert.cssClassNotPresent("#system_retrieveData", "disabled")
        .click("#system_actions_container button.dropdown-toggle")
        .waitForElementVisible('#system_retrieveData', 100)
        .click("#system_retrieveData")
        .waitForElementVisible('.modal', 100)
        .assert.containsText(".modal .modal-header h3", "Retrieve System data")
        .end();
}

module.exports = {
    "Monitor Systems": function(browser) {

        var self = this,
            tasks = [];
        AV_BASE_URL = this.client.launch_url;

        tasks.push(avAuthenticate);
        tasks.push(function(token, callback) {
            launchTests({
                browser: browser,
                token: token
            }, callback);
        });

        async.waterfall(tasks, function(err, result) {
            if (err) {
                console.log("!! Err", err);
                return;
            }
        });
    }
};



--
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/4hfihVJ9p_E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nightwatchjs...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Andrei Rusu

unread,
Feb 20, 2014, 4:34:10 AM2/20/14
to nightw...@googlegroups.com
On Thu, Feb 20, 2014 at 10:31 AM, Andrei Rusu
<andre...@beatfactor.net> wrote:
> setUp has a callback as argument. Just call that callback when things
> are done. E.g.:
>
> setUp : function(callback) {
> // got access_token
> callback();
> }
>
> On Thu, Feb 20, 2014 at 10:11 AM, Sébastien Moran <seb...@gmail.com> wrote:
>> I'll have a synchronization issue moving my authentication call to the setUp
>> function.
>> I don't see how I can say setUp is finished. Do you have an idea?
>>
>>
>> On Thu, Feb 20, 2014 at 9:50 AM, Sébastien Moran <seb...@gmail.com> wrote:
>>>
>>> Yup you're right about the setUp. I'll try that.
>>> Keep you in touch.
>>>
>>> On 2/20/14, Andrei Rusu <andre...@beatfactor.net> wrote:
>>> > Interesting, so you're test is actually depending on the authenticate
>>> > call. I think the problem is that internal command queue of nightwatch
>>> > is somehow confused so that's why you need to call the send manually.
>>> > Maybe we can make the authenticate call part of the queue somehow.
>>> > Also you could try putting the authenticate stuff in the setUp, I
>>> > think that may make your script look cleaner and you wont need async
>>> > anymore. Could you try that?

Andrei Rusu

unread,
Feb 20, 2014, 4:37:05 AM2/20/14
to Sébastien Moran, nightw...@googlegroups.com
That's for the actual test method, setUp and tearDown have a callback
instead. The nightwatch instance is also available via this.client.

On Thu, Feb 20, 2014 at 10:35 AM, Sébastien Moran <seb...@gmail.com> wrote:
> Oh ok, I thought there only was the nightwatch instance as argument.
> Thanks.

Andrei Rusu

unread,
Feb 20, 2014, 4:55:30 AM2/20/14
to Sébastien Moran, nightw...@googlegroups.com
No, the docs are right, I was mistaken. I'll have to update the implementation.

On Thu, Feb 20, 2014 at 10:39 AM, Sébastien Moran <seb...@gmail.com> wrote:
> OK, maybe http://nightwatchjs.org/guide#setup-teardown needs to be updated
> then.
> Thanks for your time!
>
>
> On Thu, Feb 20, 2014 at 10:37 AM, Andrei Rusu <andre...@beatfactor.net>

Sébastien Moran

unread,
Feb 20, 2014, 5:02:28 AM2/20/14
to Andrei Rusu, nightw...@googlegroups.com
OK now I'm lost :D

With my current version of nightwatch I don't have any callback in the setUp, right? I'll have to wait for the next release.

Andrei Rusu

unread,
Feb 20, 2014, 5:05:25 AM2/20/14
to Sébastien Moran, nightw...@googlegroups.com
Correct.

Sébastien Moran

unread,
Feb 20, 2014, 5:06:37 AM2/20/14
to Andrei Rusu, nightw...@googlegroups.com
Great, thanks!

GrayedFox

unread,
Nov 10, 2015, 8:12:35 AM11/10/15
to NightwatchJs
Hi there, 

Just bumping this thread because I am trying to execute a selenium command to get local storage like this:

return this.runProtocolCommand({
    path: '/session/' + this.sessionId + '/local_storage/',
    method: 'GET'
  }, function() {
    if (typeof callback == 'function') {
      callback.call(self);
    }
}).send();

But I get this in my console when running the test:

Error while running setAuth command: this.runProtocolCommand is not a function...

Any ideas?
Message has been deleted

GrayedFox

unread,
Nov 25, 2015, 11:16:39 AM11/25/15
to NightwatchJs
Just answering my own query here... problem was I needed to inject the JavaScript onto the page, like so:

exports.command = function(callback) {
  var self = this;

  this.execute(function getStorage() {
    return window.localStorage.getItem('item');
  },

  // allows for use of callbacks with custom function
  function(result) {
    if (typeof callback === 'function') {
      callback.call(self, result);
    }
  });

  // allows command to be chained
  return this;
};

Works much better.

Reply all
Reply to author
Forward
0 new messages