Back to Repositories

Testing Progressive Web App Integration in Vue CLI

This test suite evaluates the Progressive Web App (PWA) functionality in Vue CLI projects, focusing on service worker registration, manifest generation, and offline capabilities. It validates the complete build process and runtime behavior of PWA features.

Test Coverage Overview

The test suite provides comprehensive coverage of PWA implementation in Vue CLI projects.

Key areas tested include:
  • Service worker registration and functionality
  • Build artifact generation (manifest.json, icons)
  • HTML meta tags and PWA-specific directives
  • Offline content caching
  • Browser integration and runtime behavior

Implementation Analysis

The testing approach combines build output validation with runtime behavior verification using Puppeteer. The test implements a complete end-to-end workflow, from project creation to browser-level service worker functionality.

Notable patterns include:
  • Project scaffolding with PWA plugin
  • Build output verification
  • Service worker initialization checks
  • Browser-based functionality testing

Technical Details

Testing tools and configuration:
  • Jest as the test runner
  • Puppeteer for browser automation
  • Vue CLI test utilities
  • Portfinder for dynamic port allocation
  • Custom server setup for serving built assets

Best Practices Demonstrated

The test exemplifies robust testing practices for PWA functionality verification.

Key practices include:
  • Isolated test environment setup
  • Comprehensive artifact verification
  • Runtime behavior validation
  • Proper resource cleanup
  • Async/await pattern usage
  • Error handling and timeout management

vuejs/vue-cli

packages/@vue/cli-plugin-pwa/__tests__/pwaPlugin.spec.js

            
jest.setTimeout(50000)

const path = require('path')
const portfinder = require('portfinder')
const createServer = require('@vue/cli-test-utils/createServer')
const { defaultPreset } = require('@vue/cli/lib/options')
const create = require('@vue/cli-test-utils/createTestProject')
const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer')

let server, browser
test('pwa', async () => {
  // it's ok to mutate here since jest loads each test in a separate vm
  defaultPreset.plugins['@vue/cli-plugin-pwa'] = {}
  const project = await create('pwa-build', defaultPreset)
  expect(project.has('src/registerServiceWorker.js')).toBe(true)

  const { stdout } = await project.run('vue-cli-service build')
  expect(stdout).toMatch('Build complete.')

  expect(project.has('dist/index.html')).toBe(true)
  expect(project.has('dist/favicon.ico')).toBe(true)
  expect(project.has('dist/js')).toBe(true)
  expect(project.has('dist/css')).toBe(true)

  // PWA specific files
  expect(project.has('dist/manifest.json')).toBe(true)
  expect(project.has('dist/img/icons/android-chrome-512x512.png')).toBe(true)

  // Make sure the base preload/prefetch are not affected
  const index = await project.read('dist/index.html')

  // should split and preload app.js & vendor.js
  // expect(index).toMatch(/<link [^>]+js\/app[^>]+\.js" rel="preload" as="script">/)
  // expect(index).toMatch(/<link [^>]+js\/chunk-vendors[^>]+\.js" rel="preload" as="script">/)
  // should preload css
  // expect(index).toMatch(/<link [^>]+app[^>]+\.css" rel="preload" as="style">/)

  // PWA specific directives
  expect(index).toMatch(`<link rel="manifest" href="/manifest.json">`)
  // favicon is not minified because it's technically a comment
  expect(index).toMatch(`<!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]-->`)
  expect(index).toMatch(`<meta name="apple-mobile-web-app-capable" content="no">`)

  // should import service worker script
  const main = await project.read('src/main.js')
  expect(main).toMatch(`import './registerServiceWorker'`)

  const port = await portfinder.getPortPromise()
  server = createServer({ root: path.join(project.dir, 'dist') })

  await new Promise((resolve, reject) => {
    server.listen(port, err => {
      if (err) return reject(err)
      resolve()
    })
  })

  const launched = await launchPuppeteer(`http://localhost:${port}/`)
  browser = launched.browser

  // workbox plugin fetches scripts from CDN so it takes a while...
  await new Promise(resolve => setTimeout(resolve, process.env.CI ? 5000 : 2000))
  const logs = launched.logs
  expect(logs.some(msg => msg.match(/Content has been cached for offline use/))).toBe(true)
  expect(logs.some(msg => msg.match(/App is being served from cache by a service worker/))).toBe(true)
})

afterAll(async () => {
  if (browser) {
    await browser.close()
  }
  if (server) {
    server.close()
  }
})