How to Test Offline Mode on Web (Complete Guide)

Modern web apps rely on service workers, caching strategies, and background sync to deliver a usable experience when the network disappears. Users expect forms to stay editable, media to keep playing,

By · April 23, 2026 · 14 min read · How-To Guides

Why Offline Mode Testing Matters for Web Applications

Modern web apps rely on service workers, caching strategies, and background sync to deliver a usable experience when the network disappears. Users expect forms to stay editable, media to keep playing, and critical actions to queue for later submission. When the offline path is broken, the result is lost data, frustrated users, and a spike in support tickets.

Testing offline behavior is not a nicety; it is a risk‑mitigation activity. A missing fallback for a fetch handler can turn a simple navigation into a blank page. A mis‑configured cache can serve stale JSON that violates business rules. A button that relies on a live API call may throw an unhandled promise rejection, causing the JavaScript runtime to crash the tab.

Because offline mode touches every layer—HTML, CSS, JavaScript, service workers, IndexedDB, and server‑side APIs—defects often hide behind unit tests that mock network success. Only when the real browser encounters a true “no‑network” state do those assumptions break. A disciplined offline test plan catches those gaps before they reach production.

Common Failure Modes When Going Offline

Failure CategoryTypical SymptomRoot Cause
UI freeze / blank screenWhite page, spinner never stopsService worker fetch handler throws, or navigator.onLine check blocks rendering
Data lossForm inputs reset after reconnectionOptimistic UI updates not persisted to IndexedDB before offline
Stale data displayCached API response shows outdated infoCache‑first strategy without versioning or cache‑busting
Accessibility breakScreen reader announces “button disabled” incorrectlyARIA states tied to network status not updated when offline
Security exposureSensitive token leaked in console errorUncaught fetch rejection logs full request URL with auth header
Infinite retry loopBrowser repeatedly attempts fetch, draining batteryMissing if (!navigator.onLine) return; guard in retry logic
Broken deep linkURL opens to error page instead of app shellService worker fails to match navigation request to cached fallback

Each of these symptoms can be reproduced deliberately, but many only appear under specific timing conditions (e.g., network drops mid‑fetch, or when the user toggles airplane mode while a POST is in flight).

Test Matrix for Offline Mode

Below is a comprehensive matrix that covers happy paths, error paths, edge cases, accessibility, and security/privacy concerns. Use it as a checklist when designing manual or automated tests.

Test IDScenarioPreconditionsStepsExpected ResultPass/Fail Criteria
O-01App loads with no networkDevice offline, service worker registeredOpen URLApp shell loads, cached UI visibleUI renders within 2 s, no console errors
O-02Navigation to uncached routeDevice offline, SW with fallback to offline.htmlClick link to /profileoffline.html displayedCorrect fallback shown, no 404
O-03Form submission while offlineDevice offline, form with client‑side validationFill form, click SubmitData stored in IndexedDB, UI shows “queued”Entry appears in DB, UI reflects pending state
O-04Form submission after reconnectionDevice goes online after O-03Wait for background sync or manual retryQueued request sent, server responds 200, UI updates to “sent”Request appears in network tab, DB entry cleared
O-05Media playback continuationDevice offline, audio/video cached via SWStart playback, go offlinePlayback continues without interruptionNo stalls, media events fire as expected
O-06Cache update raceDevice online, new version deployed, then go offline quicklyReload, then disable network before SW finishes updateOld cached version served, no mix of old/new assetsConsistent version, no 404 on assets
O-07Accessibility of offline indicatorsDevice offline, ARIA live region for statusObserve screen reader outputLive region announces “offline, queued changes”Text matches spec, no silence
O-08Error handling UIDevice offline, fetch fails with 500 simulatedTrigger action that calls APIInline error message appears, not console onlyMessage visible, focus trapped if modal
O-09Security leakage checkDevice offline, fetch with auth header failsOpen devtools consoleNo auth token logged in error outputConsole clean of sensitive data
O-10Infinite retry preventionDevice offline, retry logic with exponential backoffInitiate failing requestRetry attempts stop after max attemptsNo more than N retries observed
O-11Deep link resilienceDevice offline, user receives push notification with deep linkTap notificationApp shell loads, cached route shownNo blank page, fallback shown if route uncached
O-12Concurrent offline/online togglesDevice toggles airplane mode rapidlySwitch offline/online 5 × within 10 sApp remains stable, no crashesNo unhandled promise rejections, UI responsive
O-13IndexedDB quota exceededDevice offline, large blob queued repeatedlyFill form with 5 MB attachment 20 timesOlder entries evicted per DB policy, no crashApp stays usable, logs quota warnings
O-14Service worker unregistrationDevice offline, user clears site dataRemove SW via devtools, reloadApp falls back to network‑only mode, shows offline messageNo JavaScript errors, UI indicates offline
O-15Cross‑origin resource fetchDevice offline, attempt to load third‑party scriptInsert <script src="https://cdn.example.com/lib.js">Request fails gracefully, fallback UI loadsNo console error that breaks main thread

