forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1/**
2 * Lighthouse CI puppeteer setup script.
3 *
4 * Sets the color mode (light/dark) before running accessibility audits
5 * and intercepts client-side API requests using the same fixture data
6 * as the Playwright E2E tests.
7 *
8 * The color mode is determined by the LIGHTHOUSE_COLOR_MODE environment variable.
9 * If not set, defaults to 'dark'.
10 *
11 * Request interception uses CDP (Chrome DevTools Protocol) Fetch domain
12 * at the browser level, which avoids conflicts with Lighthouse's own
13 * Puppeteer-level request interception.
14 */
15
16const mockRoutes = require('./test/fixtures/mock-routes.cjs')
17
18module.exports = async function setup(browser, { url }) {
19 const colorMode = process.env.LIGHTHOUSE_COLOR_MODE || 'dark'
20
21 // Set up browser-level request interception via CDP Fetch domain.
22 // This operates below Puppeteer's request interception layer so it
23 // doesn't conflict with Lighthouse's own setRequestInterception usage.
24 await setupCdpRequestInterception(browser)
25
26 const page = await browser.newPage()
27
28 // Set localStorage before navigating so @nuxtjs/color-mode picks it up
29 await page.evaluateOnNewDocument(mode => {
30 localStorage.setItem('npmx-color-mode', mode)
31 }, colorMode)
32
33 // Navigate and wait for DOM only - Lighthouse will do its own full load
34 await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
35
36 // Close the page - Lighthouse will open its own with localStorage already set
37 await page.close()
38}
39
40/**
41 * Set up request interception using CDP's Fetch domain on the browser's
42 * default context. This intercepts requests at a lower level than Puppeteer's
43 * page.setRequestInterception(), avoiding "Request is already handled!" errors
44 * when Lighthouse sets up its own interception.
45 *
46 * @param {import('puppeteer').Browser} browser
47 */
48async function setupCdpRequestInterception(browser) {
49 // Build URL pattern list for CDP Fetch.enable from our route definitions
50 const cdpPatterns = mockRoutes.routes.map(route => ({
51 urlPattern: route.pattern.replace('/**', '/*'),
52 requestStage: 'Request',
53 }))
54
55 // Listen for new targets so we can attach CDP interception to each page
56 browser.on('targetcreated', async target => {
57 if (target.type() !== 'page') return
58
59 try {
60 const cdp = await target.createCDPSession()
61
62 cdp.on('Fetch.requestPaused', async event => {
63 const requestUrl = event.request.url
64 const result = mockRoutes.matchRoute(requestUrl)
65
66 if (result) {
67 const body = Buffer.from(result.response.body).toString('base64')
68 await cdp.send('Fetch.fulfillRequest', {
69 requestId: event.requestId,
70 responseCode: result.response.status,
71 responseHeaders: [
72 { name: 'Content-Type', value: result.response.contentType },
73 { name: 'Access-Control-Allow-Origin', value: '*' },
74 ],
75 body,
76 })
77 } else {
78 await cdp.send('Fetch.continueRequest', {
79 requestId: event.requestId,
80 })
81 }
82 })
83
84 await cdp.send('Fetch.enable', { patterns: cdpPatterns })
85 } catch {
86 // Target may have been closed before we could attach.
87 // This is expected for transient targets like service workers.
88 }
89 })
90}