PWA Testing Guide
How to Test PWA Install Prompt with Playwright: Complete 2026 Guide
A scenario-by-scenario walkthrough of testing Progressive Web App install prompts with Playwright. The beforeinstallprompt event, web app manifest validation, custom install button UX, app scope boundaries, display mode detection, and the pitfalls that silently break PWA installability in production.
“Over 600 million users have installed PWAs to their home screens as of early 2026, driven by adoption from major platforms including Twitter, Starbucks, Pinterest, and Spotify.”
web.dev PWA adoption data
PWA Install Prompt End-to-End Flow
1. Why Testing PWA Install Prompts Is Harder Than It Looks
Progressive Web App installability depends on a chain of browser requirements that must all be satisfied simultaneously. The browser checks for a valid web app manifest with specific required fields, a registered service worker with a functional fetch event handler, HTTPS (or localhost for development), and a set of heuristics around user engagement. Only when every criterion passes does the browser fire the beforeinstallprompt event. Miss any single requirement and the event never fires, with no error message to explain why.
The complexity compounds because different browsers implement different subsets of the PWA install specification. Chromium browsers (Chrome, Edge, Opera) fire beforeinstallprompt and allow custom install buttons. Firefox does not fire the event at all and relies on its own address bar prompt. Safari on iOS adds PWAs via the Share menu with no programmatic hook. Your test suite needs to validate the underlying installability criteria (manifest, service worker, HTTPS) even on browsers that do not expose the prompt event, because those criteria determine whether your app appears as installable in the browser UI.
There are five structural reasons this flow is hard to test reliably. First, the beforeinstallpromptevent is asynchronous and fires at the browser's discretion, not on a predictable DOM event like a click handler. Second, the web app manifest is a separate HTTP resource that the browser fetches and parses independently from your page HTML. Third, the service worker lifecycle (installing, waiting, activating) introduces timing dependencies that can cause the install prompt to appear on the second page load but not the first. Fourth, the display field in the manifest controls how the installed app renders, and testing that the correct display mode is applied requires launching the app in standalone context. Fifth, the scope field restricts which URLs belong to the PWA, and navigating outside scope opens the system browser instead, which is invisible to a Playwright page context.
Browser Installability Check Chain
HTTPS
Secure context required
Manifest
Valid manifest.json linked
Icons
192px and 512px icons present
Service Worker
Registered with fetch handler
Engagement
User interaction heuristic
Event Fires
beforeinstallprompt dispatched
Custom Install Button Flow
Listen
window.addEventListener
Stash Event
Save prompt event reference
Show Button
Reveal install UI
User Clicks
Trigger prompt()
Native Dialog
Browser shows install dialog
appinstalled
Hide install button
A thorough PWA install test suite validates each link in this chain independently. The sections below walk through every scenario you need, with runnable Playwright TypeScript you can copy directly into your project.
2. Setting Up Your Test Environment
PWA install prompts require HTTPS in production, but Playwright tests typically run against localhost, which browsers treat as a secure context. This means you can test all installability criteria locally without configuring TLS certificates. However, your test server must serve a valid web app manifest and register a service worker for the install prompt to fire.
PWA Test Environment Checklist
- Local dev server running on localhost (secure context)
- Web app manifest linked in HTML <head> with <link rel='manifest'>
- Manifest includes name, short_name, start_url, display, icons (192px + 512px)
- Service worker registered with a fetch event handler
- Playwright configured with Chromium (beforeinstallprompt requires Chromium)
- Test fixtures ready for manifest and service worker manipulation
- Chrome DevTools Protocol access enabled for advanced assertions
Environment Variables
Playwright Configuration for PWA Testing
PWA install prompt testing requires Chromium specifically, because Firefox and WebKit do not fire the beforeinstallpromptevent. Configure your Playwright project to use Chromium with service worker support enabled. The key configuration is disabling the browser's automatic install prompt so your custom install button logic is testable.
Minimal Manifest and Service Worker for Testing
Your test fixtures need a valid manifest and service worker. Here is the minimum viable manifest that satisfies all Chromium installability requirements, along with a minimal service worker that includes the mandatory fetch handler.
3. Scenario: Validating the Web App Manifest
The web app manifest is the foundation of PWA installability. If any required field is missing, malformed, or points to a broken resource, the browser will silently skip the install prompt. This scenario validates that your manifest is correctly linked, parsable, and contains all required fields with valid values. It also verifies that the icons referenced in the manifest actually resolve to real image files of the correct dimensions.
Web App Manifest Validation
StraightforwardGoal
Fetch and parse the web app manifest, verify all required installability fields are present and valid, and confirm that referenced icons are accessible and correctly sized.
Preconditions
- App running at
APP_BASE_URL - Manifest linked in HTML head with
<link rel="manifest">
Playwright Implementation
What to Assert Beyond the UI
Manifest Assertions
- name and short_name are non-empty strings
- start_url resolves to a 200 response
- display is standalone, fullscreen, or minimal-ui
- At least 192x192 and 512x512 PNG icons exist
- scope field (if present) includes start_url
- theme_color is a valid CSS color
- Manifest Content-Type is application/manifest+json or application/json
4. Scenario: Intercepting the beforeinstallprompt Event
The beforeinstallprompt event is the critical gateway to custom PWA install experiences. Chrome fires this event when all installability criteria are met, and your application code is expected to listen for it, prevent the default browser prompt, stash the event reference, and use it later when the user clicks your custom install button. If your listener has a bug, registers too late, or fails to call preventDefault(), the browser shows its own mini-infobar instead of your carefully designed install experience.
Testing this event in Playwright requires a careful approach. You cannot directly fire beforeinstallprompt from JavaScript because the browser only dispatches it internally when the installability criteria are satisfied. Instead, your test must ensure all criteria are met (valid manifest, active service worker, HTTPS context) and then verify that your application code correctly handles the event by checking for observable side effects: the install button becoming visible, a state variable being set, or a data attribute being added to the DOM.
beforeinstallprompt Event Handling
ComplexGoal
Verify that the application correctly intercepts the beforeinstallprompt event, stashes the prompt reference, and reveals the custom install button as a result.
Preconditions
- All installability criteria met (manifest, SW, HTTPS)
- Application listens for
beforeinstallprompton page load - Running in Chromium (event is Chromium-only)
Playwright Implementation
What to Assert Beyond the UI
Beyond the visible install button, verify that your application's internal state is correct. Check that the stashed event reference is not null, that preventDefault()was called on the event (which suppresses the browser's default mini-infobar), and that analytics events for installability were tracked if your app implements them.
beforeinstallprompt Handling
test('beforeinstallprompt fires and is handled', async ({ page }) => {
await page.addInitScript(() => {
(window as any).__bipFired = false;
window.addEventListener('beforeinstallprompt', () => {
(window as any).__bipFired = true;
});
});
await page.goto('/');
await page.waitForFunction(
() => navigator.serviceWorker.controller !== null,
{ timeout: 10_000 }
);
const installBtn = page.getByRole('button', { name: /install/i });
await expect(installBtn).toBeVisible({ timeout: 15_000 });
const fired = await page.evaluate(() => (window as any).__bipFired);
expect(fired).toBe(true);
});6. Scenario: Display Mode Detection
The display field in your manifest controls how the installed PWA renders. standalone removes the browser chrome (address bar, tabs) and makes the app look native. fullscreen hides everything including the status bar. minimal-ui keeps a small navigation strip. Your application can detect its display mode at runtime using the CSS media feature display-mode or the JavaScript matchMedia API, and adapt its UI accordingly (for example, showing a back button when there is no browser back button in standalone mode).
Testing display mode requires verifying two things: that the manifest declares the correct display mode, and that your application correctly responds to the active display mode at runtime. Playwright can emulate display modes using the CDP session to override the display-mode media feature, letting you test standalone behavior without actually installing the PWA.
Display Mode Detection and UI Adaptation
ModerateGoal
Verify that the manifest declares standalone display mode and that the application adapts its UI when running in standalone context (for example, showing a custom back button or hiding the install prompt).
Playwright Implementation
Display Mode Testing
test('display mode: standalone detection', async ({ page }) => {
const cdpSession = await page.context().newCDPSession(page);
await cdpSession.send('Emulation.setEmulatedMedia', {
features: [{ name: 'display-mode', value: 'standalone' }],
});
await page.goto('/');
const isStandalone = await page.evaluate(() =>
window.matchMedia('(display-mode: standalone)').matches
);
expect(isStandalone).toBe(true);
const backBtn = page.getByRole('button', { name: /back/i });
await expect(backBtn).toBeVisible();
});7. Scenario: App Scope and Navigation Boundaries
The scope field in the web app manifest defines which URL paths belong to the installed PWA. When the user navigates to a URL inside the scope, the navigation stays within the PWA window. When the user navigates to a URL outside the scope, the system browser opens instead. This is a critical UX boundary that can silently break if your app has links to external domains, subdomains, or paths outside the declared scope.
For example, if your manifest declares scope: "/app/" but your navigation includes a link to /blog/, clicking that link in the installed PWA opens the system browser. This disorients users who expect to stay inside the app. Testing scope boundaries ensures that all internal navigation stays within the PWA and that external links are handled gracefully.
App Scope Navigation Boundaries
ModerateGoal
Verify that the manifest scope includes all navigable routes in the application, and that links to external URLs or out-of-scope paths are handled explicitly.
Playwright Implementation
8. Scenario: Service Worker Registration and Fetch Handler
A registered service worker with a fetch event handler is a hard requirement for the install prompt. Chrome will not fire beforeinstallprompt unless a service worker is active and includes at least a no-op fetch listener. This requirement exists because installable PWAs are expected to work offline or at least show a meaningful offline fallback, and the fetch handler is what enables that.
Testing service worker registration goes beyond checking that navigator.serviceWorker.controller is not null. You need to verify the service worker activates without errors, that it has a fetch handler registered, and that it responds to fetch requests appropriately. A common failure mode is a service worker that registers successfully but throws an error during activation, leaving the install prompt blocked with no visible indication in the page.
Service Worker Validation
ModerateGoal
Verify that the service worker registers, activates, and includes a fetch handler. Confirm that the service worker does not throw errors during installation or activation.
Playwright Implementation
9. Common Pitfalls That Break PWA Install Tests
Manifest MIME Type
Chrome accepts application/json and application/manifest+json for the manifest, but some CDNs and static hosting providers serve .json files as text/plain. While Chromium still parses the manifest in most cases, the Lighthouse installability audit will flag this as an error. Ensure your server returns the correct Content-Type header.
Service Worker Scope Mismatch
The service worker's scope is determined by its file location unless overridden with a Service-Worker-Allowed header. If your service worker is at /js/sw.js, its default scope is /js/, not /. This means pages at the root path will not be controlled by the service worker, and the install prompt will not fire. Always place your service worker at the root of your site, or set the scope explicitly during registration and configure the Service-Worker-Allowed response header.
Icon Size Requirements
Chrome requires at least one icon at 192x192 pixels and one at 512x512 pixels. If your manifest only declares a 144x144 icon (common in older PWA tutorials), the install prompt will not fire on Chrome 117 and later. Additionally, the icons must be actual PNG or WebP images. Declaring an SVG icon at those sizes does not satisfy the requirement because Chrome cannot verify the raster dimensions of an SVG.
beforeinstallprompt Listener Timing
The beforeinstallprompt event fires once per page load. If your event listener registers after the event has already fired (for example, inside a lazy-loaded React component that mounts after the initial render), you will never capture the event. Always register the listener in a script that runs synchronously in the document head, or in a useEffect with an empty dependency array at the root of your React tree. A common pattern is to add the listener in a <script> tag in the HTML head that stashes the event on window, then read it from your framework code.
Testing in Non-Chromium Browsers
Firefox and Safari do not implement beforeinstallprompt. If your test suite runs across multiple browsers, the install button tests will fail on Firefox and WebKit. Structure your test suite to separate installability criteria tests (manifest validation, service worker checks) from install prompt tests. The criteria tests work on all browsers. The prompt tests should be scoped to Chromium-only test projects in your Playwright configuration.
PWA Install Prompt Anti-Patterns
- Registering beforeinstallprompt listener inside lazy-loaded component
- Serving manifest with text/plain Content-Type
- Placing service worker in a subdirectory without scope override
- Using only SVG icons without raster fallbacks
- Testing install prompt on Firefox or Safari (unsupported)
- Forgetting to call preventDefault() on the event
- Not handling the dismissed outcome from userChoice
- Hardcoding a manifest start_url that differs from deployed path
10. Writing These Scenarios in Plain English with Assrt
Every scenario above involves Playwright code that is tightly coupled to browser APIs, CDP sessions, and service worker lifecycle internals. The manifest validation test reads JSON and asserts on field names. The display mode test opens a CDP session and injects emulation settings. The install button test monkey-patches window event listeners to track internal state. When Chrome ships a new installability requirement or changes how CDP exposes service worker metadata, your tests break even though your PWA still works correctly.
Assrt lets you describe these scenarios as intent, not implementation. The beforeinstallprompt test from Section 4 demonstrates this well. In raw Playwright, you need to inject scripts with addInitScript, wrap native event listeners, and evaluate window properties. In Assrt, you describe what you expect to happen and let the framework resolve the browser-specific mechanics.
Assrt compiles each scenario block into the Playwright TypeScript you saw in the preceding sections, including the CDP session setup, the service worker lifecycle checks, and the event listener interception. When Chrome changes its installability requirements (as it did in Chrome 93 with the offline capability mandate, and again in Chrome 117 with stricter icon requirements), Assrt detects the failure, analyzes the new criteria, and opens a pull request with the updated assertions. Your scenario files remain stable.
Start with the manifest validation scenario. It has no browser API dependencies and works across all browsers. Once it is green, add the service worker validation, then the beforeinstallprompt interception, then the install button lifecycle, then the display mode detection. In a single afternoon you can build a comprehensive PWA installability test suite that catches the silent regressions which most teams only discover when users report that the install option disappeared.
Related Guides
How to Test Geolocation Prompt
A practical guide to testing browser geolocation permission prompts with Playwright....
How to Test Service Worker Offline
A practical guide to testing service worker offline behavior with Playwright. Covers...
How to Test Toast Notifications
A practical, scenario-by-scenario guide to testing toast notifications with Playwright....
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.