How to keep Authentication state fresh across long Playwright CI runs
Playwright storageState can start tests already authenticated, but the saved state has a lifetime. If a long Playwright run in Azure DevOps, Jenkins, or another CI system outlives that state, later tests can be redirected to sign-in even though the test code has not changed.
This guide shows the Playwright solution first: create authentication state inside the CI job that consumes it, move the freshness boundary closer to shards or workers when needed, and protect authentication state as sensitive data. Later, it shows the different execution model in Leapwork Play if you do not want QA authors maintaining the same authentication and browser infrastructure themselves.
Quick answer
Generate storageState inside the CI job that will use it instead of carrying a long-lived auth file between runs. If one job can outlive the session, authenticate separately per shard; if parallel tests change shared data, use a separate account and authentication state per worker. If one individual test can outlive the session itself, split the journey or use an application-supported refresh mechanism.
Already seeing authentication fail halfway through CI?
Jump to the Play approach if you want to see how a reusable login test changes the problem at the test boundary.
A system such as Azure DevOps, Jenkins, GitHub Actions, or GitLab CI that automatically triggers and runs the Playwright test suite.
Authentication state
The browser data that represents a signed-in session, usually cookies plus local storage or IndexedDB.
Token / session lifetime
How long the identity provider or application accepts the current sign-in before it must be refreshed or recreated.
RunList
A RunList is a collection of Play test cases that are executed together as a test run. Each test case runs independently in its own short-lived execution environment, and reusable subflows can be referenced by the individual test cases.
Playwright
Before you start
A Playwright Test project that already authenticates successfully with a setup project or fixture.
A dedicated test identity approved for automation. For parallel tests that change shared data, plan for more than one test account.
Authentication credentials supplied through your CI secret store or environment variables, never hard-coded in the repository.
A rough understanding of your application or identity-provider session lifetime. If you do not know it, ask the identity team or observe when a test session is normally forced back to sign-in.
A Continuous Integration (CI) pipeline, such as Azure DevOps or Jenkins, where each job or shard can run Playwright authentication before the business tests begin.
Related guide
If you have not built reusable SSO authentication yet, start with “How to automate SSO login in Playwright with Okta, Auth0, or Microsoft Entra ID”. This guide assumes that basic pattern is already working.
Why can authentication fail halfway through a CI run?
In this guide, CI means a Continuous Integration system such as Jenkins or Azure DevOps that triggers and runs your Playwright tests automatically.
When a setup test saves storageState, it captures the browser state at that moment. New browser contexts can start from that snapshot, but Playwright does not refresh an expired auth-state file for you. Your application may refresh tokens inside a live browser session if its authentication design supports that, but you should not assume a saved state file will remain valid for an entire long-running pipeline.
Here is what that can look like during one Playwright run triggered by Azure DevOps, Jenkins, or another CI system:
Start of the test run
As the run continues
Later in the run
Azure DevOps or Jenkins triggers Playwright
The same saved authentication state is reused
The session or token reaches its expiry boundary
Login succeeds and storageState is created
Early tests pass while the state is still valid
The saved state is no longer accepted
Business tests start already signed in
The session continues to age as more tests run
Later tests are redirected to sign-in and fail
What changed? The test code may be unchanged. The saved authentication state has simply outlived the session or token lifetime enforced by the application or identity provider.
How do I keep Playwright storageState fresh in Azure DevOps?
Regenerate authentication state at the start of every CI job
If your pipeline currently restores playwright/.auth/user.json from a cache or an artifact created by an earlier run, stop doing that. Run the authentication setup inside the current job so the state is created as close as possible to the tests that use it.
Azure DevOps example: run the same Playwright authentication setup in CI
This pipeline uses the same auth.setup.ts and playwright.config.ts from the earlier sections. Because the setup project is a dependency of the chromium project, Azure DevOps starts the current job, installs the required browser, removes any stale local auth state, runs the authentication setup, and then runs the business tests with freshly created state.
Azure DevOps secret handling: Create APP_URL as a normal pipeline variable. Store SSO_USER and SSO_PASSWORD as secret variables or retrieve them from Azure Key Vault, then map them into the test step through env as shown above. Do not place the credentials directly in azure-pipelines.yml, and do not publish playwright/.auth as a pipeline artifact.
⚠ Security check
Do not upload authentication state as a normal CI artifact. A storage-state file can contain cookies or other browser data that represents the test account. Generate it inside the job, keep its lifetime short, and protect the credentials used to create it.
Which authentication freshness pattern should you use?
Situation
Recommended Playwright pattern
Why
State is carried between CI runs
Regenerate state inside every CI job
Avoids stale files and reduces credential exposure.
One job runs for a long time
Shard the suite; authenticate inside each shard
Creates state closer to the tests that consume it.
Parallel tests change shared data
One account and auth state per worker
Prevents workers from interfering through one identity.
UI login is slow or flaky
Authenticate through a supported API
Reduces browser steps while preserving the same state-reuse model.
One test is longer than the auth TTL
Split the test or use an explicit app-supported refresh design
Avoids hiding a real session-expiry boundary.
Authentication state is sensitive
Keep it out of Git, caches, and normal artifacts
State files can represent the signed-in account.
Already doing all of this just to keep CI signed in?
Playwright can solve the freshness problem, while your team owns the surrounding lifecycle. In Play, the reusable part is the login workflow rather than one shared authenticated-state file. Each RunList test case executes independently and performs its own login.
Advanced Playwright patterns for longer or parallel suites
The Azure DevOps example above is enough for many teams. Use the following patterns only when the way your suite runs creates an additional authentication boundary.
Give each CI shard fresh authentication
If one CI job is long enough for authentication to become stale, split the suite into shards. Each shard is its own CI job, so it can run authentication near the beginning of its own execution rather than sharing a state file created much earlier.
Run four independent shards
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4
In a real CI system those commands normally run on separate jobs or machines. Let the setup project run inside each shard. Avoid a design where one “authentication job” creates user.json and every shard downloads the same file if session freshness is already a problem.
Watch the trade-off
More shards mean more sign-ins. Check identity-provider rate limits and make sure you have enough approved test accounts before increasing parallelism.
Use one authenticated account per worker when tests change shared data
A single shared account works when tests can run at the same time without changing each other's server-side state. When tests create, edit, or delete shared data, Playwright recommends authenticating once per worker with a unique account.
Worker-scoped authentication pattern
import { test as base } from '@playwright/test';
import path from 'path';
export const test = base.extend<{}, { workerStorageState: string }>({
storageState: ({ workerStorageState }, use) => use(workerStorageState),
workerStorageState: [async ({ browser }, use) => {
const id = test.info().parallelIndex;
const authFile = path.resolve(
test.info().project.outputDir,
`.auth/${id}.json`
);
const account = await acquireAccount(id); // your account-pool logic
const page = await browser.newPage({ storageState: undefined });
// Complete your approved login flow with account.username/password.
// Wait until the signed-in application state is visible.
await page.context().storageState({ path: authFile });
await page.close();
await use(authFile);
}, { scope: 'worker' }],
});
The important idea is not the helper function itself. Each worker gets its own account and creates its own state under the current run output. The tests inside that worker reuse the state without fighting another worker for the same account.
If UI login is slow: Use API authentication when your application supports it
For CI, a supported authentication API is often faster and less brittle than replaying a full login UI. Playwright can authenticate with APIRequestContext and save the resulting browser state in the same way.
tests/auth.setup.ts
import { test as setup } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate by API', async ({ request }) => {
await request.post(process.env.AUTH_URL!, {
data: {
username: process.env.SSO_USER,
password: process.env.SSO_PASSWORD,
},
});
await request.storageState({ path: authFile });
});
⚠ Best practice
Use API authentication only when the application or identity team supports it for automated testing. Do not invent undocumented token endpoints, bypass MFA, or hard-code client secrets to make CI easier.
If one test exceeds the session lifetime: do not silently re-login
If a single business test runs longer than the session lifetime, automatically signing in again halfway through the test can hide a real session-expiry problem and change what the test is validating. A clearer design is to split the journey into smaller independent tests, or work with the application team on an explicit supported refresh mechanism.
At test boundaries, fail with a useful message if the application unexpectedly returns to sign-in. That makes the pipeline tell you “authentication expired” instead of producing a series of unrelated locator failures.
Practical rule
Refresh authentication between tests, jobs, shards, or workers. Be cautious about refreshing it invisibly in the middle of the business scenario.
When does authentication become CI infrastructure?
Playwright can handle each of the cases above. The additional work sits around the business tests: refresh timing, test-user pools, worker-to-account mapping, secrets, state handling across CI jobs, and browser/runtime setup. When several of these become permanent parts of the pipeline, authentication has become shared CI infrastructure rather than a small login helper.
Concern
What the Playwright project now owns
State freshness
Decide where state is recreated, detect expiry, and avoid reusing stale auth files.
Parallel workers
Allocate enough test accounts and map each worker to the right authenticated state when tests change shared data.
CI shards and jobs
Run authentication close to each job, distribute secrets safely, and avoid stale shared state artifacts.
Sensitive authentication data
Protect credentials and storage-state files from Git, normal artifacts, logs, and long-lived caches.
Browser and runtime
Install and maintain the Playwright runtime, browser dependencies, and CI execution environment.
These are valid Playwright engineering patterns. The practical decision is whether QA authors should keep owning this setup or whether more of the authoring and execution workflow should live in the testing platform.
Where Play can reduce the setup
If your team is maintaining refresh logic, account pools, worker mappings, CI secrets, and state handling mainly to keep browser tests signed in, Play changes the execution model. LoginSSO is reused as a subflow, while each RunList test case runs in its own short-lived environment and performs its own login. The built-in cloud browser, cloud execution, and Play API reduce the amount of Playwright-specific runtime and authentication plumbing visible to the QA author.
Leapwork Play
How does Leapwork Play change the authentication-freshness problem?
In Play, the SSO journey can be recorded once as a reusable LoginSSO test case. A business test that references LoginSSO runs that login flow before continuing with its own steps in Play's cloud browser. The login is a visible shared test asset rather than a JSON authentication-state file wired into the Playwright project.
For long CI runs, this changes the freshness boundary. A referenced login can create a new signed-in browser session at the beginning of the business test instead of relying on a state file created much earlier in the pipeline. storageState can still be faster while it remains valid because it can skip the login UI. Play reduces the setup and maintenance the QA author has to manage directly.
Why use Play for this workflow?
Keep the login logic as a named reusable LoginSSO subflow that QA authors can see, share, and reference from multiple test cases.
Record and execute in Play's built-in cloud browser, with no local recorder installation for the authoring workflow.
This Play workflow does not depend on passing a storageState JSON artifact from one CI job to another.
Keep the reusable login, recorded steps, and generated Playwright in the same shared workspace.
Use Play's self-healing capabilities to help the reusable login adapt when application locators change.
Trigger a Play RunList directly from CI
The CI pipeline does not have to be the machine that runs Playwright. With the Play API, Azure DevOps, Jenkins, or another CI system can trigger a Play RunList and let Play execute the test cases in its managed cloud environment.
CI pipeline
Play API
Play managed execution
Azure DevOps or Jenkins starts the pipeline
Call the Play API to start the selected RunList
Play runs the RunList test cases in its managed cloud environment
Keep the Play API credential in the CI secret store
Pass the RunList/run configuration required by your Play setup
Each test case runs in its own process on a short-lived execution container; the CI agent does not need Playwright or browser binaries for this path
Read or poll the run status from the pipeline
Use the Play RunList result exposed by the API
Use pass/fail as part of the pipeline or deployment gate
For this Play execution path, the CI agent does not need the Playwright runtime, browser binaries, or commands such as npx playwright install --with-deps. Play runs the RunList test cases in separate short-lived execution containers. The pipeline still owns the trigger, secure API credential, network access, environment or test-data inputs, and the rule that decides whether a failed Play run should stop deployment.
⚠ Security check
Store the Play API key or token as a secret pipeline variable or in your approved secret manager. Do not hard-code it in YAML, scripts, or the repository.
Use the reusable login at the test boundary
Record the approved SSO journey once and save it as LoginSSO.
Create the business test that needs an authenticated session.
Add “Use test case: LoginSSO” before the business-specific steps.
Run the test in Play. The referenced login executes first, then the business journey continues.
Play records LoginSSO once and reuses it by reference. The images demonstrates reuse
Playwright vs Leapwork Play for long CI authentication
Area
Playwright
Leapwork Play
Freshness boundary
Generate storageState at job, shard, or worker boundaries.
Each RunList test case starts in its own short-lived execution environment and runs LoginSSO for that test case.
Reuse mechanism
Saved browser state loaded through configuration or fixtures.
Reusable LoginSSO subflow. The login workflow is reused; authenticated state is not shared across test cases.
Auth artifact
Protect and expire a storage-state file.
No shared storageState artifact is carried across test cases in this workflow; each test case performs its own login.
Browser/runtime
Team owns Playwright runtime and CI browser setup.
Built-in cloud browser and cloud execution.
CI execution model
CI agent or maintained container runs Node, Playwright, and browser dependencies.
CI can call the Play API to start a RunList. Play executes the test cases in its managed cloud environment, with separate short-lived execution containers.
Speed
Usually faster because the UI login can be skipped while state is valid.
Login is replayed when referenced, so freshness trades off against sign-in time.
Locator maintenance
Update login locators and setup code when the UI changes.
Play self-healing can help the reusable login adapt when locators change.
Security policy
IdP policy still applies.
IdP policy still applies.
Expired login
Recreate storageState at the job, shard, or worker boundary you choose.
A new test case performs its own login when it starts. A single long-running test can still hit the IdP session-expiry boundary.
Parallel execution
Use worker-scoped accounts and authentication state when tests change shared server-side data.
Parallel test cases execute independently. Use different test users when sharing one account or application data could create conflicts.
CI jobs and shards
Repeat authentication setup and secret handling close to each job; avoid stale shared state files.
The CI pipeline can trigger the RunList through the Play API without distributing storageState files or installing Playwright/browser binaries on the CI agent.
Auth asset visibility
Authentication lives in setup code, configuration, and saved state files.
LoginSSO is a named reusable test case visible in the shared workspace.
When should you consider moving this workflow to Play?
Consider Play when the authentication setup around the tests is becoming a recurring engineering responsibility, especially when QA authors are maintaining refresh logic, worker-scoped fixtures, test-user pools, CI browser setup, or sensitive state artifacts. Play keeps the login as a reusable test asset and moves browser authoring and execution into the shared Play environment.
Want to reduce authentication plumbing in the QA workflow?
Try the same authentication journey in Leapwork Play. Keep the login as a reusable test asset, run it in the managed cloud browser, and compare how much refresh, state-file, browser, and fixture setup your QA team still has to own directly.
Best practices
Know the session lifetime you are designing for. A 15-minute access token and an 8-hour refresh session are very different CI problems.
Generate authentication close to where tests run: per job, shard, or worker rather than once for the whole pipeline.
Never hard-code usernames, passwords, client secrets, tokens, or OTP seeds. Use environment variables or a secret manager and mask values in CI logs.
Keep storage-state files out of source control, persistent caches, and normal CI artifacts.
Use separate accounts when parallel tests modify shared server-side data.
Prefer a supported authentication API when it is more reliable than the login UI.
Do not weaken MFA or Conditional Access simply to make automation pass.
Do not silently reauthenticate in the middle of a test unless the scenario explicitly requires and validates that behavior.
Troubleshooting
Early tests pass but later tests return to sign-in - The session created at the start may have expired. Regenerate state closer to the tests, shard the suite, or reduce the amount of time one state file is reused.
Only one shard fails authentication - Make sure every shard runs its own authentication setup and is not downloading an old shared state file. Also check whether that shard is using a different test account or region.
Several parallel workers interfere with each other - If tests change server-side state, give each worker its own test account and worker-scoped authentication state.
The auth file is new but the browser still redirects to login - The application may rely on sessionStorage, browser-specific state, device policy, or another authentication mechanism not represented by the state you saved.
Authentication API succeeds but the UI is still signed out - Confirm that the API login actually creates the cookies or browser storage the web application expects. A successful API response does not automatically mean the browser session is authenticated.
A Play test loses authentication inside one long journey - The identity-provider session lifetime still applies. Split the journey or use an explicit supported refresh design rather than assuming LoginSSO refreshes a session in the middle of a running test.
Frequently asked questions
Does Playwright storageState refresh itself?
No. storageState is saved browser state. Playwright can load it into a new browser context, but it does not make an expired state file valid again. Your application may refresh tokens inside a live session if it is designed to do so.
Should I generate one auth file per pipeline or one per shard?
If authentication expiry is a risk, generate state inside each CI job or shard so it is created close to the tests that use it.
Can I share storageState between CI jobs as an artifact?
You technically can move files between jobs, but it is a poor default for sensitive, time-limited authentication state. It increases both staleness and credential exposure. Prefer regenerating state in the consuming job.
How often should I refresh authentication?
There is no universal interval. It depends on your identity-provider and application session policy. Design the refresh boundary to be comfortably shorter than the session lifetime.
What if a token expires in the middle of one test?
Split very long journeys where possible, or use an explicit application-supported refresh mechanism. Silent re-login can hide the behavior you are supposed to test.
Is LoginSSO in Play the same as storageState?
No. LoginSSO is a reusable login subflow. In a Play RunList, each test case runs in its own short-lived execution environment and performs the login when it references that subflow. storageState instead loads a saved browser-authentication snapshot and can start a Playwright test already signed in.
Does Leapwork Play guarantee that authentication will never expire?
No. Okta, Auth0, Microsoft Entra ID, or your configured identity provider still controls MFA, session lifetime, token expiry, Conditional Access, and related policy.
Does Play share one authenticated session across all test cases in a RunList?
No. Each test case has its own process and short-lived execution container. If LoginSSO is referenced, that test case performs its own login. When RunList test cases run in parallel, use different test users where sharing one account could create conflicts.