vaadin / testbench

Vaadin TestBench is a tool for automated user interface testing of Vaadin applications.
https://vaadin.com/testbench
Other
20 stars 22 forks source link

waitForVaadin() fails when switching between multiple UIs #869

Open curo-automation opened 7 years ago

curo-automation commented 7 years ago

Vaadin support suggested that we file following issue as a bug.

We use selenium and phantomjs (v2.1.1) alongside testbench.

After navigating to a page, we call waitForVaadin() via the WebDriver in order to wait for Vaadin initialization to complete.

After this, we attempt to find an element like so:

$(TextFieldElement.class).first();

but receive this error:

Vaadin could not find elements with the selector (//com.vaadin.ui.TextField)[0] For documentation on this error, please visit: http://seleniumhq.org/exceptions/no_such_element.html Caused by: org.openqa.selenium.WebDriverException: {"errorMessage":"undefined is not a function (evaluating 'clients[client].getElementsByPath(arguments[0])')" ...}

We receive this error after navigating between UIs.

After close inspection of the "undefined is not a function" error message and TestBenchCommandExecutor.waitForVaadin() method we constructed the following experimental wait method:

private void waitForVaadinInit() {
    StringBuilder commandBuilder = new StringBuilder(500);
    commandBuilder.append("if (window.vaadin == null) { return 4; }");
    commandBuilder.append("var clients = window.vaadin.clients;");
    commandBuilder.append("if (!clients) { return 3; }");
    commandBuilder.append("for (var key in clients) {");
    commandBuilder.append(" var client = clients[key];");
    commandBuilder.append(" if (client.getElementsByPath == undefined) { return 2; }");
    commandBuilder.append(" else if (client.isActive()) { return 1; } }");
    commandBuilder.append("return 0;");
    String command = commandBuilder.toString();
    long startTime = System.currentTimeMillis();
    long maxTime = startTime + 20000;
    JavascriptExecutor js = (JavascriptExecutor) driver;
    long errorCode = -1;
    while (System.currentTimeMillis() < maxTime && errorCode != 0) {
        errorCode = (Long) js.executeScript(command);
    }
    if (errorCode != 0) {
        throw new CurocompAutomationException("Timeout waiting for Vaadin init.");
    }
}

We find that calling this function will properly wait for Vaadin to initialise. We accumulated error codes from cases where the javascript code was retried, and found that it failed with error codes 4, and 2, corresponding to window.vaadin == null and client.getElementsByPath == undefined respectively.

We noticed that TestBenchCommandExecuter.waitForVaadin() does not check for case 2, and will exit early without retrying if it encounters case 4. Whereas the above workaround fixes the issue for us by retrying on these cases.

Thanks in advance!

Artur- commented 7 years ago

Hi,

Do you happen to have some kind of test to share, where you encounter this problem? Does it happen when you navigate by clicking a link, opening a new URL or some other means?

When opening a page using the get command, then TestBench should wait for the DOMContentLoaded event before continuing. After this, a waitForVaadin is automatically triggered so after doing driver.get("someUrl") you should always be able to directly do e.g. $(TextFieldElement.class).first();

When the DomContentLoaded event occurs, window.vaadin should always be defined unless the bootstrap JavaScript has failed to load. If we would make TestBench wait when window.vaadin is not defined, we would wait forever if you visit a page where there is no Vaadin app. I don't see when error code "4" would happen so please enlighten me.

curo-automation commented 7 years ago

Hi! Thanks so much for the speedy response.

As to "when does it happen", I should have mentioned this is an intermittent failure.

We have around 300 tests running on CI and every other build we are hit with either situation 2 or 4 above in one of our tests. (Which one seems to be random).

This happens when opening a new URL like so:

driver.navigate().to(url);

and also after clicking links/buttons.

Should we use driver.get(url) instead of driver.navigate().to(url);?

About a test I can share with you, I've shared this thread with our development lead and we'll see what we can do. I'll get back to you on that.

Artur- commented 7 years ago

driver.navigate().to(url) is the same as driver.get(url), it's just another way to write it.

I tried running a test which navigates between UIs by either clicking on a link or by using driver.get() but I see no problems so there must be something else involved here.