Speeding up tests execution in Playwright — shared session

mati-qa
6 min readSep 25, 2023

--

The fastest test execution is, the better for us — we can check the result of the changes faster. However, as always, it depends on the complexity of the app we are testing.

Case description — Intro

Let’s assume that we have an app, which allow at least three types of the user: read-only, read-write and admin. Each of the type has some dedicated actions which could be performed, but the UI is generally the same. There is also a login form to authenticate the particular context. We could also assume that we have at least six ‘describe’ — which in Playwright (as well as in Protractor) mean groups of tests (or test suites). Each describe in a single spec file — as in the example below.

Each describe contains at least 3 test cases, and these tests are fully independent of each other. The project is configured to use the ‘fullyParallel’ flag, which is enabled. BTW. each describe contains a ‘beforeAll’ hook in which the login procedure is executed. The tests inside the describe are using the same context, namely the same user. So, if we run this in serial more, then each describe will ‘execute’ the login procedure only once, and then the tests will be executed one after the other. But we are using fullyParallel mode, which means that each instance will execute the login procedure for itself.

First question: Why not use only 3 ‘describe’, if we have only 3 user contexts (read-only, read-write, admin)? IMO, if we are using fullyParallel mode, each browser instance which is fired will execute the login procedure for itself. I also have the impression that the more we split the suites, the better we can manage them and the better the parallelism works. In my experiments, sometimes parallel execution was blocked for the cases inside the same describe — no idea why — maybe some PW feature. ;)

To the point: how we could speed up this? Here I’ll focus on the login procedure. In PW, we can easily deal with the session storage of the browser, and as you may know, all cookies, tokens, and other stuff responsible for user authorisation (or keeping the user authenticated) is stored there. So, there is a method called — storageState — that allows you to export all your browser storage to a file. But how to use it? Let’s have a look at what changes are needed.

Exemplary use

Current state:

  • exemplary mati-qa-suite2.spec.js content:
  • pretty default playwright.config.js:

As you can see, the login procedure is imported for the helper file (as we are trying to keep PageObjectPattern) — so, all the code needed to log in is stored there. And here is how the method could look like:

Last line (with ‘waitFor’ method) is added to be sure that we are logged in and can proceed. All the spec.js files are similar, with the difference of the user context (read-only, read-write, admin). So, again, each browser instance — because of the fullyParallel mode — will use its own authentication — which can take up to 10 seconds. And in the worst cases scenario, it will try to authenticate the user for each test separately — about 18 times — 180 second — 3 min.

Changes

To speed these up, the following changes will be needed:

  • Extract the call for our login procedure to a separated spec file, such as read-only.session-creator.spec.js. Basically, you just need to create a new spec.js file and invoke the loginToThePortal method from there. The only change is to add a line to export the storage state after successful login. Here is how it could look like:

As you can see, this is almost a regular spec.js file. The only difference is that we are importing test from Playwright and aliasing it as setup (first line of code). Then we’re defining a const variable with the path to the file where we’ll store the gathered browser session. No matter if the file exists — if not, the file will be created; if yes, the file will be overwritten. Then there is a ‘setup == test’ block in which we are calling our login method, and after all (10th line) we are keeping/saving the browser session storage for further use using the storageState method with the argument path (defined variable).

  • Changes in the playwright.config.js with the projects. We need to add one additional project per each user context. These projects will be responsible only for running those session-creator.spec.js files. And then, we need to add dependencies for the related test suites. Here is how one of the pairs could look like (and we would need three pairs as we have three user contexts):

As you can see, we have one new project with a particular name and the testMatch property, which can be a regular expression or the direct name of the suite that should be executed. In our example, this is our read-only.session-creator.spec.js file. Then we need to add a dependency to our main project based on the name (25th line on the above screen, which equals the 20th line), and inside the use tag, we need to set the storageState that will be used (28th line). We should also update/rethink our spec file names. I’m suggesting here to add the user context name as a prefix for each spec file. So, instead of mati-qa-suite1.spec.js, we would have read-only.mati-qa-suite1.spec.js. IMO, this is the easiest way to distinguish suites based on user context. We could also use tags inside describe, but then looking for the specs that are using a particular context would be a bit painful, as we would need to open each file and look into the describe title.

  • Small change in the place where we performed the login — instead of the login procedure, we just need to invoke entering the URL. So, in our main describe, where we have all cases, we need to make the following change:

And that’s it. Now, no matter how many instances of the browser are run, each user flow login will be run only once — so, 3 times in general. After all changes, here is how our ‘spec structure’ should look like:

Of course, we could now organize it a bit (split it between folders, e.g., admin, read-write, read-only), but this is only an example, so it doesn’t have to be pretty. ;)

Conclusion

As you can see, sometime, even such a simple change may speed up your tests’ execution speed. Of course, all depends, as always, but in most cases, if your / the app which you’re testing, contains login flow, it’s always better to use this approach (shared session) instead of explicit login. You never know, when the number of your tests exceed 100, and then each second matter. So, have fun and experiment. ;)

--

--