PWA Today documentation

Service Worker Deployment Testing

Coordinate Vercel, Netlify, and AWS Amplify deployments to verify real service worker updates.

This guide explains how to let a PWA Today audit coordinate a production deployment so it can verify a real service worker update.

Use this flow when the audit must observe the transition from the currently deployed service worker to the new version. A normal audit that starts after deployment can test the resulting application, but it cannot observe that transition.

Related documentation:

How deployment testing works

The CLI coordinates the audit and deployment in this order:

  1. PWA Today opens the existing application and captures its service worker, document, and configured cached content.
  2. After the baseline is ready, the CLI runs the deployment command from pwa-check.yml.
  3. The deployment command starts the hosting-provider deployment and waits for it to finish.
  4. The CLI reports deployment completion to PWA Today.
  5. PWA Today waits for the new service worker and verifies its installation, activation, cache behavior, and configured content changes.
  6. The CLI runs the remaining runtime checks and creates the reports.

The deployment command must not run before step 2.

Tested scenarios

The deployment check is designed to catch release-transition failures, not just confirm that the new homepage eventually loads. It compares the release that was open before deployment with the release that becomes active after deployment.

Scenario What the audit does Failure it catches
Deployment handover Captures the current service worker and page before deployment, waits for the provider deployment, and confirms that the service worker file changed. The pipeline deploys too early, deploys the wrong commit, or does not publish a detectable worker update.
Old release while an update is waiting Leaves the old client controlled by the v1 worker, turns off the network, and checks that the old document’s scripts, styles, and configured deep routes still load with the same content. New HTML refers to assets that the old release cannot load, or the worker replaces/removes v1 cache entries before activation.
Release overlap during activation When enabled, keeps two v1 clients open, reloads the second tab, and observes whether it becomes v2 while the first tab remains v1. Once the worker is waiting, the harness uses Chrome's DevTools Protocol equivalent of the DevTools skipWaiting button. It then identifies a changed resource, disables the network, and verifies that the first tab receives the v1 fingerprint while the second receives v2. It also compares both cache generations without requiring an application-specific SKIP_WAITING message. The new worker deletes or overwrites the old chunk set while a v1 client is still open, or the v2 client cannot load its own assets offline.
New release activation Closes controlled old clients when necessary, waits for the new worker to activate, and opens a fresh controlled client. The worker remains stuck in waiting, does not control new pages, or activates with an invalid state.
New release integrity Discovers same-origin scripts, stylesheets, preloads, and runtime-loaded assets from the v2 document, verifies their hashes online, then requests the same assets with the network disabled. Production serves incomplete, mixed-version, missing, or corrupted assets after activation.
Offline navigation Opens the application and any configured integrityUrls while offline and confirms that the service worker returns a usable document. A deep route works online but fails after deployment when the user is offline.

The overlap scenario is currently enabled in the isolated deployment-test stack while it is being validated. The normal waiting, activation, asset integrity, and offline-navigation scenarios remain part of the regular deployment audit.

Prerequisites

You need:

  • A PWA Today customer account and access token.
  • The @pwa-today/pwa-check CLI installed in the application repository.
  • A stable public URL for the application.
  • Permission for the pipeline to start a deployment on the hosting provider.
  • A deployment that changes the service worker file.

Run this check against a production URL or a stable staging URL that keeps the previous deployment active until the audit has captured its baseline.

Configure pwa-check.yml

The service-worker-deployment check is not included automatically, even when the full profile is selected. Add it explicitly:

version: 1

audit:
  profile: full
  applicationId: example.com

  include:
    - service-worker-deployment

  options:
    service-worker-deployment:
      deploymentTimeout: 900000
      commandTimeout: 900000
      command:
        - ./scripts/deploy-and-wait.sh
      requiredCachedUrls:
        - /
        - /offline
      contentCheckUrls:
        - /
        - /app.js

qualityGate:
  minimumScore: 90
  failOn:
    - critical
    - high

reports:
  json: reports/pwa-audit.json
  junit: reports/pwa-audit.xml

Remove requiredCachedUrls and contentCheckUrls when the application does not need those additional assertions.

Make the deployment script executable:

chmod +x scripts/deploy-and-wait.sh

The command is executed directly without a shell. Keep shell expressions, pipes, redirects, and multiple deployment steps inside the script.

Control automatic deployments

The hosting provider must not deploy the commit immediately when it is pushed. That could replace the old service worker before PWA Today captures it.

For the production branch used by this audit:

  1. Prevent the hosting provider from automatically deploying the push.
  2. Let the CI/CD pipeline start pwa-check audit.
  3. Let pwa-check invoke deploy-and-wait.sh after the baseline is ready.
  4. Let the script start and monitor the provider deployment.

Automatic preview deployments on other branches can remain enabled.

Deployment script contract

Every provider-specific script must:

  • Start deployment of the commit being audited.
  • Wait until the provider reports that deployment succeeded.
  • Ensure the deployment is assigned to the URL being audited.
  • Exit with code 0 only after the new version is publicly available.
  • Exit with a non-zero code when deployment fails.

The CLI cancels the hosted deployment check when the script fails or exceeds commandTimeout. Cancellation releases the active check for the URL.

Vercel

Install the Vercel CLI in the application repository:

npm install --save-dev vercel

Add these secured CI/CD variables:

Variable Description
VERCEL_TOKEN Vercel access token allowed to deploy the project.
VERCEL_ORG_ID Vercel account or team ID that owns the project.
VERCEL_PROJECT_ID Vercel project ID.

The organization and project variables let the CLI identify the project in a non-interactive environment without running vercel link.

Create scripts/deploy-and-wait.sh:

#!/usr/bin/env bash

set -euo pipefail

: "${VERCEL_TOKEN:?VERCEL_TOKEN is required}"
: "${VERCEL_ORG_ID:?VERCEL_ORG_ID is required}"
: "${VERCEL_PROJECT_ID:?VERCEL_PROJECT_ID is required}"

npx vercel deploy \
  --prod \
  --yes \
  --token "$VERCEL_TOKEN"

Do not add --no-wait. The script must remain active until Vercel finishes the deployment and assigns it to the production domain.

If the application already creates a Vercel Build Output locally, use the equivalent vercel deploy --prebuilt --prod command instead.

Netlify

Install the Netlify CLI in the application repository:

npm install --save-dev netlify-cli

Add these secured CI/CD variables:

Variable Description
NETLIFY_AUTH_TOKEN Netlify personal access token allowed to deploy the site.
NETLIFY_SITE_ID Netlify project ID or project name.

Create scripts/deploy-and-wait.sh:

#!/usr/bin/env bash

set -euo pipefail

: "${NETLIFY_AUTH_TOKEN:?NETLIFY_AUTH_TOKEN is required}"
: "${NETLIFY_SITE_ID:?NETLIFY_SITE_ID is required}"

npx netlify deploy \
  --prod \
  --context production \
  --auth "$NETLIFY_AUTH_TOKEN" \
  --site "$NETLIFY_SITE_ID"

The deploy command builds the application and waits for the production deploy. If the pipeline already built the exact files that should be deployed, add --no-build and the application-specific --dir option.

AWS Amplify Hosting

This example applies to an Amplify Hosting branch connected to a Git repository. Disable automatic builds for the production branch so pushing the commit does not deploy it before the audit baseline is ready.

The pipeline needs AWS CLI version 2 and these variables:

Variable Description
AWS_REGION AWS Region containing the Amplify application.
AMPLIFY_APP_ID Amplify application ID.
AMPLIFY_BRANCH Amplify branch to deploy, such as main.

Authenticate the pipeline with an AWS role when the CI/CD provider supports workload identity. Otherwise, store AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as secured variables.

The AWS identity needs permission to call:

  • amplify:StartJob
  • amplify:GetJob

Create scripts/deploy-and-wait.sh:

#!/usr/bin/env bash

set -euo pipefail

: "${AWS_REGION:?AWS_REGION is required}"
: "${AMPLIFY_APP_ID:?AMPLIFY_APP_ID is required}"
: "${AMPLIFY_BRANCH:?AMPLIFY_BRANCH is required}"

job_id="$(
  aws amplify start-job \
    --region "$AWS_REGION" \
    --app-id "$AMPLIFY_APP_ID" \
    --branch-name "$AMPLIFY_BRANCH" \
    --job-type RELEASE \
    --query "jobSummary.jobId" \
    --output text
)"

if [[ -z "$job_id" || "$job_id" == "None" ]]; then
  echo "Amplify did not return a deployment job ID." >&2
  exit 1
fi

echo "Amplify deployment job $job_id started."

while true; do
  status="$(
    aws amplify get-job \
      --region "$AWS_REGION" \
      --app-id "$AMPLIFY_APP_ID" \
      --branch-name "$AMPLIFY_BRANCH" \
      --job-id "$job_id" \
      --query "job.summary.status" \
      --output text
  )"

  case "$status" in
    SUCCEED)
      echo "Amplify deployment completed."
      exit 0
      ;;
    FAILED|CANCELLED)
      echo "Amplify deployment ended with status: $status" >&2
      exit 1
      ;;
    PENDING|PROVISIONING|RUNNING|CANCELLING)
      echo "Amplify deployment status: $status"
      sleep 15
      ;;
    *)
      echo "Unexpected Amplify deployment status: $status" >&2
      exit 1
      ;;
  esac
done

StartJob builds the latest commit available on the configured Amplify branch. The polling loop does not exit successfully until Amplify reports SUCCEED.

Run the coordinated audit

The surrounding CI/CD job still retrieves the PWA Today access token before running the audit:

export PWA_AUDIT_TOKEN="$(
  curl --silent --show-error --fail \
    --user "$PWA_CLIENT_ID:$PWA_CLIENT_SECRET" \
    --request POST \
    https://api.pwa.today/token |
  node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).access_token'
)"

npx pwa-check audit "$PWA_AUDIT_URL"

Do not call deploy-and-wait.sh directly from the pipeline. The CLI invokes it only after PWA Today reports that the baseline is ready.

Expected progress

A deployment audit reports progress similar to:

Deployment check: starting
Deployment check: establishing-baseline
Deployment check: baseline-ready
Deployment check: deployment-reported
Deployment check: completed

The provider deployment output appears between baseline-ready and deployment-reported.

Troubleshooting

The deployment finishes before baseline-ready

The hosting provider is still deploying automatically from the Git push. Disable that production-branch deployment and let the provider script start it.

The command exits immediately

The provider command started an asynchronous deployment but did not wait for it. Use the provider’s blocking deploy command or poll its deployment status before exiting.

The check reports that the service worker did not change

The deployed service worker file is identical to the baseline. Change its generated output, cache version, revision manifest, or other build content so the browser can detect an update.

The command exceeds its timeout

Increase both commandTimeout and deploymentTimeout when normal provider deployments take longer than the configured values. Keep commandTimeout less than or equal to deploymentTimeout.

A deployment check is already active

A previous run is still active for the URL. Let it finish or cancel it before starting another deployment audit. The CLI automatically cancels its check when its own deployment command fails.

The provider deploys the wrong commit

Confirm that the provider branch points to the commit being audited before starting pwa-check. Do not reuse a deployment command that promotes an older preview or rebuilds an unrelated branch.

Security

  • Store provider tokens and PWA Today client credentials as secured CI/CD variables.
  • Give provider credentials access only to the application being deployed.
  • Prefer short-lived workload identity credentials over permanent AWS access keys.
  • Do not print tokens or include them in reports and artifacts.
  • Keep production deployment jobs restricted to trusted branches and users.