35

I'm trying to test if a todo app has the right number of elements.

The docs seem to deal almost exclusively with single elements, so I had to use the Selenium Protocol functions. Would this be the right way to test the count of matching selectors (in this case, checking for 2 li elements)?

client.elements('css selector','#todo-list li', function (result) {
    client.assert.equal(result.value.length, 2);
});

This works in my test, but I wasn't sure if there were gotchas around using a callback for this. Also not sure why Nightwatch doesn't have any helper functions dealing with more than one element.

Tev
  • 660
  • 1
  • 6
  • 13
  • Seems to me it's the proper way to use Selenium Protocol with Nightwatch (see http://nightwatchjs.org/api#elements). Why do you doubt the callback? I agree it's not pretty, but it works like it should – Phortuin Dec 02 '15 at 12:45
  • I see the following when I console.log(result) : { state: 'success', sessionId: '4568bfcf-fa1a-4d4c-bd19-082c3983cc42', hCode: 1527071402, value: [], class: 'org.openqa.selenium.remote.Response', status: 0 } --- The element is not passed into the function. Any ideas? – zero_cool Feb 17 '16 at 00:04
  • I was using 'id' instead of 'css selector.' Using 'css selector' does produce a length. – zero_cool Feb 17 '16 at 00:05

6 Answers6

13

I found the following very elegant solution within a VueJS template. It shows how to add a custom assertion into Nightwatch that counts the number of elements returned by a selector. See http://nightwatchjs.org/guide#writing-custom-assertions for details of how to write custom assertions within Nightwatch.

Once installed, usage is as simple as:

browser.assert.elementCount('#todo-list li', 2)

The plugin:

// A custom Nightwatch assertion.
// the name of the method is the filename.
// can be used in tests like this:
//
//   browser.assert.elementCount(selector, count)
//
// for how to write custom assertions see
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
  this.message = 'Testing if element <' + selector + '> has count: ' + count;
  this.expected = count;
  this.pass = function (val) {
    return val === this.expected;
  }
  this.value = function (res) {
    return res.value;
  }
  this.command = function (cb) {
    var self = this;
    return this.api.execute(function (selector) {
      return document.querySelectorAll(selector).length;
    }, [selector], function (res) {
      cb.call(self, res);
    });
  }
}

This code was added to vuejs-templates by yyx990803 in 2016. So full credit goes to yyx990803.

Chris K
  • 10,844
  • 1
  • 29
  • 45
  • 1
    Any chance you tell us how to implement this as well? By that I mean setup nightwatch to take this custom assertion – Jamie Hutber Jan 30 '18 at 16:31
  • 2
    To add this: Create a directory called plugins or whatever. In your nightwatch.json file add that directory path to 'custom_assertions_path'. Add the code in a file called elementCount.js and add the file to your plugin directory and that should work. – Mike W Sep 11 '18 at 10:19
  • I've added a TypeScript version that does this without having to execute code in the browser. – Zymotik Feb 21 '19 at 17:36
8

Just to reassure you I do a similar thing when trying to grab all matching elements, ex:

    browser.elements("xpath","//ul[@name='timesList']/h6", function(result){
        els = result.value;
        var i = 0;
        els.forEach(function(el, j, elz){
            browser.elementIdText(el.ELEMENT, function(text) {
                dates[i] = text.value;
                i++;
            });
        });
    });
Madison Haynie
  • 408
  • 3
  • 12
  • 1
    Important to notice CSS selector is **browser.elements('css selector', ...)** but the XPath selector is **browser.elements('xpath', ...)**. I have been a while trying to work with 'xpath selector' and no... that's not the name selector. – kitimenpolku Jan 25 '17 at 10:58
7

Alternatively, if you want to be sure that n number of elements exist, you can use a combination of :nth-of-type/:nth-child selectors and nightwatch's expect.

For example, if you want to test if #foo has nine direct children:

function(browser) {
  browser
    .url('http://example.com')
    .expect.element('#foo > *:nth-child(9)').to.be.present;
  browser.end();
}

Or if #bar has three direct article children:

function(browser) {
  browser
    .url('http://example.com')
    .expect.element('#bar > article:nth-of-type(3)').to.be.present;
  browser.end();
}
zgreen
  • 2,043
  • 17
  • 17
  • It should be noted that this solution seems to only work if the target elements are 'cleanly' organized. For example I have a shopping cart of `div.items div.item` with three items in the cart (`div.item`). Unfortunately there is also a hidden `div.empty` for when there are no contents in the cart that is located as a sibling to the `div.item` elements. So in this scenario, when attempting to count the `div.item` elements, it is actually `:nth-of-type(2-4)` since the empty div element is the first child of the item element's parent. – jheep Sep 14 '18 at 14:43
2

I adapted Chris K's answer to support XPath expressions by using the built-in method this.api.elements:

exports.assertion = function elementCount(selector, count) {
  this.message = 'Testing if element <' + selector + '> has count: ' + count

  this.expected = count

  this.pass = function pass(val) {
    return val === this.expected
  }

  this.value = function value(res) {
    return res.value.length
  }

  this.command = function command(callback) {
    return this.api.elements(this.client.locateStrategy, selector, callback)
  }
}

For usage instructions and credits see his answer

Tom McKenzie
  • 663
  • 7
  • 18
2

You could use expect.elements(<selector>).count():

  browser.expect.elements('div').count.to.equal(10);
  browser.expect.elements('p').count.to.not.equal(1);
mrroot5
  • 1,254
  • 3
  • 21
  • 29
0

And if you like a bit of TypeScript, here is an assertion that will confirm the element count:

import { NightwatchCallbackResult, NightwatchAssertion, NightwatchAPI } from "nightwatch";

module.exports.assertion = function (selector: string, count: number, description?: string) {

    this.message = description || `Testing if element <${selector}> has count: ${count}`;
    this.expected = count;

    this.pass = (value: number) => value === this.expected;

    this.value = (result: number) => result;

    this.command = (callback: (result: number) => void): NightwatchAPI => {

        const self: NightwatchAssertion = this;

        return self.api.elements(this.client.locateStrategy, selector, (result: NightwatchCallbackResult) => {
            callback(result.value.length);
        });
    }

}

Use like this:

browser.assert.elementCount('body', 1, 'There is only one body element');
Zymotik
  • 3,378
  • 1
  • 28
  • 41