diff --git a/packages/playwright/src/reporters/perfetto.ts b/packages/playwright/src/reporters/perfetto.ts index 2f0f59e27ed6f..c8b2fd9766587 100644 --- a/packages/playwright/src/reporters/perfetto.ts +++ b/packages/playwright/src/reporters/perfetto.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { once } from 'events'; import fs from 'fs'; import path from 'path'; import zlib from 'zlib'; @@ -298,6 +299,10 @@ class ChunkWriter { const gzip = file.endsWith('.gz') ? zlib.createGzip() : undefined; gzip?.pipe(fileStream); this._stream = gzip ?? fileStream; + // pipe() only unpipes on a destination error, so the gzip stream would never + // emit 'drain' or 'error' again. Destroy it to wake up the pending write. + if (gzip) + fileStream.on('error', error => gzip.destroy(error)); // The file is only complete once the destination closes, which is later than // the gzip stream ending. this._closed = new Promise(resolve => fileStream.on('close', resolve)); @@ -309,7 +314,7 @@ class ChunkWriter { if (this._error) throw this._error; if (!this._stream.write(chunk)) - await new Promise(resolve => this._stream.once('drain', () => resolve())); + await once(this._stream, 'drain'); // Rejects if 'error' is emitted first. } async close() { diff --git a/tests/playwright-test/reporter-perfetto.spec.ts b/tests/playwright-test/reporter-perfetto.spec.ts index 15d230405e996..eaedea02bdaa8 100644 --- a/tests/playwright-test/reporter-perfetto.spec.ts +++ b/tests/playwright-test/reporter-perfetto.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as zlib from 'zlib'; @@ -215,6 +216,25 @@ test('should report step params', async ({ runInlineTest }, testInfo) => { expect(findSlice(events, 'my step')!.args.params).toEqual({ foo: 'bar', count: 7 }); }); +for (const fileName of ['trace.json', 'trace.json.gz']) { + test(`should fail when ${fileName} output is a directory`, async ({ runInlineTest }, testInfo) => { + const outputFile = testInfo.outputPath(fileName); + await fs.promises.mkdir(outputFile); + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { reporter: [['perfetto', { outputFile: ${JSON.stringify(outputFile)} }]] }; + `, + 'a.test.ts': ` + import { test } from '@playwright/test'; + test('one', async () => { + test.info().annotations.push({ type: 'blob', description: ${JSON.stringify(crypto.randomBytes(256 * 1024).toString('base64'))} }); + }); + `, + }); + expect(result.exitCode).not.toBe(0); + }); +} + test('should respect outputFile option', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ 'playwright.config.ts': `