diff --git a/docs/src/test-reporters-js.md b/docs/src/test-reporters-js.md index 35169ae31c..4fffcadf57 100644 --- a/docs/src/test-reporters-js.md +++ b/docs/src/test-reporters-js.md @@ -215,13 +215,13 @@ Blob reports contain all the details about the test run and can be used later to npx playwright test --reporter=blob ``` -By default, the report is written into the `blob-report` directory in the package.json directory or current working directory (if no package.json is found). The report file name looks like `report-.zip` or `report--.zip` when [sharding](./test-sharding.md) is used. The hash is an optional value computed from `--grep`, `--grepInverted`, `--project` and file filters passed as command line arguments. The hash guarantees that running Playwright with different command line options will produce different but stable between runs report names. Both output directory and report file name can be overridden in the configuration file. Report file name can also be passed as `'PLAYWRIGHT_BLOB_FILE_NAME'` environment variable. +By default, the report is written into the `blob-report` directory in the package.json directory or current working directory (if no package.json is found). The report file name looks like `report-.zip` or `report--.zip` when [sharding](./test-sharding.md) is used. The hash is an optional value computed from `--grep`, `--grepInverted`, `--project` and file filters passed as command line arguments. The hash guarantees that running Playwright with different command line options will produce different but stable between runs report names. The output file name can be overridden in the configuration file or pass as `'PLAYWRIGHT_BLOB_OUTPUT_FILE'` environment variable. ```js title="playwright.config.ts" import { defineConfig } from '@playwright/test'; export default defineConfig({ - reporter: [['blob', { outputDir: 'my-report', fileName: `report-${os.platform()}.zip` }]], + reporter: [['blob', { outputFile: `blob-report/report-${os.platform()}.zip` }]], }); ``` diff --git a/packages/playwright/src/reporters/blob.ts b/packages/playwright/src/reporters/blob.ts index 3d2f498627..33c642b65b 100644 --- a/packages/playwright/src/reporters/blob.ts +++ b/packages/playwright/src/reporters/blob.ts @@ -30,6 +30,7 @@ type BlobReporterOptions = { configDir: string; outputDir?: string; fileName?: string; + outputFile?: string; _commandHash: string; }; @@ -48,7 +49,7 @@ export class BlobReporter extends TeleReporterEmitter { private readonly _attachments: { originalPath: string, zipEntryPath: string }[] = []; private readonly _options: BlobReporterOptions; private readonly _salt: string; - private _reportName!: string; + private _config!: FullConfig; constructor(options: BlobReporterOptions) { super(message => this._messages.push(message)); @@ -71,26 +72,22 @@ export class BlobReporter extends TeleReporterEmitter { params: metadata }); - this._reportName = this._computeReportName(config); + this._config = config; super.onConfigure(config); } override async onEnd(result: FullResult): Promise { await super.onEnd(result); - const outputDir = resolveReporterOutputPath('blob-report', this._options.configDir, this._options.outputDir); - if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE) - await removeFolders([outputDir]); - await fs.promises.mkdir(outputDir, { recursive: true }); + const zipFileName = await this._prepareOutputFile(); const zipFile = new yazl.ZipFile(); const zipFinishPromise = new ManualPromise(); const finishPromise = zipFinishPromise.catch(e => { - throw new Error(`Failed to write report ${this._reportName}: ` + e.message); + throw new Error(`Failed to write report ${zipFileName}: ` + e.message); }); (zipFile as any as EventEmitter).on('error', error => zipFinishPromise.reject(error)); - const zipFileName = path.join(outputDir, this._reportName); zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', () => { zipFinishPromise.resolve(undefined); }).on('error', error => zipFinishPromise.reject(error)); @@ -109,11 +106,23 @@ export class BlobReporter extends TeleReporterEmitter { await finishPromise; } - private _computeReportName(config: FullConfig) { - if (this._options.fileName) - return this._options.fileName; - if (process.env.PLAYWRIGHT_BLOB_FILE_NAME) - return process.env.PLAYWRIGHT_BLOB_FILE_NAME; + private async _prepareOutputFile() { + let outputFile = reportOutputFileFromEnv(); + if (!outputFile && this._options.outputFile) + outputFile = path.resolve(this._options.configDir, this._options.outputFile); + // Explicit `outputFile` overrides `outputDir` and `fileName` options. + if (!outputFile) { + const reportName = this._options.fileName || this._defaultReportName(this._config); + const outputDir = resolveReporterOutputPath('blob-report', this._options.configDir, this._options.outputDir); + if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE) + await removeFolders([outputDir]); + outputFile = path.resolve(outputDir, reportName); + } + await fs.promises.mkdir(path.dirname(outputFile), { recursive: true }); + return outputFile; + } + + private _defaultReportName(config: FullConfig) { let reportName = 'report'; if (this._options._commandHash) reportName += '-' + sanitizeForFilePath(this._options._commandHash); @@ -140,3 +149,9 @@ export class BlobReporter extends TeleReporterEmitter { }); } } + +function reportOutputFileFromEnv(): string | undefined { + if (process.env[`PLAYWRIGHT_BLOB_OUTPUT_FILE`]) + return path.resolve(process.cwd(), process.env[`PLAYWRIGHT_BLOB_OUTPUT_FILE`]); + return undefined; +} diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 2c733de3cd..423cd1ca18 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -1198,7 +1198,31 @@ test('support fileName option', async ({ runInlineTest, mergeReports }) => { expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']); }); -test('support PLAYWRIGHT_BLOB_FILE_NAME environment variable', async ({ runInlineTest, mergeReports }) => { +test('support outputFile option', async ({ runInlineTest, mergeReports }) => { + const files = (fileSuffix: string) => ({ + 'playwright.config.ts': ` + module.exports = { + reporter: [['blob', { outputDir: 'should-be-ignored', outputFile: 'my-reports/report-${fileSuffix}.zip' }]], + projects: [ + { name: 'foo' }, + ] + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('math 1 @smoke', async ({}) => {}); + `, + }); + + await runInlineTest(files('one')); + await runInlineTest(files('two')); + + const reportDir = test.info().outputPath('my-reports'); + const reportFiles = await fs.promises.readdir(reportDir); + expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']); +}); + +test('support PLAYWRIGHT_BLOB_OUTPUT_FILE environment variable', async ({ runInlineTest, mergeReports }) => { const files = { 'playwright.config.ts': ` module.exports = { @@ -1218,9 +1242,9 @@ test('support PLAYWRIGHT_BLOB_FILE_NAME environment variable', async ({ runInlin `, }; - await runInlineTest(files, { shard: `1/2` }, { PLAYWRIGHT_BLOB_FILE_NAME: 'report-one.zip' }); - await runInlineTest(files, { shard: `2/2` }, { PLAYWRIGHT_BLOB_FILE_NAME: 'report-two.zip', PWTEST_BLOB_DO_NOT_REMOVE: '1' }); - const reportDir = test.info().outputPath('blob-report'); + await runInlineTest(files, { shard: `1/2` }, { PLAYWRIGHT_BLOB_OUTPUT_FILE: 'subdir/report-one.zip' }); + await runInlineTest(files, { shard: `2/2` }, { PLAYWRIGHT_BLOB_OUTPUT_FILE: test.info().outputPath('subdir/report-two.zip') }); + const reportDir = test.info().outputPath('subdir'); const reportFiles = await fs.promises.readdir(reportDir); expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']); });