-
-
Notifications
You must be signed in to change notification settings - Fork 87
Description
Detail Bug Report
Summary
- Context: The lighthouse package provides a wrapper function that manages browser context lifecycle and runs lighthouse audits on web pages.
- Bug: When
browserless.withPage()throws an error (e.g., timeout, navigation failure), the registered teardown callback is never executed. - Actual vs. expected: The teardown callback should always execute to clean up the browser context, but it only executes when
withPage()succeeds. - Impact: Browser contexts are leaked when errors occur during lighthouse execution, leading to resource exhaustion in long-running CLI processes.
Code with Bug
module.exports =
getBrowserless =>
async (url, { output = 'json', timeout, flags, ...opts } = {}) => {
let teardown
const browserless = await getBrowserless(fn => (teardown = fn))
const fn = page => async () =>
lighthouse({
flags,
config: await getConfig({ ...opts, output }),
page,
url
})
const result = await browserless.withPage(fn, { timeout })()
if (teardown) await teardown() // <-- BUG 🔴 Never called if withPage() throws, leaking context
return result
}Explanation
teardown() is invoked only after awaiting browserless.withPage(...)(). If withPage() throws (timeouts/navigation failures are expected), control flow skips the teardown call, so browserless.destroyContext() (registered via the teardown callback) never runs.
Codebase Inconsistency
The CLI expects this teardown mechanism to perform cleanup:
const lighthouse = createLighthouse(async teardown => {
teardown(() => browserless.destroyContext())
return browserless
})
const report = await lighthouse(url) // <-- If this throws, destroyContext() never calledAdditionally, tests can miss the leak because the test helper registers its own cleanup via the test framework (t.teardown(browserless.destroyContext)), so production behavior differs.
Recommended Fix
Wrap the withPage call in try-finally so teardown always runs:
try {
const result = await browserless.withPage(fn, { timeout })()
return result
} finally {
if (teardown) await teardown()
}History
This bug was introduced in commit 75c31e5. The commit upgraded Lighthouse from v9.6.8 to v10.0.0 and refactored the lighthouse integration to use a new withPage pattern with a teardown callback mechanism for browser context cleanup. The bug slipped in because the teardown call was placed after the withPage() call without wrapping it in a try-finally block, so the teardown never executes when withPage() throws an error (such as timeout errors).