Each row isolates a single variable, making it easy to automate or manually verify.

Manual Testing Approach Step‑by‑Step

  1. Prepare the environment
  1. Baseline load
  1. Navigate through cached routes
  1. Interact with offline‑capable features
  1. Simulate reconnection
  1. Accessibility audit
  1. Security check
  1. Repeat with variations

Manual testing is valuable for exploratory work, especially when you want to see how the UI feels under flaky conditions. However, it is hard to repeat at scale, which is why we complement it with automation.

Automated Testing Strategies

Using Service Worker Mocks

Unit tests can instantiate a service worker in a testing environment (e.g., using workbox-window or a custom mock) and feed it fabricated fetch events.


// test/sw.fetch.test.js
import { register } from 'workbox-window';
import { createFetchEvent } from './testHelpers';

describe('SW fetch handler – offline', () => {
  let sw;

  beforeAll(() => {
    sw = register('/sw.js', { scope: '/' });
  });

  test('returns offline fallback for unknown route', async () => {
    const event = createFetchEvent({
      request: new Request('/profile', { method: 'GET' }),
    });
    await sw.active.postMessage({ type: 'MOCK_FETCH', event });
    const response = await event.waitUntil(
      caches.match(event.request)
    );
    expect(response).toBeDefined();
    expect(await response.text()).toContain('Offline');
  });
});

The mock lets you assert that the SW returns a cached offline.html without needing to toggle the network.

Network Throttling Tools

Chrome DevTools provides a programmatic interface via the Chrome DevTools Protocol (CDP). Tools like puppeteer or playwright can set the network condition to “offline” before each test.


// playwright.config.js
module.exports = {
  use: {
    headless: false,
    // launch with offline flag
    launchOptions: {
      args: ['--disable-background-networking'],
    },
  },
};

 // test file
import { test, expect } from '@playwright/test';

test('app loads offline', async ({ page }) => {
  await page.context().setOffline(true);
  await page.goto('/');
  await expect(page.locator('h1')).toHaveText(/Welcome/);
});

Puppeteer offers the same API: await page.setOffline(true);.

End‑to‑End Frameworks with Offline Plugins

Cypress does not natively support offline mode, but you can combine it with cypress-network-idle or manually alter navigator.onLine.


// cypress/integration/offline.spec.js
describe('Offline behavior', () => {
  beforeEach(() => {
    // Overwrite the property
    cy.window().then((win) => {
      Object.defineProperty(win.navigator, 'onLine', {
        get: () => false,
      });
    });
  });

  it('shows offline banner', () => {
    cy.visit('/');
    cy.get('[data-test="offline-banner"]').should('be.visible');
  });

  it('stores form data in IndexedDB', () => {
    cy.get('[data-test="name-input"]').type('Ada');
    cy.get('[data-test="submit"]').click();
    cy.window().then((win) => {
      return win.indexedDB.open('app-db', 1).then((db) => {
        const tx = db.transaction('outbox', 'readonly');
        const store = tx.objectStore('outbox');
        return store.getAll();
      }).then((rows) => {
        expect(rows).to.have.lengthOf(1);
        expect(rows[0].payload.name).to.eq('Ada');
      });
    });
  });
});

Playwright offers a more straightforward approach with setOffline.


// playwright/offline.test.js
import { test, expect } from '@playwright/test';

test.describe('Offline mode', () => {
  test.beforeEach(async ({ page }) => {
    await page.context().setOffline(true);
  });

  test('retries queued requests on reconnect', async ({ page }) => {
    await page.goto('/');
    await page.fill('[data-test="email"]', 'test@example.com');
    await page.click('[data-test="login"]');
    // Wait for IndexedDB entry
    await page.waitForFunction(() =>
      window.indexedDB.open('app-db', 1).then((db) => {
        const tx = db.transaction('outbox', 'readonly');
        return tx.objectStore('outbox').getAll();
      }).then((rows) => rows.length > 0)
    );

    // Go online
    await page.context().setOffline(false);
    // Expect network request
    await page.waitForResponse((resp) =>
      resp.url().endsWith('/login') && resp.status() === 200
    );
    // UI should show success
    await expect(page.locator('[data-test="login-success"]')).toBeVisible();
  });
});

Unit/Integration Tests for Fetch Handlers

If your app isolates network calls in a service layer, you can mock fetch with libraries like msw (Mock Service Worker) or fetch-mock.


// src/services/api.js
export async function getUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error('Network error');
  return res.json();
}

