3

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

austinrulezd00d
  • 185
  • 1
  • 12

2 Answers2

1

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)

    }
Bharath Kumar S
  • 1,332
  • 2
  • 8
  • 25
  • Were you using Spectron or just webdriverio. I can never get this to work with Spectron. – cham Jan 22 '19 at 02:40
  • Oooh, it only works with xpath! You have made my day, if not my week. Thank you very much!!!!!! – cham Jan 28 '19 at 22:43
0

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!

iamdanchiv
  • 3,828
  • 4
  • 32
  • 40
  • I realize the ID isn't quite right, its something im going to fix eventually, but whats interesting is that isn't an enforced rule, its more of a suggestion, but for some DOM functions the behavior becomes undefined. Also while your answer makes sense, it does not fix my problem, because the selector is not the issue. The issue is that getText is undefined. see `EXCEPTION TypeError: Cannot read property 'getText' of undefined` – austinrulezd00d Dec 06 '17 at 19:06
  • @austinrulezd00d I would start by locking the test-case in debug-mode using `.debug()` and looking at what `fooElements` is returning after initialization: `const fooElements = app.client.$$(foo);`. As I've demonstrated in the answer above, it works with a regular setup, but unfortunately I have my hands tied regarding TypeScript where I am as newbie as they come. – iamdanchiv Dec 14 '17 at 15:27