# Test a PWA

> Test a PWA in Chrome DevTools: read the Installability section, drive the service worker from the Service workers pane, and install on a real phone.

At the end of this guide you can answer, from Chrome DevTools, why a page is or is not
installable, step a service worker through install, waiting, and activation on demand,
simulate offline without unplugging the machine, and confirm the install flow on a real
Android device. The **Application** panel does all of it; Lighthouse's PWA category is
deprecated and is not part of the procedure.

You need the app served over HTTPS or `localhost`, Chrome with DevTools, and for the last
step an Android phone with USB debugging enabled. The names below are the English DevTools
labels.

## 1. Read the manifest and its Installability section

Open DevTools, select **Application**, then **Manifest**. The **Identity** and
**Presentation** sections show the manifest fields in readable form, **Icons** lists each
declared icon (the **Show only the minimum safe area for maskable icons** checkbox previews
the cropped safe area), and **Shortcut #N** and **Screenshot #N** sections follow. When an
icon fails to load or a required field is missing, an **Installability** section appears
with the reason. Keep the **Console** drawer open while you click the address bar's
**Install** button: it logs manifest problems and the install lifecycle.

A manifest that passes has at least this:

```json
{
  "name": "Example PWA",
  "short_name": "Example",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
```

The full list of conditions is in
[Installability criteria: what makes a PWA installable](/reference/installation/installability-criteria/).

## 2. Drive the worker from the Service workers pane

**Application** > **Service workers** lists the registration for the open page with its
**Source**, **Status**, and **Clients** lines. The controls, and when to use each:

- **Offline** puts DevTools into offline mode, the same as the Network panel's **Offline**
  throttling preset. Reload with it checked to see what the worker serves without a network.
- **Update on reload** refetches the worker script on every navigation, installs it even
  when it is byte-identical so `install` runs again, skips the waiting phase, and then
  navigates. It removes the "close every tab" step while you edit `sw.js`; turn it off
  before you test the real update path.
- **Bypass for network** sends requests straight to the network, which tells you whether a
  bug lives in the worker or in the page.
- **Push** and **Sync** fire a `push` event with the typed payload and a `sync` event
  without a server.
- **stop** halts the running worker; the next event starts it with fresh global state,
  which surfaces code that assumed in-memory variables survive.
- **Unregister** removes the registration; **Update** performs a one-time update check.

The **Update Cycle** table shows the worker's activities and elapsed times, such as
install, wait, and activate; the **Expand** buttons reveal exact timestamps.

:::observed
After editing `sw.js` and reloading once without **Update on reload**, the pane shows two
entries: the old worker with **Status** `activated and is running`, and the new one as
`waiting to activate` with a **skipWaiting** link beside it. Clicking the link activates
the new worker without closing the tab, the same effect as calling `self.skipWaiting()`
from the worker.
:::

## 3. Detect a registration before asserting on it

A test harness that assumes `navigator.serviceWorker` exists throws in browsers without
the API, and `getRegistration()` resolves with `undefined` when nothing is registered for
the scope. Check both so the harness reports a reason instead of a stack trace.

```js
export async function serviceWorkerState() {
  if (!('serviceWorker' in navigator)) {
    return { ok: false, reason: 'Service workers are not supported in this browser.' };
  }
  const registration = await navigator.serviceWorker.getRegistration();
  if (!registration) {
    return { ok: false, reason: 'No service worker is registered for this page yet.' };
  }
  const worker = registration.active || registration.waiting || registration.installing;
  return { ok: true, state: worker ? worker.state : 'unknown', scope: registration.scope };
}
```

Run it from the Console: `(await serviceWorkerState()).state` prints `activated` once the
first install has finished.

## 4. Install on a real phone over remote debugging

Desktop Chrome's **Install** button cannot reproduce the Android flow (WebAPK minting, the
home screen icon, the splash screen), and the Lighthouse PWA audits that once covered this
are deprecated in favour of the installability criteria and this panel. Connect the phone
over USB, open `chrome://inspect/#devices` on the desktop, select the page, and in Chrome
on the phone open the three-dot menu and choose **Install app** (or **Add to Home screen**
on some devices). The desktop DevTools instance attached to the phone shows the same
**Manifest** and **Service workers** panes, so the Installability section from step 1
applies to the device you are holding. When the icon appears on the home screen and the
app launches full-screen with the manifest's `theme_color`, the install path is verified.

## See also

- [Service workers: what they are, and a minimal one](/reference/service-worker/)
- [Debugging service workers](/reference/service-worker/debugging/)
- [Debug a PWA](/guides/debugging/)
- [Installability criteria: what makes a PWA installable](/reference/installation/installability-criteria/)
- [Debug Progressive Web Apps](https://developer.chrome.com/docs/devtools/progressive-web-apps/) (developer.chrome.com)
- [The service worker lifecycle](https://web.dev/articles/service-worker-lifecycle) (web.dev)
- [ServiceWorkerContainer: getRegistration() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/getRegistration) (developer.mozilla.org)

← Back to the [Guides](/guides/) overview.