1

I'm using WebdriverIO and its wdio testrunner with mocha and chai.

I want to build some custom commands, but in this scenario, where and how is the best way to add custom commands?

shilovk
  • 7,603
  • 15
  • 54
  • 63
Siegfried
  • 80
  • 1
  • 7

3 Answers3

2

The best place to keep the custom command is in your wdio config file.

 before: function(capabilities, specs) {
       // Comment why this command is getting added and what it is supposed to do
        browser.addCommand('nameOfCommand', function() {
            // your code for the command            
        });
    },

When you are finished with adding the custom command in config file. You can call them anywhere in your whole framework by just calling browser object. Like for above custom command to be called : browser.nameOfCommand() will do the magic.

T Gurung
  • 323
  • 1
  • 7
  • That's **very bad** advice. The `wdio.config.js` file should only contain configs & hooks, as it comes out-of-the-box. That file is already too big, adding 20-30 custom commands will make it a mess (totally unreadable). If you care about **modularity** and understand that approach isn't **scalable**, you wouldn't EVER do that. – iamdanchiv Aug 03 '17 at 18:31
  • 1
    You might be correct with your approach and the way you feel comfortable. It's all about declaring them and using as per your choice. Why would you need 20-30 custom commands ? If needed then I think you should opt alternative framework. And keeping the custom command in pageObject will always require you to import or require. We had 4 custom commands and that never cluttered config files. Anyways choice is yours where you want to declare and use it. It is only a suggestion. – T Gurung Aug 04 '17 at 08:57
  • For such a small number of custom-commans, I totally agree Gurung! But on the long run, you'll want to abuse them as they make your test cases so much easier to read/write. Cheers! – iamdanchiv Aug 04 '17 at 09:05
  • Even in description of before function in generated wdio.conf.js file there is a statement "It is the perfect place to define custom commands.". If you have many custom commands you can always separate them to external module/file and in wdio.conf.js in before function just trigger commands registration. – Adrian Bystrek Sep 05 '17 at 10:39
1

While building a full-fledged automation harness powered by the exact tech-stack you mentioned (WebdriverIO/Mocha&Chai), I have come to the conclusion that Page Objects are't there yet (it's also a pain to keep them up-to-date) and the best way to harness the complete power of WebdriverIO is to write your own Custom Commands.


The main reasons why I recommend custom commands:

  • avoid reusing waitUntil() statements (explicitly waiting) for a WebElement to be displayed;
  • easily access my WebElement modules (files where I mapped my WebElements based on views, routes, or websites);
  • reuse the same custom commands in other custom commands;
  • the Mocha it() (test case) statement is much cleaner and easier to follow.

Here's an example of how you can write a custom click() (action.js file):

 module.exports = (function() {

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Def: Performs a 'click' action on the given element (WebElement)
     *      after waiting for the given element to exist and be visible. 
     * @param:  {String} element 
     * @returns {WebdriverIO.Promise}
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    browser.addCommand('cwClick', function(element) {
        var locator = cwElements.<jsonPathTo>[element] ||
                      thirdPartyElements.<jsonPathTo>[element];

        assert.isOk(locator, "\nNo CSS-path for targeted element ('" + element + "')\n");

        return browser
            .waitUntil(function() {
                return browser.isExisting(locator);
            }, timeout, "Oups! An error occured.\nReason: element ('" + locator + "') does not exist")
            .waitUntil(function() {
                return browser.isVisible(locator);
            }, timeout, "Oups! An error occured.\nReason: element ('" + locator + "') is not visible")
            .waitUntil(function() {
                return browser.click(locator);
            }, timeout, "Oups! An error occured.\nReason: element ('" + locator + "') could not be clicked")
    });

 })();

Finally, in your test suite (feature file), import the file that contains your custom command, in this case, action.js: var action = require('.<pathToCommandsFolder>/action.js');.

That's it. You're done! :)


Note: In order to keep my custom-cummand files clean, I've broken them down into several categories:

  • Actions (action.js):
    • Contains actions such as: cwClick, cwGetText, cwGetValue, cwSetValue, etc.
  • Validation (validate.js):
    • Contains validations such as: isDisplayed, isNotDisplayed, etc.
  • Navigation (navigate.js):
    • Contains navigation commands: cwBack, cwRefresh, cwForward, tab-manipulation-custom-commands, etc.
  • etc. (the skyhardware is the limit!)

Hope this helps. Cheers!

iamdanchiv
  • 3,828
  • 4
  • 32
  • 40
  • Thanks a lot! I was recommended to use Page Objects. Instead of using custom commands, then implementing the command as method of a Page Object. In a first moment it looks I could achieve the same result as I could get with Custom Commands. It appears to be more of a preference than a requirement (choosing between Custom Command and Page Objects). Your suggestion looks to be a more organized way. – Siegfried Aug 04 '17 at 12:50
  • @Siegfried indeed. When implementing a mature web-automation harness, your main concerns are: **the `WebElement` module files** (it's better to keep your `WebElements` mapped to readable-names, instead of just using hard-coded CSS/Xpath-selectors(single-point-of-change)) & secondly, the `custom-commands`/`library-functions`/`methods` (however you call them, they are the ***bread and butter*** of an automation harness). Hope you take the best decision man. Cheers! – iamdanchiv Aug 04 '17 at 14:33
  • 1
    brilliant! the pageObjects seem to be a little redundant I agree. – zero_cool May 13 '19 at 20:25
  • Thanks man! Give it a thumbs up if you like it. To be honest, the format of the solution is not up to date. I might update it today if I find the time to match both versions (`wdio-v4` & `wdio-v5`). Cheers! – iamdanchiv May 14 '19 at 09:12
0

For the record, this is an example of how I'm creating my custom commands inside Page Objects.

var HomePage = function() {
    var me = this;

    this.clickFlag = function() {
        var flag = me.navbar.$('.//li[@class="xtt-popover-click xtt-flags"]'),
            flagActive = me.navbar.$('.//li[@class="xtt-popover-click xtt-flags active"]');
        flag.click();
        flagActive.waitForExist(1000);
    }

    this.elementThatContainsText = function(tag, text) {
        var el;
        if (tag) {
            el = $('//' + tag + '[contains(content(), "' + text + '")]');
        } else {
            el = $('//*[contains(content(), "' + text + '")]');
        }
        return el;
    }

    this.highlight = function(webElement) {
        var id = webElement.getAttribute('id');
        if (id) {
            browser.execute(function(id) {
                $(id).css("background-color", "yellow");
            }, id);
        }
    }

};

Object.defineProperties(HomePage.prototype, {
    navbar: {
        get: function() {
            return $('//div[@class="navbar-right"]');
        }
    },
    comboLanguage: {
        get: function() {
            return this.navbar.$('.//a[@id="xtt-lang-selector"]');
        }
    },
    ptLink: {
        get: function() {
            return this.navbar.$('.//a[@href="/institutional/BR/pt/"]');
        }
    }
});

module.exports = new HomePage();

So, my HomePage have a now a custom clickFlag command, and a highlight command. And properties like navbar and comboLanguage which are selectors.

Siegfried
  • 80
  • 1
  • 7