Reuse a login session
Log in once, save the browser state to a file, and load it into fresh contexts.
Logging in through the UI for every test is slow and fragile. Sign in once, save the cookies and local storage, then reuse that state where authentication is only test setup.
Save the state once
Run a setup script that logs in and writes the state to a file. Wait for a signed-in signal before saving, so you never persist a half-finished login.
<?php
require __DIR__.'/vendor/autoload.php';
use Playwright\Playwright;
$statePath = __DIR__.'/.auth/user.json';
if (!is_dir(dirname($statePath))) {
mkdir(dirname($statePath), 0770, true);
}
$context = Playwright::chromium(['headless' => true]);
$page = $context->newPage();
$email = getenv('TEST_USER_EMAIL');
$password = getenv('TEST_USER_PASSWORD');
if (false === $email || '' === $email || false === $password || '' === $password) {
throw new RuntimeException('TEST_USER_EMAIL and TEST_USER_PASSWORD are required');
}
$page->goto('https://app.example.com/login');
$page->getByLabel('Email')->fill($email);
$page->getByLabel('Password')->fill($password);
$page->getByRole('button', ['name' => 'Sign in'])->click();
$page->waitForURL('**/dashboard', ['timeout' => 15000]);
$context->saveStorageState($statePath);
$context->close();
Commit the script, not the generated file. Add .auth/ to .gitignore unless it holds only fake local credentials.
Load the state into a new context
Pass the file path when you create the context. The new context starts already signed in.
use Playwright\PlaywrightFactory;
$playwright = PlaywrightFactory::create();
$browser = $playwright->chromium()->withHeadless()->launch();
$context = $browser->newContext([
'storageState' => __DIR__.'/.auth/user.json',
]);
$page = $context->newPage();
$page->goto('https://app.example.com/dashboard');
For a context that already exists, load the state before you open a page:
$context->loadStorageState(__DIR__.'/.auth/user.json');
$page = $context->newPage();
Keep it fresh
Save state too early and you store a logged-out session. Reuse one live context across tests and they leak state into each other: share the file, not the context. When cookies expire, regenerate the file by rerunning the setup script.
Expected result
The saved state file exists after setup, and a fresh context using that file opens the authenticated page without visiting the login form. Assert a signed-in heading, navigation item, or account label.
Go next
- Handling authentication: per-role state files, CI setup, and common mistakes
- Testing with PHPUnit: where the setup step belongs in a suite