angular / protractor

E2E test framework for Angular apps
http://www.protractortest.org
MIT License
8.75k stars 2.31k forks source link

Lazy loading AngularJS library never invokes resumeBootstrap #4724

Open jbedard opened 6 years ago

jbedard commented 6 years ago

While in the transition from AngularJS to Angular I've started lazy-loading the legacy AngularJS app only when a user navigates to a legacy page. Essentially exactly what is described at https://blog.nrwl.io/using-ngupgrade-like-a-pro-lazy-loading-angularjs-applications-469819f5c86.

Note that the lazy-loaded bundle includes ng-upgrade, AngularJS and the AngularJS app, this bundle is lazy loaded by the Angular router when you navigate to a legacy page.

The issue is that on page load protractor does not detect AngularJS (which is correct because it hasn't loaded yet), so it doesn't inject the mockModules + resumeBootstrap. However the defer label is inserted. This means the bootstrapping is deferred, but nothing ever resumes it. This causes the navigation to an AngularJS page to defer and never finish the bootstrapping, normally getting stuck and timing out on a blank page.

Workaround I've essentially copied the injecting the mockModules + resumeBootstrap and invoke it manually whenever navigating to an AngularJS route, if not done already.

Fix Maybe make injecting the mockModules + resumeBootstrap a public method on browser that can be invoked manually? Or somehow detect if/when AngularJS is lazy loaded?

jbedard commented 6 years ago

For reference, this is my workaround...

This method must be invoked essentially anytime that AngularJS can potentially be loaded such as when clicking links to AngularJS pages in your app. I assume there is a bit of a race condition where this method (or at least the NG_DEFER_BOOTSTRAP part) must be invoked before the AngularJS app actually bootstraps. If the the click was embedded into this method that could be worked around.

//Workaround for https://github.com/angular/protractor/issues/4724
function async loadAngularJS() {
    //Abort if `resumeBootstrap` has already occured
    if (await browser.executeScript(`return "__TESTABILITY__NG1_APP_ROOT_INJECTOR__" in window;`)) {
        return;
    }

    //Might have to re-insert the "NG_DEFER_BOOTSTRAP!" if the name has been changed since protractor loaded the page
    if (!await browser.executeScript("window.name.includes('NG_DEFER_BOOTSTRAP!')")) {
        await browser.executeScript("window.name = 'NG_DEFER_BOOTSTRAP!' + name");
    }

    //Wait for the AngularJS bundle to download and initialize
    await browser.wait(ExpectedConditions.presenceOf($("a1-app")), 5000, "AngularJS a1-app");

    // Run the protractor pre-bootstrap logic and resumeBootstrap
    // Based on https://github.com/angular/protractor/blob/5.3.0/lib/browser.ts#L950-L969
    {
        let moduleNames = [];
        for (const {name, script, args} of browser.mockModules_) {
            moduleNames.push(name);
            await browser.executeScriptWithDescription(script, "add mock module " + name, ...args);
        }

        await browser.executeScriptWithDescription(
            //TODO: must manually assign __TESTABILITY__NG1_APP_ROOT_INJECTOR__ (https://github.com/angular/angular/issues/22723)
            `window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__ = angular.resumeBootstrap(arguments[0]) || angular.element("a1-app").injector();`,
            "resume bootstrap",
            moduleNames
        );
    }

    //Wait for the initial AngularJS page to finish loading
    await browser.waitForAngularEnabled(true);
    await browser.waitForAngular();
}

There's also a workaround for https://github.com/angular/angular/issues/22723 in there.

evilaliv3 commented 4 years ago

This seems to still impact safari 12 and 13 (current versions). Did anyone identify a solution or a mock that can be configured inside the protractor configuration?