⚡️ Offline-First Apps: How do you TEST the "impossible"?
Designing an end-to-end test for a complex web app's offline capabilities with Playwright, focusing on Service Workers, IndexedDB, and data sync, demands a sophisticated strategy.
📌 Problem Statement
Our field technician SPA needs robust offline-first validation:
• Create/edit reports while offline.
• Automatic backend sync on reconnect.
• Data consistency across sessions (offline, close, reopen).
• Accurate UI status for offline/syncing.
💡 Solution & Code Walkthrough
Playwright offers powerful APIs for network control and browser context interaction.
✅ Simulate Network Changes:
Use setOffline() to toggle connectivity.
// Go offline
await page.context().setOffline(true);
// Go online
await page.context().setOffline(false);
✅ Verify Service Worker (SW) Registration:
Ensure the SW is active and controlling the page.
const serviceWorker = (await page.context().serviceWorkers())[0];
expect(serviceWorker).toBeDefined(); // SW registered
// await serviceWorker.evaluate(() => self.registration.scope); // Evaluate SW state
✅ Inspect IndexedDB Data:
Access IndexedDB via page.evaluate() to assert local data state.
const localData = await page.evaluate(async () => {
const db = await new Promise(resolve => {
const request = indexedDB.open('MyAppDB', 1);
request.onsuccess = () => resolve(request.result);
});
const transaction = db.transaction(['reports'], 'readonly');
const store = transaction.objectStore('reports');
return new Promise(resolve => store.getAll().onsuccess = e => resolve(e.target.result));
});
expect(localData).toContainEqual({ id: 'report-123', status: 'pending' });
✅ Trigger & Monitor Data Synchronization:
Toggle network, then wait for UI feedback or direct IndexedDB updates.
// Perform offline action (e.g., create report)
await page.context().setOffline(true);
await page.locator('#create-report').click();
// Go online to trigger sync
await page.context().setOffline(false);
// Wait for sync completion UI element
await expect(page.locator('.sync-status')).toHaveText('Synced!');
✅ Handle Race Conditions:
Use waitForFunction or waitForResponse to await specific state changes.
// Wait for IDB update after sync
await page.waitForFunction(async () => {
// Assume getDbConnection() is a helper for IndexedDB connection
const db = await getDbConnection();
const transaction = db.transaction(['reports'], 'readonly');
const store = transaction.objectStore('reports');
const reports = await new Promise(resolve => store.getAll().onsuccess = e => resolve(e.target.result));
return reports.some(r => r.status === 'synced');
}, { timeout: 10000 });
🔑 Key Takeaways
• page.context().setOffline() is crucial for network simulation.
• page.context().serviceWorkers() provides direct SW access.
• page.evaluate() bridges Playwright to browser-side APIs (IndexedDB).
• Strategic use of waitFor methods prevents flaky tests due to async operations.
❓ Quick Summary Q&A
Q: How to simulate offline? A: page.context().setOffline(true).
Q: How to check IndexedDB? A: Use page.evaluate() to run browser-side IDB code.
Q: Why waitForFunction? A: To reliably await dynamic, asynchronous state changes.
TAGS: playwright, e2e testing, offline-first, service workers, indexeddb, automation, web testing
────────────────────────────────────────
Download our app for more dev insights & interview prep!
────────────────────────────────────────
📲 𝐅𝐑𝐄𝐄 𝐌𝐎𝐁𝐈𝐋𝐄 𝐀𝐏𝐏 — 𝟔𝟎𝟎+ 𝐒𝐃𝐄𝐓 𝐐&𝐀𝐬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:
🤖 𝐆𝐨𝐨𝐠𝐥𝐞 𝐏𝐥𝐚𝐲 (𝐀𝐧𝐝𝐫𝐨𝐢𝐝):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20261012
🍎 𝐀𝐩𝐩 𝐒𝐭𝐨𝐫𝐞 (𝐢𝐎𝐒):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20261012&mt=8
────────────────────────────────────────
────────────────────────────────────────
Top comments (0)