microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
65.79k stars 3.58k forks source link

Wait for Angular zone to be stable #8433

Closed manuman94 closed 2 years ago

manuman94 commented 3 years ago

See here: https://playwright.dev/docs/protractor#polyfilling-waitforangular

Edit: From maintainers


Introduction

Hi! I'm starting with Playwright and did my first projects.

I come from Protractor, that made me start easily to the Web Frontend E2E testing, but let me tell you that Playwright is fulfilling our needs incredibly and looks so promising.

We are migrating some projects we had working with Protractor to Playwright and we found it easy except for one thing. Protractor worked very well with Angular applications since it had the waitForAngular function that waits for the Angular zone (from zonejs) to be stable. That was so useful to avoid manual waits of network requests or animations.

The problem

We have solved it successfully by using the waitForFunction method and tests are working the same as with Protractor. But they have become less descriptive and they include programming logic that we would like to have as short/easy to read as it was.

Example

In a Protractor project we had something like:

expect(page.getServiceListItems().count()).toBe(0);

As Protractor waits for the Angular Zone to be stable, the initial page load and further ajax asynchronous calls are made before this verification is done, so the ServiceList is populated when we verify that the count is not 0 (data to populate ServiceList comes from an ajax call).

In the Playwright project we have solved like this:

wait(() => {
    return new Promise(async (resolve, reject) => {
        const service_list_items = await page.getServiceListItems();
        if ( service_list_items.count() > 0 ) {
            resolve(true);
            return;
        }
        reject();
    });
}, TIMEOUT_MILLIS, DEBOUNCE_MILLIS, '0 apps were listed expecting more than 0')

The wait function is something similar to the Playwright's page.waitForFunction, it waits until the promise is resolved.

The fact is that with Protractor we had it resolved in one line and with Playwright we have to create a function to make the verification.

Feature Request

We would like to have an optional "Angular mode" that waits for the Angular zone to be stable before every webdriver action so that ensures that HTTP requests/animations/whatever thing blocking Angular zone is not happening at this time.

Links

waitForAngular at Protractor docs

We must say that this is NOT a blocking thing as we found workarounds to solve it. But we think that could help to the readability/manteinability of the Playwright projects and would make lots of people working with Protractor to make their lives easier if they are thinking of migrating to Playwright.

Thank you so much for your support and for such a nice E2E framework.

Greetings.

jhyot commented 3 years ago

I don't really have a stake is this, and wouldn't wish to deny anybody a feature they want :-) , but I wanted to mention that Protractor is being deprecated by Google, and in the deprecation rationale (https://github.com/angular/protractor/issues/5502) they explicitly mention waitForAngular:

Although waitForAngular is useful, it strongly couples the testing platform to the Angular framework. Some teams at Google have found that solutions that do not require knowledge of Angular can perform the tests equally well by using a robust retry strategy. We believe this is how e2e testing should be done going forward, and projects in Google are already converging towards a testing platform that is WebDriver compliant and framework agnostic. This allows our web test team to maintain a single solution for all web applications.

So in light of this it might be questionable to insert something into a 3rd-party-framework, which itself is rather going away in Angular.

We are using Playwright with Angular, and waitForAngular didn't really work that well in edge cases in our previous Selenium-based tests anyway, and now our Playwright tests seem to be quite stable even without waitForAngular, thanks to Playwright's robust auto-waiting and -retrying mechanisms.

manuman94 commented 3 years ago

Hi @jhyot!

Thank you for your answer! I understand that is better to be framework agnostic but the fact is that the feature we are proposing would not be a core change, but a functionality that would not affect the other.

As I mentioned in the request, our projects are working solid with other strategies, but the fact is that waitForAngular was so useful to preserve the test readability and manteinability.

The example I left is so simple, but it grows a lot when more than one wait is done.

And yes! The reason for us to find other e2e framework was that Protractor deprecation rationale hehe

Maybe the solution would not be to implement waitForAngular itself but developing some wait utils to make the utils shorter and common. I will be thinking about this because I believe maybe this is the way.

Again, thank you for your response! Greetings.

manuman94 commented 3 years ago

Hi! I'm implementing a way to wait to the Angular zone to be stable by injecting javascript scripts to the browser. I found a way to achieve this that is working with Angular 2+. I'm trying to implement the Angularjs version and when I get both working I will post my solution here.

It is a solution outside Playwright, but it may help other people with my issue. The fact is that projects are running flawlessly with my custom waitForAngular() function and lots of waits have been avoided.

Thank you, greetings.

aslushnikov commented 3 years ago

waitForAngular polyfill for Playwright:

NOTE Make sure you have protractor installed in your package.json

Polyfill function:

async function waitForAngular(page) {
 ​const clientSideScripts = require('protractor/built/clientsidescripts.js');

 ​async function executeScriptAsync(page, script, ...scriptArgs) {
   ​await page.evaluate(`
     ​new Promise((resolve, reject) => {
       ​const callback = (errMessage) => {
         ​if (errMessage)
           ​reject(new Error(errMessage));
         ​else
           ​resolve();
       ​};
       ​(function() {${script}}).apply(null, [...${JSON.stringify(scriptArgs)}, callback]);
     ​})
   ​`);
 ​}

 ​await executeScriptAsync(page, clientSideScripts.waitForAngular, '');
}

Polyfill usage:

const page = await context.newPage();
await page.goto('https://example.org');
await waitForAngular(page);
manuman94 commented 3 years ago

Hi @aslushnikov,

Wow, that is awesome! Nice approach.

I have a question about it: since Protractor is not going to be mantained anymore in a not so far future, woudn't be better to import only the clientsidescripts.js file instead of depending on the whole Protractor module?

On the other hand, it would be awesome if Playwright provide us a way to "hook" an asynchronous function before any webdriver action (such as obtaining ElementHandles, clicking, typing, etc.) to implement our custom waits hooked instead of using "await waitForAngular(page)" before all actions.

Thank you!

manuman94 commented 3 years ago

Well, I tried your approach and works flawlessly. Thank you so much @aslushnikov, you have help us a lot.

I did what I commented in my last answer, I copied only the "clientsidescripts.js" avoiding to have Protractor in our package.json. I don't believe this is bad because the last change of this script in the official Protractor repo is from 2018 and it feels like they are not going to update this file anymore.

I made a custom hook before the webdriver actions so I can call automatically the "waitForAngular()" function when the project has "waitForAngular" enabled (using a configuration file). Now our tests are similar to what we had previously when we were using Protractor and it takes less lines improving readability/maintainability.

In the projects in which we don't need waiting for angular we have the waitForAngular option disabled so we rely only on the Playwright smart waits.

I think this would be positive to the people that come from Protractor projects since is fast to migrate and it has sense to wait to the Angular zone since it works well.

Thank you for all your attention, greetings.

manuman94 commented 3 years ago

One side note: when copied and pasted from the code you posted at your comment, some weird characters appeared. If someone try to copy/paste the above waitForAngular code ensure that the tab characters are the only ones before each line, because I got some invisible characters that made the script break.

aslushnikov commented 3 years ago

I have a question about it: since Protractor is not going to be mantained anymore in a not so far future, woudn't be better to import only the clientsidescripts.js file instead of depending on the whole Protractor module?

@manuman94 Yes, copying is fine as well! i had it copied in my experiments, but thought that using protractor from NPM is easier.

On the other hand, it would be awesome if Playwright provide us a way to "hook" an asynchronous function before any webdriver action (such as obtaining ElementHandles, clicking, typing, etc.) to implement our custom waits hooked instead of using "await waitForAngular(page)" before all actions.

We discussed this on the meeting, but we're not convinced yet this is the best way forward. We'll keep thinking about it!

I made a custom hook before the webdriver actions so I can call automatically the "waitForAngular()" function when the project has "waitForAngular" enabled (using a configuration file).

Interesting! How did you implement this hook? Did you override the methods on frame class?

One side note: when copied and pasted from the code you posted at your comment, some weird characters appeared.

Hmm, I double-checked and don't see any!

MPonraj commented 3 years ago

@aslushnikov, The expectation for the request is migrating the scripts from Protractor to Playwright. In Cypress, there is the documentation for migration https://docs.cypress.io/guides/migrating-to-cypress/protractor#Introduction. So if possible kindly provide the documentation for migration.

vsravuri commented 3 years ago

The default behavior of Protractor is to wait for all outstanding $http and $timeout calls which is basically waitForAngularEnabled = true. My application has session timeout of 15 minutes inactivity. Test never ran successfully because it finds the $timeout value 15 minutes and waits for the call to complete. Protractor team never fixed this issue https://github.com/angular/protractor/issues/4290. Before every test case i need to explicitly set waitForAngularEnabled = false and treat my application as Non-Angular.

manuman94 commented 3 years ago

Hi all!

@aslushnikov I implemented the wait system based on our abstraction layer. We thought that Protractor and other libraries oftenly get unmaintained or deprecated (or simply have lacks based on our needs) so we implemented an abstraction layer that made us easy migrate projects to other libraries without having to change the actual tests, only reimplement the abstraction layer.

In this case, we implemented that hook at the abstraction layer. If you have any tips on how to extend Frame class to implement the custom waits (as we don't know how to tell Playwright to use our custom Frame class) please tell me and we will discuss if it's better than our current implementation.

@MPonraj The aim of this issue was to think about a wait mechanism to work with Angular applications, not how to migrate from Protractor to Playwright. Most of the driver actions are named similar and the documentation has a good coverage. We found it easy to migrate all Protractor methods to Playwright (click, isDisplayed -> isVisible, type, etc.). Althought is related, I would treat the request of that guide in another issue as it is not the same proposal.

@vsravuri Yes, you're right! The angular zone is blocked everytime the application uses $http and $timeout calls. However it is not a good practice to implement long term timeouts inside the angular zone. Most of the time, the timeout calls do not change the current state of the application, so trigger a change tick is unnecessary. In this cases the good practice is to use runOutsideAngular and trigger the change when the timeout has been resolved.

Here is a post explaining this topic: https://indepth.dev/posts/1434/running-event-listeners-outside-of-the-ngzone

Thank you all for being part of this. I'm glad we are not the only ones that come from Protractor!

Grakel commented 2 years ago

One side note: when copied and pasted from the code you posted at your comment, some weird characters appeared.

Hmm, I double-checked and don't see any!

Hey everyone, I was checking this thread and more specifically the function @aslushnikov posted, and as @manuman94 said, I also see some weird character at the start of every line except the first and the latest.

Code on SublimeText

I've been reading about it, and it seems it is a special character: https://en.wikipedia.org/wiki/Zero-width_space I removed the characters and it works perfectly.

Regards

mxschmitt commented 2 years ago

We added it to the docs, see here: https://playwright.dev/docs/protractor#polyfilling-waitforangular

raja-piq commented 5 months ago

Well, I tried your approach and works flawlessly. Thank you so much @aslushnikov, you have help us a lot.

I did what I commented in my last answer, I copied only the "clientsidescripts.js" avoiding to have Protractor in our package.json. I don't believe this is bad because the last change of this script in the official Protractor repo is from 2018 and it feels like they are not going to update this file anymore.

I made a custom hook before the webdriver actions so I can call automatically the "waitForAngular()" function when the project has "waitForAngular" enabled (using a configuration file). Now our tests are similar to what we had previously when we were using Protractor and it takes less lines improving readability/maintainability.

In the projects in which we don't need waiting for angular we have the waitForAngular option disabled so we rely only on the Playwright smart waits.

I think this would be positive to the people that come from Protractor projects since is fast to migrate and it has sense to wait to the Angular zone since it works well.

Thank you for all your attention, greetings.

@manuman94 Can yo help me with how to use this waitForAngular() polyfill in Playwright. Like where should this function be placed and how it can be called.