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.basic-auth.spec.ts
- 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. 👇google-login.spec.ts
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 theotpauth
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.
2fa.spec.ts
- 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.
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.
webauthn.spec.ts
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.api-token.spec.ts
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 calledstorageState
.
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 yourplaywright.config.ts
file and defining a setup
step that references file — auth.setup.ts
for example — that takes care of the necessary authentication flow.
playwright.config.ts
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.
auth.setup.ts
- 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 ofnpx 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.
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.