Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playwright #18927

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Conversation

AdrienClairembault
Copy link
Contributor

@AdrienClairembault AdrienClairembault commented Feb 5, 2025

Description

Setup playwright and migrate two tests cypress tests files.

It does seems faster locally, the 2 migrated tests take ~15 seconds with 6 threads on my machine (vs 28 seconds for their cypress equivalent on the CI).

On the CI, the improvement seems less impressive (unsure why, it even seems slower).
Maybe there isn't enough tests to properly compare.
Anyway, I've saved a trace of its execution to investigate it next week (the work can still be reviewed).

Regarding the tests themselves, they feel nicer to write.
Cypress was great at the start because it is easy to get started, however as you go on you face many issues when you try to do more complicated things due to its design choices (thenable everywhere that is not a real promise, command pattern, ...).

For that, playwright feels much better IMO.
It requires a bit more time to understand it at the start but after that it get quite smooth due to its design which is way more predictable.

@AdrienClairembault AdrienClairembault self-assigned this Feb 5, 2025
@AdrienClairembault AdrienClairembault force-pushed the playwright branch 8 times, most recently from 5c5bc1a to 48aafcd Compare February 7, 2025 15:54
@AdrienClairembault
Copy link
Contributor Author

Don't worry about sonar cloud failure, it report a new OS command execution but it should be safe given its just a call to glpi's console:

image

@trasher
Copy link
Contributor

trasher commented Feb 7, 2025

Don't worry about sonar cloud failure, it report a new OS command execution but it should be safe given its just a call to glpi's console:
[...]

Iv'e marked it as safe

@cconard96
Copy link
Contributor

