You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jul 29, 2024. It is now read-only.
Right now, if anyone clicks a link (or in some other way navigates without using browser.get), we have three major problems:
None of our bootstrapping happens
Users might not be able to update browser.rootEl, browser.ignoreSynchronization, or browser.ng12Hybrid at the correct time.
Selenium doesn't know there's a page load happening, and so sometimes lets commands run against a partially loaded page, or a page might unload while a selenium command is being executed (example).
This is an attempt to deal with problem (1).
Problem (2) is being mitigated by #3847, and possibly either #3858or #3859.
The idea for handling browser-initiated navigation is:
The crucial part of setup can be done in a unload event listener.
Bootstrapping can be be slightly delayed, as long as it happens before any actual commands are issued.
Use redundancy to cover edge cases.
Setup from an unload listener
Setup currently does 3 things:
Disables blocking proxy waiting so that we can interact with the browser more directly
Navigate to a dummy URL so we can tell once navigation has finished
Sets the "NG_DEFER_BOOTSTRAP!" label so that we can load mock modules
Of these steps, only the third is necessary for browser-initiated navigation. To do this, we just need to install an event listener for unload that adds the defer label to window.name:
We always install this listener, even if synchronization is disabled. This is a change from the previous behavior, where bootstrap would only get deferred if synchronization was enabled.
It will still be necessary to set the NG_DEFER_BOOTSTRAP!" label in browser.get, since the first page load won’t be preceded by an unload. Additionally, since window.name and unload events are both slightly unreliable, the redundancy is a good thing.
Bootstrapping
The bootstrapping process will be split out into a doBootstrap helper function. It will also change in the following ways:
It will manage a variable on the window object to ensure that the same page isn’t bootstrapped multiple times
It will add the unload listener described above
It will be invoked in several places, including periodically every 100ms or so (details later on).
doBootstrap(timeout=this.getPageTimeout,fromGetFn=false): wdpromise.Promise<void>{// If window.__PROTRACTOR_BOOTSTRAP_STATUS_ is undefined, we bootstrap in this call// If window.__PROTRACTOR_BOOTSTRAP_STATUS_ is 'started', another call is bootstrapping// already and we should wait for it to finish// If window.__PROTRACTOR_BOOTSTRAP_STATUS_ is 'complete', bootstrapping has been// completed by another callreturnthis.executeScriptWithDescription(function(){if(window.__PROTRACTOR_BOOTSTRAP_STATUS_===undefined){window.__PROTRACTOR_BOOTSTRAP_STATUS_='started';window.addEventListener('unload',function(){if(window.name.indexOf("NG_DEFER_BOOTSTRAP!")==-1){window.name="NG_DEFER_BOOTSTRAP!"+window.name;}});return'not started';}returnwindow.__PROTRACTOR_BOOTSTRAP_STATUS_;},'Protractor Bootstrap - get/set bootstrap status + add unload listener').then((bootstrapStatus: string)=>{if(bootstrapStatus!='not started'){// Bootstrapping is either already finished on this page or is currently being// managed by a previous call to doBootstrap. Either way, we should wait until// we're sure bootstrap is completereturnthis.driver.wait(()=>{returnthis.executeScriptWithDescription(function(){returnwindow.__PROTRACTOR_BOOTSTRAP_STATUS_;,},'Protractor Bootstrap - get status')).then((status: any)=>{returnstatus=='complete';},(err: IError)=>{if(err.code==13){// IE bug - ignore and try againreturnfalse;}else{throwerr;}});},timeout,'Wait for bootstrap for '+timeout+'ms');}else{// It's our job to bootstrap returnthis.waitForAngularEnabled().then((waitEnabled)=>{if(waitEnabled){
... // Plugins.onPageLoad
... // testForAngular.then((angularVersion: number)=>{// Load mock modules, but do not resume bootstrap yet.// Return a promise for the module names.});}}).then((moduleNames?: string[])=>{// Try to resume bootstrapreturnthis.executeScriptWithDescription(function(moduleNames){if(angular.resumeBootstrap){window.__TESTABILITY__NG1_APP_ROOT_INJECTOR__=angular.resumeBootstrap(arguments[0]);}else{window.name=window.name.replace(/^NG_DEFER_BOOTSTRAP!/,'');}},'Protractor Bootstrap - resume bootstrap',moduleNames||[]));}).then(()=>{if(fromGetFn){
... // Reset bpClient sync}}).then(()=>{returnthis.waitForAngularEnabled().then((waitEnabled)=>{if(waitEnabled){
... // Run plugins.onPageStable}});}).then(()=>{returnthis.executeScriptWithDescription(function(){window.__PROTRACTOR_BOOTSTRAP_STATUS_='complete';},'Protractor Bootstrap - set status as "complete"')});}});
We then invoke doBootstrap in three places:
At the start of browser.waitForAngular
This on its own ensured that the app is bootstrapped before any commands which require synchronization are run.
Periodically, every 100ms or so.
Because we are deferring bootstrap, we never want to get into a situation where the app spends a long time waiting for angular.resumeBootstrap, which could happen if a user is doing work which doesn't require browser.waitForAngular.
At the end of browser.get, as we currently do.
This is no longer necessary, and indeed won't help at all for browser-initiated navigation, but it doesn't hurt anything either, and reduces reliance on the periodic check.
Edge Case: Interrupted by navigation
As per #4052, we need to refactor bootstrapping to make it more tolerant of interruption (i.e. navigation occurring part way through bootstrapping).
Initially, if we get interrupted, we'll simply give up on the remaining bootstrap work. This is to avoid accidentally running bootstrap code extraneously. As mentioned in #4052 (comment), when we make the changes described in this issue, we will want to change that behavior so that:
If bootstrapping is interrupted, we will retrying bootstrapping from the beginning on the new page.
If waitForAngular is interrupted, we will restart from the beginning of waitForAngular, including retrying bootstrap.
Edge Case: Blocking Proxy
Pending the Blocking Proxy change described in #4052, this modified bootstrapping process should be compatible with Blocking Proxy.
However, it does go against the spirit of Blocking Proxy to add all this new work to waitForAngular. The point of Blocking Proxy is that this kind of work should be done automatically for you. Pending #4064 and angular/blocking-proxy#16, we should be able to add a angular_bootstrap_barrier.ts file to do bootstrapping before each command. We can execute this intentionally by doing a blank executeScript at the end of browser.get/periodically.
Preface
Right now, if anyone clicks a link (or in some other way navigates without using
browser.get), we have three major problems:browser.rootEl,browser.ignoreSynchronization, orbrowser.ng12Hybridat the correct time.This is an attempt to deal with problem (1).
Problem (2) is being mitigated by #3847, and possibly either #3858
or #3859.(3) will be mitigated by #4053
Solution
A
browser.getcall consists of three phases:The idea for handling browser-initiated navigation is:
Setup from an unload listener
Setup currently does 3 things:
"NG_DEFER_BOOTSTRAP!"label so that we can load mock modulesOf these steps, only the third is necessary for browser-initiated navigation. To do this, we just need to install an event listener for
unloadthat adds the defer label towindow.name:We always install this listener, even if synchronization is disabled. This is a change from the previous behavior, where bootstrap would only get deferred if synchronization was enabled.
It will still be necessary to set the
NG_DEFER_BOOTSTRAP!"label inbrowser.get, since the first page load won’t be preceded by an unload. Additionally, sincewindow.nameand unload events are both slightly unreliable, the redundancy is a good thing.Bootstrapping
The bootstrapping process will be split out into a
doBootstraphelper function. It will also change in the following ways:windowobject to ensure that the same page isn’t bootstrapped multiple timesbrowser.waitForAngularto make them interruptible #4052 for details on what that means. When interrupted, the restart process will be retried from the beginning.NG_DEFER_BOOTSTRAP!labels will be removed.Relative to the implementation in Protractor 5.1, the new bootstrapping process will be something like:
We then invoke
doBootstrapin three places:browser.waitForAngularangular.resumeBootstrap, which could happen if a user is doing work which doesn't requirebrowser.waitForAngular.browser.get, as we currently do.Edge Case: Interrupted by navigation
As per #4052, we need to refactor bootstrapping to make it more tolerant of interruption (i.e. navigation occurring part way through bootstrapping).
Initially, if we get interrupted, we'll simply give up on the remaining bootstrap work. This is to avoid accidentally running bootstrap code extraneously. As mentioned in #4052 (comment), when we make the changes described in this issue, we will want to change that behavior so that:
waitForAngularis interrupted, we will restart from the beginning ofwaitForAngular, including retrying bootstrap.Edge Case: Blocking Proxy
Pending the Blocking Proxy change described in #4052, this modified bootstrapping process should be compatible with Blocking Proxy.
However, it does go against the spirit of Blocking Proxy to add all this new work to
waitForAngular. The point of Blocking Proxy is that this kind of work should be done automatically for you. Pending #4064 and angular/blocking-proxy#16, we should be able to add aangular_bootstrap_barrier.tsfile to do bootstrapping before each command. We can execute this intentionally by doing a blankexecuteScriptat the end ofbrowser.get/periodically.