diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index a34a9b20c7..e205f2c4fc 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -85,6 +85,13 @@ function downloadFile(options: DownloadParams): Promise { file.on('error', error => promise.reject(error)); response.pipe(file); response.on('data', onData); + response.on('close', () => { + if (response.complete) + return; + file.close(); + log(`-- download failed, server closed connection`); + promise.reject(new Error(`Download failed: server closed connection. URL: ${options.url}`)); + }); }, (error: any) => promise.reject(error)); return promise; diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index fabc8012fb..3d202da81d 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -68,3 +68,30 @@ test(`playwright cdn should race with a timeout`, async ({ exec }) => { await new Promise(resolve => server.close(resolve)); } }); + +test(`npx playwright install should not hang when CDN closes the connection`, async ({ exec }) => { + let retryCount = 0; + const server = http.createServer((req, res) => { + ++retryCount; + res.writeHead(200, { + 'Content-Length': 100 * 1024 * 1024, + 'Content-Type': 'application/zip', + }); + res.end('a'); + }); + await new Promise(resolve => server.listen(0, resolve)); + try { + await exec('npm i playwright'); + const result = await exec('npx playwright install', { + env: { + PLAYWRIGHT_DOWNLOAD_HOST: `http://127.0.0.1:${(server.address() as AddressInfo).port}`, + DEBUG: 'pw:install', + }, + expectToExitWithError: true + }); + expect(retryCount).toBe(3); + expect([...result.matchAll(/Download failed: server closed connection/g)]).toHaveLength(3); + } finally { + await new Promise(resolve => server.close(resolve)); + } +});