Not a fan of using TypeScript. That's both a personal thing and also a worry that it makes writing E2E tests less desirable for the developers that already don't work with JavaScript a lot. Also, working with TypeScript is incredibly frustrating in PHPStorm. There have been too many times I've Ctrl + Click a JS function related to a library to try and see the code for it, only to be directed to the useless .d.ts file for it. Same issue started happening when TS files were being added to the Cypress tests. I don't see the benefit of it at all, and only downsides. I voiced my opinion about it before, but that was when we only had a .d.ts file added for Cypress commands all to fix VSCode behavior (which I also don't like but I know others are using it and it was an OK solution to fix autocomplete).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be handled during the installation of GLPI like we handled with Cypress?
We should have a PHP script that handles test environment data and then include it during the installation when the env is testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That actually what we do not want to have anymore.
It isn't convenient because you can't run the test locally unless you are in the right environnement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an additional note, maybe we could force the environnement to switch to testing when running e2e tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to have a separate command/script/whatever to be sure that we do not have to embed testing data in the base GLPI installation logic. Also, having this logic in the tools directory is a good option to be sure that it will not be embed in the release archive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an additional note, maybe we could force the environnement to switch to testing when running e2e tests.

This is what we do for the web test suite, but the impossibility to use a DB transaction sometimes cause issues requires to have a specific cleaning logic executed after the tests are executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as discussed together if we want to force a specific env then it should be a 3rd database reserved for e2e tests like symfony does.
But this isn't the priority right now I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That actually what we do not want to have anymore. It isn't convenient because you can't run the test locally unless you are in the right environnement.

No reason not to use the docker containers provided and the convenient run_tests script or push to a PR for the CI to run them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there are still something like 50% of the GLPI regular devs that are not using docker at all and want to be able to run the tests locally.

Copy link
Contributor Author

@AdrienClairembault AdrienClairembault Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script is not perfect and lack configuration options to run a single test for example.

Also the cost of spinning up the containers make it painful for repeatedly running a single test when doing something like test driven development.
I think it might also complicate things like xdebug integration, which might be critical in some cases.

Overall, we are mostly speaking from experience here.
Indeed, we've had feedback from others members of the team that they were often confused when trying to run the tests for the first time (often to debug one of their pull request where a single test failed).

Some members even defaulted to using the testing environnement by default for their work to avoid dealing with the setup issues, so clearly the previous solution was not a good fit for everyone.

With the new behavior, it should be "plug and play" no matter what you are using (local env, docker script, ...) and it avoid putting environnement specific code into the installation script which is not a great pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script has support for specifying scopes, but I know it wasn't handled for all the test types.

I added an interactive mode a while ago to prevent the need to keep recreating the containers for every test run.

XDebug is indeed annoying, but I think it was explicitly disabled in the docker image. Maybe it could be re-enabled and have the XDebug env variable optionally set in the test runner to avoid always having that performance hit.

Having the test commands that existed before the local docker environment in the composer file added to the confusion.

From personal experience, running tests locally without docker is not reliable at all. Several times I've had tests fail that way when there was nothing wrong with the code, but instead the tests failed because of some extra data/configuration I had in my developer instance. The server-side tests expect to be run in a clean environment which isn't possible if you just run them directly on your local GLPI. Of course JS and Lint tests at least are fine to run outside Docker.

The only time I run tests directly are for specific E2E tests that I know don't rely on global configuration stuff. I just manually added the E2E entity and user to my development DB. This isn't required for me though. I just think being able to run Cypress not in headless mode can be useful for understanding issues in certain cases. I'm definitely not an expert, but couldn't it also be configured to launch the GUI from the Docker container?

tests/playwright/utils/CsrfManager.ts Show resolved Hide resolved

test('can remove tiles', async ({ page }) => {
// Make sure the tile exist before trying to delete it
await expect(config_page.getTile("Request a service")).toBeVisible();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan that we trade command queuing for "await hell". At the very least, we should be careful to not use await for every single line of playwright test code. Should use Promise.all when possible to run things together at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO (and most online opinions from what I can tell) await is correct modern way of using javacript while cypress command queue is an un-intuitive nightmare that lead to callback hell.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests present in the Playwright documentation are using await for all asynchronous commands. IMHO, even if it requires to use await on every command, it does not make the tests less readable. I even think it is more readable than multi level of promises callbacks embed in each other.

throw new Error(`Unknown profile: ${profile}`);
}

await this.request.post(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be AJAX rather than a fake form submit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an AJAX POST request, not sure what mean here.
If by "fake subit" you refer to the CSRF token, it is required since we changed this endpoint to accept only POST queries on GLPI's 11.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it not just a $.ajax call? We already inject the X-Glpi-Csrf-Token header with these calls.

If this is how it was done in Cypress (and the taking CSRF tokens from another page), I probably missed the change in a review or I wasn't looking at Cypress changes at the time.

Our CSRF implementation is weird. Idk why there are per-form tokens and per-page reusable token which can all be used for whatever we want on any page.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glpi's $.ajax load a CSRF token by extracting a token from the current page (parsing the DOM, same as we are doing here).

It works for GLPI because there is always by definition a page already loaded.

From the context of the tests you may not be on a page yet and thus this process need to be independent.
This is not some GLPI's javascript code attached to a page, this is a dedicated process running on a nodejs script.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I understand now. I don't have any idea to do it different right now, so it seems OK as it is.

@AdrienClairembault
Copy link
Contributor Author

Not a fan of using TypeScript. That's both a personal thing and also a worry that it makes writing E2E tests less desirable for the developers that already don't work with JavaScript a lot. Also, working with TypeScript is incredibly frustrating in PHPStorm. There have been too many times I've Ctrl + Click a JS function related to a library to try and see the code for it, only to be directed to the useless .d.ts file for it. Same issue started happening when TS files were being added to the Cypress tests. I don't see the benefit of it at all, and only downsides. I voiced my opinion about it before, but that was when we only had a .d.ts file added for Cypress commands all to fix VSCode behavior (which I also don't like but I know others are using it and it was an OK solution to fix autocomplete).

It is the recommended way to setup playwright.

Here is an extract form the documentation:

TypeScript in Playwright works out of the box and gives you better IDE integrations. Your IDE will show you everything you can do and highlight when you do something wrong. No TypeScript experience is needed and it is not necessary for your code to be in TypeScript, all you need to do is create your tests with a .ts extension.

Copy link
Member

@cedric-anne cedric-anne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you restore the Cypress tests that were removed (to be able to easilly get their timings report), and use the list reporter in both Cypress tests and Playwright test to ease the timings comparison ?

.github/workflows/ci.yml Show resolved Hide resolved
@cedric-anne
Copy link
Member

Could you restore the Cypress tests that were removed

You can add a [ALREADY MIGRATED] prefix in ther name for instance.

@AdrienClairembault
Copy link
Contributor Author

Could you restore the Cypress tests that were removed (to be able to easilly get their timings report), and use the list reporter in both Cypress tests and Playwright test to ease the timings comparison ?

I'll restore the tests but unsure about the reporters.
The list reporter on cypress does not change anything (it is very close to the default spec reporter).
This list reporter on playwright works greats locally but it works by refreshing the terminal content which won't work great on a CI. It is probably best to download the html report that is uploaded as an artifact and contains the complete timings informations.

@cedric-anne
Copy link
Member

Could you restore the Cypress tests that were removed (to be able to easilly get their timings report), and use the list reporter in both Cypress tests and Playwright test to ease the timings comparison ?

I'll restore the tests but unsure about the reporters. The list reporter on cypress does not change anything (it is very close to the default spec reporter). This list reporter on playwright works greats locally but it works by refreshing the terminal content which won't work great on a CI. It is probably best to download the html report that is uploaded as an artifact and contains the complete timings informations.

Whatever the reporter used, we need to be able to compare metrics. The dot reporter does not provide the time required for each test, so we cannot compare anything right now.

@AdrienClairembault
Copy link
Contributor Author

Regarding general performances feedback, here is a quick overview on a test that I've migrated this morning.

I've made care to migrate it as close as possible to its cypress version (even if it could be optimized a bit more) to make sure it is a good comparison.

Here are the two tests:

image

Running the test on cypress take 5s, with a total 12s execution time (cypress is very slow to initialize - but note that this mater less when running multiple tests as this initialization is only done once).

image

Comparing with playwright, we get a 3.5s execution time with a 11s total execution time.

Note that the 11s total is "penalized" by the mandatory tests/playwright/setup/global.setup.ts:37:6 setup that create the needed user and entities (something we don't do with cypress so it can't be compared)

This extra setup takes a good 5s by itself (but we are still a bit faster overall for the total execution time, showcasing that playwright initialization is incredibly fast compared to cypress).

image

So over one single test, on my laptop, playwright seems faster (3.5s vs 5s).

Note that an individual test might appear slower when running things in parallel, as the GLPI's server can get a bit overwhelmed.
For example, running the default 8 threads on my machine will bump this test to 14s.

image

This is explained because this test is executed at the very start of the process.
If you look closely, the first 8 tests (excluding setup so tests 2 to 9) are a lot slower because they are the first executed on each of the 8 threads (so GLPI need to deal with 8 login requests at the exact same time, which might not be great).
The remaining tests after that run quite fast (they don't need to login again and reuse the same session).

This might seems a potential issue but it isn't because the parallelization gain are more important that this initial "speed bump".
Indeed, if we run the same command with one single thread, the total goes to 60s instead of 33s:

image

Sadly, these parallelization gains are less effective on the CI because we only have 2 threads available.
We could maybe think about getting a dedicated runner with a lot of thread for the e2e job, I think it would be a great investment.

@AdrienClairembault
Copy link
Contributor Author

On a side note, there is also the option of running only a subset of the tests on the CI ("smoke tests") and run the whole suite only on the nightly action and before releases.
Developers are still free to run the full suite on their own before submitting a PR, using the many cores of their workstations.

This seems to be a very common practice, but this require the tests to not be flaky at all (which is not the state of the cypress suite, and too early to say for the playwright suite).

@cconard96
Copy link
Contributor

On a side note, there is also the option of running only a subset of the tests on the CI ("smoke tests") and run the whole suite only on the nightly action and before releases. Developers are still free to run the full suite on their own before submitting a PR, using the many cores of their workstations.

This seems to be a very common practice, but this require the tests to not be flaky at all (which is not the state of the cypress suite, and too early to say for the playwright suite).

I don't run tests locally unless I've already pushed to a PR and the CI reports issues, or I am working on specific tests. E2E tests take half an hour, so I push to let the hosted CI run them and switch to another task instead of sitting and waiting for a complete suite to run.

@AdrienClairembault
Copy link
Contributor Author

More feedback on the CI execution time, it seems the 15 migrated tests take 1m05 on cypress for 58s on playwright.
So it isn't worse but the goal of reducing greatly the execution time doesn't seem to be achieved with the current runner limitations.

@cconard96
Copy link
Contributor

I updated the local docker scripts and ran both the Playwright and Cypress tests and then compared the times for all common tests.

Cypress took 59.66 seconds while Playwright took 92.73 seconds. That made Playwright 55.4% slower than Cypress just on individual test execution. Rerunning Playwright tests a few times did not noticeably improve test execution time so it isn't likely to be a one-time performance issue.

It also didn't seem to solve the reliability issues. Both Cypress and Playwright had tests outright fail all retries at least once while I was comparing and they had to be run a few times to get a full run without failures. None of the 2x slower tests on Playwright were tests that had to be retried.

There is quite the difference between what I see locally and in CI for both performance and reliability so maybe I missed something when configuring things to run in the local docker environment?

@AdrienClairembault
Copy link
Contributor Author

Do you have any screenshots of the results (how many threads were used ?) and maybe the changes to the tests scripts so I can try it on my side ?

For the flakiness, note that if you run the full suite (cypress then playwright), some flakyness from cypress can "leak" into the playwrights tests.

For example, if the display preference test from cypress fails, it will create a failure on playwright due to an unexpected "Pending reason" column being displayed:

image

@AdrienClairembault
Copy link
Contributor Author

There is quite the difference between what I see locally and in CI for both performance and reliability so maybe I missed something when configuring things to run in the local docker environment?

This is something I suspect as well, I feel like it should be much quicker on the CI than the actual result (compared to what I get locally).
Maybe it is docker related ? I've tried to find issues on this but without success.

@AdrienClairembault
Copy link
Contributor Author

AdrienClairembault commented Feb 12, 2025

Running with 2 thread on a local docker seems reliable on my side and comparable to the execution time of the github actions CI given than I am a laptop CPU (still a lot slower than running locally without docker tho...).

image

@cconard96
Copy link
Contributor

Do you have any screenshots of the results (how many threads were used ?) and maybe the changes to the tests scripts so I can try it on my side ?

All I have is the ctf-report and an HTML report which don't show the threads used. I didn't touch the configuration, but instead just moved the commands run in the CI workflow to scripts like we did with the other test suites. I'm not even sure where the thread count is configured. Even with a single thread, I expected Playwright to be the same or faster than Cypress, at least once the initial setup was done.

For the flakiness, note that if you run the full suite (cypress then playwright), some flakyness from cypress can "leak" into the playwrights tests.

To be fair to both frameworks and rule out me missing something with the test runner to handle interactive mode properly, I had the containers recreated every time but also I ran the initial Playwright tests first. I split the old E2E tests and Playwright tests into their own "suites" so running tests/run_tests.sh e2e only runs Cypress and tests/run_tests.sh playwright only runs Playwright.

For the CLI output, all I see is:
Selection_427
Possibly because of how I reused the CI stuff for docker.

I published my changes to a branch on my fork for you to see what I did.
https://github.com/cconard96/glpi/tree/feature/playwright_local

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants