-
-
Notifications
You must be signed in to change notification settings - Fork 275
test: mock client side requests in lighthouse #1171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,28 @@ | ||
| /** | ||
| * Lighthouse CI puppeteer setup script. | ||
| * Sets the color mode (light/dark) before running accessibility audits. | ||
| * | ||
| * Sets the color mode (light/dark) before running accessibility audits | ||
| * and intercepts client-side API requests using the same fixture data | ||
| * as the Playwright E2E tests. | ||
| * | ||
| * The color mode is determined by the LIGHTHOUSE_COLOR_MODE environment variable. | ||
| * If not set, defaults to 'dark'. | ||
| * | ||
| * Request interception uses CDP (Chrome DevTools Protocol) Fetch domain | ||
| * at the browser level, which avoids conflicts with Lighthouse's own | ||
| * Puppeteer-level request interception. | ||
| */ | ||
|
|
||
| const mockRoutes = require('./test/fixtures/mock-routes.cjs') | ||
|
|
||
| module.exports = async function setup(browser, { url }) { | ||
| const colorMode = process.env.LIGHTHOUSE_COLOR_MODE || 'dark' | ||
|
|
||
| // Set up browser-level request interception via CDP Fetch domain. | ||
| // This operates below Puppeteer's request interception layer so it | ||
| // doesn't conflict with Lighthouse's own setRequestInterception usage. | ||
| await setupCdpRequestInterception(browser) | ||
|
|
||
| const page = await browser.newPage() | ||
|
|
||
| // Set localStorage before navigating so @nuxtjs/color-mode picks it up | ||
|
|
@@ -21,3 +36,55 @@ module.exports = async function setup(browser, { url }) { | |
| // Close the page - Lighthouse will open its own with localStorage already set | ||
| await page.close() | ||
| } | ||
|
|
||
| /** | ||
| * Set up request interception using CDP's Fetch domain on the browser's | ||
| * default context. This intercepts requests at a lower level than Puppeteer's | ||
| * page.setRequestInterception(), avoiding "Request is already handled!" errors | ||
| * when Lighthouse sets up its own interception. | ||
| * | ||
| * @param {import('puppeteer').Browser} browser | ||
| */ | ||
| async function setupCdpRequestInterception(browser) { | ||
| // Build URL pattern list for CDP Fetch.enable from our route definitions | ||
| const cdpPatterns = mockRoutes.routes.map(route => ({ | ||
| urlPattern: route.pattern.replace('/**', '/*'), | ||
| requestStage: 'Request', | ||
| })) | ||
|
|
||
| // Listen for new targets so we can attach CDP interception to each page | ||
| browser.on('targetcreated', async target => { | ||
| if (target.type() !== 'page') return | ||
|
|
||
| try { | ||
| const cdp = await target.createCDPSession() | ||
|
|
||
| cdp.on('Fetch.requestPaused', async event => { | ||
| const requestUrl = event.request.url | ||
| const result = mockRoutes.matchRoute(requestUrl) | ||
|
|
||
| if (result) { | ||
| const body = Buffer.from(result.response.body).toString('base64') | ||
| await cdp.send('Fetch.fulfillRequest', { | ||
| requestId: event.requestId, | ||
| responseCode: result.response.status, | ||
| responseHeaders: [ | ||
| { name: 'Content-Type', value: result.response.contentType }, | ||
| { name: 'Access-Control-Allow-Origin', value: '*' }, | ||
| ], | ||
| body, | ||
| }) | ||
| } else { | ||
| await cdp.send('Fetch.continueRequest', { | ||
| requestId: event.requestId, | ||
| }) | ||
| } | ||
| }) | ||
|
Comment on lines
+62
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling within the The 🛡️ Proposed fix to add error handling cdp.on('Fetch.requestPaused', async event => {
+ try {
const requestUrl = event.request.url
const result = mockRoutes.matchRoute(requestUrl)
if (result) {
const body = Buffer.from(result.response.body).toString('base64')
await cdp.send('Fetch.fulfillRequest', {
requestId: event.requestId,
responseCode: result.response.status,
responseHeaders: [
{ name: 'Content-Type', value: result.response.contentType },
{ name: 'Access-Control-Allow-Origin', value: '*' },
],
body,
})
} else {
await cdp.send('Fetch.continueRequest', {
requestId: event.requestId,
})
}
+ } catch {
+ // Target may have closed mid-request; safe to ignore.
+ }
}) |
||
|
|
||
| await cdp.send('Fetch.enable', { patterns: cdpPatterns }) | ||
| } catch { | ||
| // Target may have been closed before we could attach. | ||
| // This is expected for transient targets like service workers. | ||
| } | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 115
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 111
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 129
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 1238
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 15791
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 471
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 43
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 148
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 1954
🏁 Script executed:
rg "https://api.npmjs.org/downloads" test/ -A 2 -B 2Repository: npmx-dev/npmx.dev
Length of output: 1423
🏁 Script executed:
Repository: npmx-dev/npmx.dev
Length of output: 3971
🏁 Script executed:
rg "Fetch\.enable" lighthouse-setup.cjs -A 5 -B 5Repository: npmx-dev/npmx.dev
Length of output: 734
The pattern conversion from
/**to/*will break CDP interception for multi-segment paths.CDP Fetch patterns use glob syntax where
*matches a single path segment and/**matches recursively. Converting patterns likehttps://api.npmjs.org/**tohttps://api.npmjs.org/*prevents CDP from pausing requests to endpoints with multiple path segments (e.g.,https://api.npmjs.org/downloads/point/last-week/vue). Requests that don't match CDP's patterns won't pause, somockRoutes.matchRoute()is never called and the mock response is never served.Keep the patterns unchanged or use
/**syntax if that's supported by CDP's urlPattern implementation.