How to Test Browser Storage Reset Between Runs Without Hiding Real Session Bugs
By Markus Gasser · September 17, 2026
Learn what to clear, what to keep, and how to prove browser state isolation in automation without masking auth, cart, or draft-saving defects.
Browser state is one of the easiest ways to get false confidence from automation. If a login test only passes because yesterday’s session cookie is still present, you do not have a stable test, you have a hidden dependency.
The goal is not to wipe everything blindly. The goal is to test browser storage reset between runs in a way that proves two things at once:
- Each test starts from a clean, known state.
- Your cleanup does not erase real bugs in auth, cart, draft saving, or session recovery.
The useful question is not “did storage get cleared?”, it is “did the app still behave correctly when storage was absent, stale, or partially cleared?”
What counts as browser storage in a test run?
In this article, browser storage means the client-side state that can survive page reloads and, depending on how you launch tests, sometimes survive between runs.
The main buckets
- Cookies: Often used for session identifiers, CSRF tokens, preferences, and analytics.
- localStorage: Persistent key-value data for the origin. It survives browser restarts until cleared.
- sessionStorage: Key-value data scoped to a tab or top-level browsing context. It usually disappears when the tab closes.
- IndexedDB: Structured client-side database, frequently used for offline data, drafts, queues, and app caches.
- Cache Storage / Service Worker state: Can keep fetched assets or offline responses around, depending on your app.
The browser APIs are defined in primary documentation such as the MDN Web Docs for localStorage, sessionStorage, Cookies, and IndexedDB.
What should be cleared, and what should not?
The right answer depends on the test’s purpose.
Clear these when the test needs a clean start
- Session cookies for authenticated flows that must start logged out
localStoragefor stateful UI settings, cached auth tokens, feature flags, and app preferencessessionStoragewhen the test depends on a fresh tab-level state- IndexedDB if the app stores drafts, offline queues, or cached records there
- Service worker data when offline behavior or stale cache is affecting determinism
Do not clear these if you want to catch real bugs
- Auth persistence in a test that is supposed to verify “remember me” or session restore
- Cart persistence in a shopping flow where a user expects items to survive reloads
- Draft saving if the product promises recovery after accidental navigation or refresh
- User preferences if the test is specifically checking whether the app honors saved settings across sessions
The important distinction is between test isolation and product behavior. Isolation removes accidental leftovers from prior tests. Product behavior is the feature you actually want to verify.
A simple rule for deciding what to reset
Classify each state item using three questions:
- Is the state part of the feature under test?
- Does the app promise that the state persists across refresh, tab close, or sign-out?
- Could leftover state make a pass or fail misleading?
If the answer to 1 is yes, be careful. If the answer to 2 is yes, do not delete it in every setup step. If the answer to 3 is yes, isolate it in a fixture, context, or profile dedicated to that test.
Why naive cleanup hides bugs
The most common mistake is to run a universal cleanup before every test and then assume the app is healthy when tests pass.
That can hide defects like these:
- The app fails to clear an auth token on logout, but your suite deletes the token before the next test starts.
- A cart is supposed to persist after reload, but your cleanup removes the cart before the follow-up assertion.
- A draft editor leaks state between users, but your test only ever runs with an empty profile.
- A stale IndexedDB record causes a broken upgrade path, but an overzealous reset removes the old database before the migration code runs.
If a bug only appears with leftover browser state, the correct response is not “clear harder.” It is “write one test that preserves the state and exposes the failure.”
Recommended test design: separate isolation from persistence checks
Do not try to solve everything in one setup hook.
Use two kinds of tests
Isolation tests prove the app behaves correctly from a clean state.
- Start from a new browser context or profile
- Clear only the storage required for determinism
- Verify the page loads without relying on prior session data
Persistence tests prove the app preserves or restores state correctly.
- Preload one specific state item, such as an auth cookie or draft record
- Reload, reopen, or revisit the app
- Assert that the state remains or is intentionally removed
This separation matters because a universal reset can make persistence bugs invisible.
What browser automation frameworks can help with
Frameworks differ in how they isolate state, so it is worth choosing the mechanism that matches your test goal.
Playwright
Playwright documents browser contexts as isolated sessions, which is useful when you want a fresh storage container per test. Its browser contexts documentation explains how contexts separate cookies and storage, and its authentication guide describes storageState for saving and restoring login state.
A small Playwright pattern for a clean context looks like this:
import { test, expect } from '@playwright/test';
test('starts without leaked storage', async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
await context.close();
});
Use this style when you care about session isolation more than speed.
Cypress
Cypress provides commands for clearing state during a test, including clearCookies, clearLocalStorage, and related storage helpers. That is convenient, but it is also easy to overuse them and accidentally suppress a bug that depends on prior state.
A good pattern is to clear only what your test truly depends on, then keep a separate spec that verifies persistence across reloads.
Selenium
Selenium itself does not define a single universal storage API, because the mechanics depend on the language binding and browser driver. The browser-facing behavior still maps back to the same primitives: cookies, origin storage, and IndexedDB. The WebDriver specification is the primary reference for session behavior and browser automation boundaries.
For Selenium suites, prefer explicit browser profile control or fresh driver sessions over ad hoc JavaScript cleanup when test isolation matters.
A practical cleanup strategy that avoids overreach
The safest default is to reset in layers.
Layer 1: new browser session or context
This is the cleanest boundary for most UI tests. A fresh context usually gives you new cookies and storage containers.
Layer 2: targeted application cleanup
If your app stores data in IndexedDB or service workers, a new context may not be enough in every browser or deployment configuration. In those cases, clear the app-specific stores explicitly or use a dedicated test account and domain.
Layer 3: verify cleanup itself
Do not assume the reset worked. Add a small assertion that storage is actually empty before the test continues.
For example, in a browser page context you can check localStorage and cookies after setup:
const cookies = await context.cookies();
expect(cookies).toHaveLength(0);
const keys = await page.evaluate(() => Object.keys(localStorage));
expect(keys).toHaveLength(0);
That does not prove IndexedDB or service worker state is gone, but it does prove the test did not inherit the obvious leftovers.
How to tell leaked state from a broken app flow
When a test fails, trace the failure backward.
Signs of leaked browser state
- The failure disappears when you run the test in a fresh profile or incognito-style context
- The failure only happens after another test that logs in, saves a draft, or seeds localStorage
- The failure reproduces with UI navigation but not with a blank browser profile
Signs of a real app bug
- The failure reproduces in a truly clean context
- The same bug appears on first visit, not just after previous tests
- The app’s documented behavior is violated, such as logout not clearing session cookies or draft recovery not restoring data
A useful debugging trick is to run the same scenario twice:
- Once in a freshly created browser context.
- Once after explicitly seeding the expected storage state.
If the first passes and the second fails, you may have uncovered a state-transition bug rather than a flaky test.
Edge cases worth testing explicitly
Some bugs only appear when storage is partially cleared.
1. Auth cookie removed, localStorage token remains
This catches apps that read auth from more than one place. If logout clears only cookies, a token in localStorage may resurrect a session.
2. localStorage cleared, session cookie remains
This catches apps that treat localStorage as disposable but still rely on a server session.
3. IndexedDB survives a version upgrade
This matters for apps with offline data or cached drafts. A stale schema can break migrations even when cookies and localStorage look clean.
4. Cross-tab behavior
sessionStorage is tab-scoped, but cookies and localStorage are shared by origin. If your app uses multiple tabs, test that the right state appears in the right place.
A reproducible checklist for CI
Use this checklist when adding a browser storage reset to your suite:
- Create a new browser context or equivalent isolated session per test where possible
- Define which storage types the test should start without
- Seed state only when the scenario explicitly requires it
- Assert the absence of leftover cookies and localStorage keys before the test starts
- Keep separate tests for persistence, logout, draft restore, and cart retention
- Re-run failures in a fresh context before blaming the application
- If IndexedDB or service workers are involved, include one scenario that exercises stale state on purpose
A small decision table
| Test goal | Reset storage? | Reset what | What to keep |
|---|---|---|---|
| Login should start from scratch | Yes | Cookies, localStorage, sessionStorage | Nothing from prior auth |
| Remember me should survive reload | No, or seed only what matters | Avoid blanket reset | The selected session state |
| Cart should persist across sessions | Partial | Clear unrelated app data only | Cart storage |
| Draft recovery should work after refresh | Partial | Clear unrelated test state | Draft record or IndexedDB entry |
| Logout should fully invalidate user state | No | Preserve the state long enough to verify removal | The pre-logout session |
When a fresh profile is better than manual cleanup
A fresh browser profile or context is usually the better choice when:
- Your app uses multiple storage types
- You have flaky cross-test contamination
- You are debugging a failure that disappears after
localStorage.clear()but reappears later - You need confidence that no hidden state survives from a previous scenario
Manual cleanup still has a place when you are intentionally preserving one state item to test persistence or restoration.
Final takeaway
To test browser storage reset between runs without hiding real session bugs, do not treat cleanup as a blanket precondition. Treat it as a test design choice.
Clear the storage that would contaminate the scenario, keep the storage that is part of the feature, and add at least one persistence test that proves you are not deleting the very bug you wanted to catch.
FAQ
Should I clear all browser storage before every test?
No. Clear only what would make the test state-dependent in a misleading way. If the scenario is about persistence, logout, cart retention, or draft recovery, a full reset can hide the bug.
Is localStorage.clear() enough?
Usually not. It does not clear cookies, sessionStorage, IndexedDB, or service worker caches. It also may erase state you actually wanted to verify.
What is the safest way to isolate browser tests?
A fresh browser context or profile per test is the safest starting point. Then add targeted cleanup only if the app stores data outside the context boundary.
How do I know if a failure is caused by leaked state?
Re-run the scenario in a fresh context. If the failure disappears, leaked state is likely. If it remains, the app flow itself is more suspect.
Should logout tests start with a clean browser?
No. Logout tests need existing authenticated state so they can verify that the app removes it correctly.
Do I need to test IndexedDB separately?
Yes, if your app uses it for drafts, offline data, queues, or cached user state. It can produce failures that cookies and localStorage cleanup will never reveal.