Spectron/WebdriverIO nth child using $$[n] instead of selector

3.1k Views Asked by At

I am trying to use Spectron to test my Electron application. The documentation says when trying to find the nth child, you can use an nth child selector, or get all children that match selector using $$ and then use index operator ie $$ ("foo")[0] gets first foo. DOCS

With that knowledge, i expect the following code to output: BAR I can't get this to work, i try the following:

const foo = ".foo";
const fooElements = app.client.$$ (foo);
console.log ("FOOELEMENTS", await TP (app.client.getHTML (foo)));
console.log ("BAR", fooElements[0].getText (".bar"));

And get the following output:

console.log components\pages\analysis\__test__\Analysis.spectron.ts:44
    FOOELEMENTS [ '<div class="foo"><div class="bar">BAR</div><div class="baz">BAZ</div></div>',
    '<div class="foo"><div class="bar">BAR2</div><div class="baz">BAZ2</div></div>',
    '<div class="foo"><div class="bar">BAR3</div><div class="baz">BAZ3</div></div>'
    '<div class="foo"><div class="bar">BAR4</div><div class="baz">BAZ4</div></div>' ]

console.log components\pages\analysis\__test__\Analysis.spectron.ts:50
    EXCEPTION TypeError: Cannot read property 'getText' of undefined
        at components\pages\analysis\__test__\Analysis.spectron.ts:45:44
        at Generator.next (<anonymous>)
        at fulfilled (components\pages\analysis\__test__\Analysis.spectron.ts:4:58)
        at <anonymous>
        at process._tickDomainCallback (internal/process/next_tick.js:228:7)

As you can see, the output HTML indeed has several .foo divs, but when i try to access the first one, it says fooElements[0] is undefined

sideNote (which should not be relevant): TP is an alias for a function i wrote called toPromise which lets me await the webdriver promises, because TypeScript is confused by the way they are implemented:

export async function toPromise<T> (client: Webdriver.Client<T>)
{
    return client as any as Promise<T>;
}

// Promise
    interface Client<T> {
        finally(callback: (...args: any[]) => void): Client<T>;

        then<P>(
            onFulfilled?: (value: T) => P | Client<P>,
            onRejected?: (error: any) => P | Client<P>
        ): Client<P>;

        catch<P>(
            onRejected?: (error: any) => P | Client<P>
        ): Client<P>;

        inspect(): {
            state: "fulfilled" | "rejected" | "pending";
            value?: T;
            reason?: any;
        };
    }

Any idea what i am doing wrong? Or a suggested alternative? I'd prefer to avoid nth-child selectors if possible.

EDIT: changed to class

2

There are 2 best solutions below

2
On

First off, you're example is intriguing. I don't know how you got that piece of <html> to be ran. The id value must be unique:

The id attribute specifies a unique id for an HTML element (the value must be unique within the HTML document).

Your code should work after a small change, just remove the #bar selector. You were passing the selector to the ELEMENT object, but it already contains that information via the value of the selector key.

let locator = 'span[connectqa-device="installed"]'

browser.debug()

Output (blocked test in debug-mode):

> let elems = $$(locator)
[18:10:22]  COMMAND     POST     "/wd/hub/session/775b024e-0b6a-4a60-a5b2-26d4df961d0a/elements"
[18:10:22]  DATA                {"using":"css selector","value":"span[connectqa-device=\"installed\"]"}
[18:10:22]  RESULT              [{"element-6066-11e4-a52e-4f735466cecf":"c6719646-30da-43ff-9b17-40c074b4988a"},{"element-6066-11e4-a52e-4f735466cecf":"d5f8acf2-8f4f-4554-9fe6-7f863555f5b5"},{"element-6066-11e4-a52e-4f735466cecf":"53ff9b0a-2a88-4b13-9e54-9c54411c03c5"}]

> elems[0]
{ ELEMENT: 'c6719646-30da-43ff-9b17-40c074b4988a',
  'element-6066-11e4-a52e-4f735466cecf': 'c6719646-30da-43ff-9b17-40c074b4988a',
  selector: 'span[connectqa-device="installed"]',
  value: { ELEMENT: 'c6719646-30da-43ff-9b17-40c074b4988a' },
  index: 0 }
>
> elems[0].selector
'span[connectqa-device="installed"]'
>
> elems[0].getText()
[18:10:54]  COMMAND     GET      "/wd/hub/session/775b024e-0b6a-4a60-a5b2-26d4df961d0a/element/c6719646-30da-43ff-9b17-40c074b4988a/text"
[18:10:54]  DATA                {}
[18:10:54]  RESULT              "Installed"
'Installed'

Alternatively, you could have done this: let retText2 = browser.getText('span[connectqa-device="installed"]') > let retText2 = browser.getText(elems[0].selector). The second example of course is only for didactic purposes, as you'd probably never want to to it that way.

Hope it helps. Cheers!

2
On

Actually webdriver window index and elements refer [1] to be the first element.

This worked fine for me.

var button = ('//*[contains(@class,"popper")]')[1];

return this.app.client.click(button);

Example:

class Clix {

    constructor() {

        this.clix_search = '(//input[contains(@class,"clix-search")])[1]';

    }

    clix_input_search(app) {
        return help.setValue(app, this.clix_search, "pack");
    }

}

In helpers class

setValue(app, element, text) {

        return app.client.waitForVisible(element, 60000)
            .waitForEnabled(element, 60000)
            .clearElement(element)
            .setValue(element, text)
            .getValue(element)
            .should.eventually.equal(text)

    }