// src/services/api.test.js
import { getUser } from './api';
import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json({ id: req.params.id, name: 'Test' }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('returns data when network ok', async () => {
  const user = await getUser(42);
  expect(user.name).toBe('Test');
});

test('throws when offline', async () => {
  server.use(
    rest.get('*', (req, res, ctx) => {
      // Simulate network failure by not responding
      return res.networkError('Failed to fetch');
    })
  );
  await expect(getUser(1)).rejects.toThrow(/Network/);
});

These tests guarantee that the error path is handled correctly before the SW even sees the request.

Edge Cases that Surface Only in Production

Even with thorough lab testing, certain conditions only appear when real users interact with the app under unpredictable circumstances.

These scenarios rarely appear in scripted test suites but can be exercised with tools that manipulate the browser’s internal state (e.g., Chrome’s --disable-background-networking, --disk-cache-size, or custom DevTools overrides).

Accessibility and Security Considerations in Offline Mode

Accessibility

Security

Checklist for Offline Mode Readiness

✅ ItemDescription
Service worker registered and controlling the pageVerify via navigator.serviceWorker.controller
offline.html or app shell cached under precacheEnsure navigation to unknown routes shows fallback
All critical UI components functional without networkForms, media, navigation, alerts
Optimistic UI writes persisted to IndexedDB before offlineCheck DB after each user action
Background sync or retry mechanism re‑sends queued requestsConfirm network calls after setOffline(false)
No console errors containing auth tokens or PIIReview Console while offline
Accessible offline status announced via ARIA live regionTest with screen reader
Touch targets meet 44 × 44 dpManual inspection or automated axe test
CSP and SRI headers present on SW and cached resourcesUse curl -I or devtools Network pane
Storage quota handled gracefully (QuotaExceededError)Simulate full storage and verify UI stays usable
Battery saver / background sync restrictions respectedTest with device settings or emulator flags
Network‑type detection (e.g., navigator.connection) used to adjust aggressiveness of prefetchOptional but recommended for low‑bandwidth

Mark each item as done before signing off a release.

How Autonomous Persona‑Driven Exploration (SUSA) Finds Hidden Offline Bugs

Traditional test suites follow predetermined paths; they rarely simulate the erratic behavior of real users who toggle airplane mode mid‑gesture, rapidly switch between apps, or use assistive technology in unconventional ways. SUSA’s autonomous agent changes that equation by combining three core techniques:

  1. Goal‑free exploration – The agent starts from the entry point and follows any clickable element, scrollable region, or deep link it discovers, without a pre‑written script.
  2. Persona profiles – Each run can be configured with a distinct behavior set (e.g., “impatient” clicks repeatedly before waiting for responses, “elderly” uses larger tap targets and slower gestures, “adversarial” attempts to trigger error states by rapid network toggling).
  3. Cross‑session memory – The agent records which screens it has visited, which actions led to dead ends, and which network conditions caused failures. On subsequent runs it prioritizes unexplored states and retries flaky scenarios with varied timing.

When testing offline mode, SUSA automatically:

Because the agent does not rely on hardcoded assertions like “the button must be disabled,” it can discover bugs that a scripted test would never anticipate—for example, a scenario where a user opens a modal, goes offline, then closes the modal via the ESC key, leaving a stray focus trap that only appears when the modal’s exit animation is interrupted by a network loss.

In practice, autonomous exploration catches offline-mode defects a Cypress or Playwright suite tends to miss — not because its assertions are better, but because it visits states nobody wrote a spec for. We publish no measured comparison; the areas where the difference shows up are:

Integrating SUSA into your CI pipeline is straightforward:


# Install the agent
pip install susatest-agent

# Run a persona‑driven exploration with offline focus
susatest-agent test https://app.example.com \
  --persona impatient \
  --output ./susareport.json

The resulting report includes a list of discovered flows, each tagged with PASS/FAIL, screenshots, and console excerpts, giving developers actionable evidence without writing a single test line.

Closing Takeaways

Offline mode is not a toggle‑switch feature; it is a set of contracts between your service worker, caching strategy, persistence layer, and UI. Breaking any of those contracts produces data loss, confusing UX, or security exposure.

A disciplined approach combines:

By treating offline mode as a first‑class citizen in your test strategy—just like login flows or checkout—you ship web applications that remain useful, trustworthy, and resilient when the network disappears.

---

*Keep this guide bookmarked. Return to it whenever you add a new caching rule, adjust a service worker, or introduce a feature that writes to IndexedDB. The effort you invest now prevents the costly, frustrating bugs that only surface when users lose connectivity.*

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 11 real users — finds bugs, accessibility violations, and security issues. No scripts. New to the category? Start with what autonomous product intelligence & QA means.

Try SUSA Free