简体   繁体   中英

Selenium - Determine whether web page is finished loading in Angular 2+

I have a Selenium test suite that is running Selenium integration tests against a number of web applications, some that are written in Angular 2+, and some that are written in AngularJS.

We use a custom ExpectedCondition with WebDriverWait that we use to make test cases wait until AngularJS apps have finished loading, in order to avoid waiting an arbitrary amount of time:

private static ExpectedCondition<Boolean> angularIsFinished() {
    return new ExpectedCondition<Boolean>() {
        public Boolean apply(final WebDriver driver) {
            Object result = null;

            while(result == null || result.toString().equals("undefined")) {
                result = ((JavascriptExecutor)driver).executeScript("return typeof angular;");

                try {
                    Thread.sleep(200L);
                } catch (final InterruptedException ex) {
                    logger.error("Error while trying to sleep", ex);
                }
            }

            final String script = "  var el = document.querySelector(\"body\");\n" +
                    "  var callback = arguments[arguments.length - 1];\n" +
                    "  angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);";
            ((JavascriptExecutor)driver).executeAsyncScript(script);
            return true;
        }

        public String toString() {
            return "Wait for AngularJS";
        }
    };
}

However, return typeof angular; will always return undefined for an Angular 2+ app. Is there a similar way to AngularJS's notifyWhenNoOutstandingRequests that you can use to determine when an Angular 2+ app has finished loading?

This question mentions using NgZone as a possible solution, but how would you get a handle on that via a script executed via JavascriptExecutor ?

Looking at the Protractor code I have come up with two possible solutions:

First of all we have an option where we find a list of testability's, then add a callback to all of them, and then wait for one of them to flag the site as testable (This does mean that your script will continue after any one testability has become testable, it will not wait for all of them to become testable).

private static ExpectedCondition angular2IsTestable() {
    return (ExpectedCondition<Boolean>) driver -> {
        JavascriptExecutor jsexec = ((JavascriptExecutor) driver);
        Object result = jsexec.executeAsyncScript("window.seleniumCallback = arguments[arguments.length -1];\n" +
                        "if (window.getAllAngularTestabilities()) {\n" +
                        "    window.getAllAngularTestabilities().forEach(function (testability) {\n" +
                        "            testability.whenStable(window.seleniumCallback(true))\n" +
                        "        }\n" +
                        "    );\n" +
                        "} else {\n" +
                        "    window.seleniumCallback(false)\n" +
                        "}"
        );

        return Boolean.parseBoolean(result.toString());
    };
}

The second option is to specifically check an angular root elements testability state:

private static ExpectedCondition angular2ElementIsTestable(final WebElement element) {
    return (ExpectedCondition<Boolean>) driver -> {
        JavascriptExecutor jsexec = ((JavascriptExecutor) driver);
        Object result = jsexec.executeAsyncScript(
                "window.seleniumCallback = arguments[arguments.length -1];\n" +
                        "var element = arguments[0];\n" +
                        "if (window.getAngularTestability && window.getAngularTestability(element)) {\n" +
                        "    window.getAngularTestability(element).whenStable(window.seleniumCallback(true));\n" +
                        "} else {\n" +
                        "    window.seleniumCallback(false)\n" +
                        "}"
        , element);

        return Boolean.parseBoolean(result.toString());
    };
}

The second option is more targeted and therefore more reliable if you want to test a specific area of the site.

A third option would be to write something a bit more complicated that tracks the state of all testability's and then only fires a true callback when all of them have become true. I don't have an implementation for this yet.

You can check it by calling eg document.querySelector('app-root') ? or arbitrary component selector...

Or what about calling document.readyState ? It should have result 'complete' after fully loaded wep page and it doesn't matter if web page is based on angular.

Thanks to @Ardesco's answer , I was able to do something similar to what Protractor does, using the window.getAllAngularTestabilities function. Here is the script that I run to determine if the Angular 2+ page loads:

var testability = window.getAllAngularTestabilities()[0];
var callback = arguments[arguments.length - 1];
testability.whenStable(callback);

And here is what the complete ExpectedCondition looks like that works for both AngularJS and Angular 2+:

private static ExpectedCondition<Boolean> angularIsFinished() {
    return new ExpectedCondition<Boolean>() {
        public Boolean apply(final WebDriver driver) {
            Object result = null;

            boolean isAngular2Plus = false;

            while(result == null || result.toString().equals("undefined")) {
                result = ((JavascriptExecutor)driver).executeScript("return typeof angular;");
                if (result == null || result.toString().equals("undefined")) {
                    result = ((JavascriptExecutor)driver).executeScript("return typeof window.getAngularTestability;");
                    if (result != null && !result.toString().equals("undefined")) {
                        isAngular2Plus = true;
                    }
                }

                try {
                    Thread.sleep(200L);
                } catch (final InterruptedException ex) {
                    logger.error("Error while trying to sleep", ex);
                }
            }

            final String script;
            if (isAngular2Plus) {
                script ="  var testability = window.getAllAngularTestabilities()[0];\n" +
                        "  var callback = arguments[arguments.length - 1];\n" +
                        "  testability.whenStable(callback);";
            } else {
                script ="  var el = document.querySelector(\"body\");\n" +
                        "  var callback = arguments[arguments.length - 1];\n" +
                        "  angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);";
            }
            ((JavascriptExecutor) driver).executeAsyncScript(script);
            return true;
        }

        public String toString() {
            return "Wait for AngularJS";
        }
    };
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM