How to Manage Authentication in Playwright
When testing or monitoring web applications like e-commerce sites, SaaS applications or anything that lives behind a login, you will have to deal with authentication. Playwright takes care of a lot of the heavy lifting here, meaning you don’t have to manually get, set and persist cookies or manage local storage data.
In this article we will look at some typical authentication scenarios, explain how using Playwright’s storageState
works under the hood and give some best practices when dealing with authentication data.
Handling authentication flows
For web and applications and APIs, you will typically have to run through some interactive authentication flow. Here are the top, common flows.
Use Playwright’s built-in code generator to quickly get a script of your login steps. It will do the right thing in 90% of the cases. Just run npx playwright codegen <mysite>
Basic Auth / Username & Password
Any flow that relies on the Basic Authentication standard or typing in a username and password will tend to look very similar to example test below.
Notice the following:
- We are using the
getBy
locators where we can. - The username and password are referenced as environment variables. Never store them in your
.spec.ts
file! - At the end, we assert that the login actually worked, by checking for a user specific piece of content on the page.
- All handling of cookies, hashing of username and password and other things your browser typically takes care of are handled here transparently.
SSO & Social Login
Any SSO or social login — think Google, GitHub, Microsoft, SAML-based solutions or any 3rd party auth provider like Okta— works pretty much the same as basic authentication / username & password from the perspective of the test you are writing.
You will probably see some extra redirects as the login attempt is executed on a second domain, but in 99% of the cases Playwright is smart enough to handle these redirects. Awesome.
Here is an example of using Google login on stackoverflow.com. 👇
Check the full article on Google Login.
Two-Factor Authentication / TOTP
Things get a little harder when using Two-Factor Authentication (2FA) and / or Time Based One Time Passwords (TOTP). You will probably be familiar with providing an extra “factor” like a code from an SMS message, a authenticator app or email to a login flow. How do we access a text message (or any of the other options) in Playwright? In short, we don’t. To solve this we need to use the excellent otpauth NPM package.
We wrote a full article on using the otpauth
package to login to a GitHub account protected by 2FA, so please check out that article for all the details. Below is the eventual .spec.ts
file you will end up with.
Notice the following in this code example:
- We call the
OTPAuth.TOTP()
function and pass in a secret token we previously got from the GitHub UI. - In our test, we run through a fairly normal login routine. In the end, we focus on the field filled with the
XXXXXX
placeholder and calltotp.generate()
to create the 2FA token.
As mentioned, there is some prep you need to do. Refer to our full article on 2FA login for all instructions.
Passkey & WebAuthn
Passkeys are a fairly recent authentication scheme. Passkeys are passwordless, so passwords can’t be stolen or phished. There is also no need to memorize a passkey. Often, a passkey is tied to biometric data: every time you authenticate on some app or service with Face ID or Touch ID or using YubiKey you are using a passkey.
You can see how this is problem when running Playwright. However, under the hood all passkeys are an implementation based on the Web Authentication API (WebAuthn) spec and all Chrome Devtools Protocol (CDP) based browsers like Chrome and Edge ship with a WebAuthn Virtual Authenticator. This virtual authenticator allows you to automate the user interactions normally done by an actual human being. This excellent write up from the folks at Corbado goes into a lot more detail.
Let’s look at an E2E example. We will use the https://webauthn.io/ site as our testing target and perform the following actions.
- Enabled WebAuthn.
- Sign up with a username + passkey.
- Login with that passkey.
Notice how we first have to set up the WebAuthn virtual authenticator via the CDP session. This allows us to simulate the passkey authentication flow that would normally require user interaction.
The code is quite straightforward after that - we just fill in a username and click through the registration and authentication steps.
API tokens
When working with APIs, you most often need to authenticate using some Bearer token / API key. There is no magic here, as Playwright makes it easy to include these tokens in your request headers.
Just like with username and password authentication, make sure to store your API tokens securely using environment variables.
Reusing authentication state with storageState
All of the above examples work fine if you are running just one test that requires authentication. However, the moment you will run more tests — either in parallel or in sequence — you will exercise your authentication endpoint / provider over and over again. This will cause issues with rate limiting and just make your tests run unnecessarily long.
Ideally, you can authenticate once and reuse the authenticated state across multiple tests.
Playwright has this feature baked in and leverages something called storageState
.
It requires a little setup 👇
Setting up the playwright.config.ts file
The recommended way to reuse auth state is by setting up projects in your playwright.config.ts
file and defining a setup
step that references file — auth.setup.ts
for example — that takes care of the necessary authentication flow.
Writing a auth.setup.ts
file
The code in auth.setup.ts
stores the eventual authenticated state (cookies and local storage) on disk in a user
defined file. Any other projects declare an explicit dependency on the setup
step and read the auth state from disk,
defined by the storageState
property. Playwright then makes sure the cookies and local storage items are handled
correctly in subsequent tests.
The example below shows how to use the basic authentication login flow from above in the auth.setup.ts
file.
Notice that:
- We just run a normal
test
, renamed tosetup
. - We store the all cookies and local storage items in the
authFile
by calling.storageState()
. How this happens doesn’t interest us. Playwright takes care of it.
Reusing authentication in tests
Now, on each invocation of npx playwright test
, the auth.setup.ts
routine gets called first. This means we can just create a new test as normal and magically the normal page
fixture is authenticated.
Have a look at the official Playwright documentation for more advanced scenarios.
Best practices for authentication
When writing automation scripts that deal with authentication, there are some general principles you should stick to:
- Use the Playwright Code Generator,
npx playwright codegen example.com
to get the boilerplate code for any authentication flows. Saves time and let’s you focus on the difficult parts. - Never, ever store credentials in your tests. Not even during writing and debugging. Always use environment variables like
process.env.MY_PASSWORD
. Use them right at the beginning as you will forget about them and then accidentallygit push
them. - Try to always use dedicated test users, not your own account or — god forbid — a customer’s account. This way you can control the test data more easily, not accidentally trigger a lock out due to bot detection.
- Always add any
.env
files and the/playwright/.auth/user.json
to your.gitignore
file.