When you’re building end-to-end tests with Playwright, the most surprising thing about reusing login state is how much it doesn’t feel like you’re reusing anything. It’s not just about skipping a few clicks; it’s about fundamentally changing the scope of your tests.
Imagine you’ve got a complex application. Logging in might involve a multi-factor authentication flow, an OAuth handshake, or even a CAPTCHA. Running this every single time a test suite spins up is a massive time sink. Playwright’s storageState feature lets you capture the authenticated state of a browser context and reuse it across multiple tests, or even across different test runs.
Here’s how it looks in practice. First, you need a test that actually logs in and saves the state:
# test_login.py
import pytest
from playwright.sync_api import sync_playwright
def test_login_and_save_state():
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context()
page = context.new_page()
page.goto("https://your-app.com/login")
page.fill("input[name='username']", "testuser")
page.fill("input[name='password']", "password123")
page.click("button[type='submit']")
# Wait for navigation or a known element after login
page.wait_for_url("https://your-app.com/dashboard")
# Save the authentication state
context.storage_state(path="auth/state.json")
browser.close()
Now, any subsequent test can load this saved state. Notice how the storageState option is passed when creating the browser object, not the context:
# test_dashboard.py
import pytest
from playwright.sync_api import sync_playwright
def test_access_dashboard_with_saved_state():
with sync_playwright() as p:
# Load the authentication state
browser = p.chromium.launch(
storage_state="auth/state.json"
)
# No need to create a new context for authentication here
# The browser object itself is already authenticated
page = browser.new_page()
page.goto("https://your-app.com/dashboard")
# Assert that we are on the dashboard page and logged in
assert "Dashboard" in page.title()
assert page.locator("text=Welcome, testuser").is_visible()
browser.close()
This isn’t just about cookies. storageState captures cookies, local storage, and session storage. It’s a snapshot of everything the browser needs to maintain an authenticated session. When you load this state, Playwright effectively injects these details into the new browser context, making it appear as if the user has already logged in.
The core problem this solves is test execution speed and reliability in authentication-heavy applications. Without it, every test suite run would include the full login flow, adding minutes to your CI/CD pipeline. More importantly, flaky login steps (like CAPTCHAs or third-party OAuth providers) would introduce noise and false negatives into your test results. By externalizing the login to a single setup test, you isolate the authentication logic and then run your functional tests against the application’s core features without the authentication overhead.
The mental model here is that each browser object launched with a storageState is a pre-authenticated session. You can then create multiple page objects or context objects from this single authenticated browser instance, and they will all inherit that authenticated state. This is incredibly powerful for testing different user roles or permissions within the same application, as you can generate separate storageState files for each role.
The storageState itself is a JSON file. You can inspect it to see the various tokens and session identifiers that Playwright has captured. It’s not encrypted by default, so be mindful of storing sensitive credentials if your application’s authentication relies on data that shouldn’t be committed to version control. For production scenarios or sensitive data, consider encrypting these files or using environment variables to manage them.
If you’re dealing with dynamic tokens that expire quickly, or if your authentication mechanism is very complex, you might find that simply saving and loading the storageState isn’t enough. You might need to implement a strategy where a dedicated "auth" test runs periodically to refresh the storageState file, ensuring that subsequent tests use a valid, current session. This is often done by configuring your test runner to execute the login test before a batch of functional tests.
The next logical step is to explore how to manage multiple different authentication states for various user roles within the same test suite.