feat: outputFile option and PLAYWRIGHT_BLOB_OUTPUT_FILE env for blob (#30559)

Reference https://github.com/microsoft/playwright/issues/30091
This commit is contained in:
Yury Semikhatsky 2024-04-26 09:33:53 -07:00 committed by GitHub
parent b5aca9fca8
commit 3643fd456b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 19 deletions

View File

@ -215,16 +215,24 @@ Blob reports contain all the details about the test run and can be used later to
npx playwright test --reporter=blob 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-<hash>.zip` or `report-<hash>-<shard_number>.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-<hash>.zip` or `report-<hash>-<shard_number>.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" ```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test'; import { defineConfig } from '@playwright/test';
export default defineConfig({ export default defineConfig({
reporter: [['blob', { outputDir: 'my-report', fileName: `report-${os.platform()}.zip` }]], reporter: [['blob', { outputFile: `./blob-report/report-${os.platform()}.zip` }]],
}); });
``` ```
Blob report supports following configuration options and environment variables:
| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_BLOB_OUTPUT_DIR` | `outputDir` | Directory to save the output. Existing content is deleted before writing the new report. | `blob-report`
| `PLAYWRIGHT_BLOB_OUTPUT_NAME` | `fileName` | Report file name. | `report-<project>-<hash>-<shard_number>.zip`
| `PLAYWRIGHT_BLOB_OUTPUT_FILE` | `outputFile` | Full path for the output. If defined, `outputDir` and `fileName` will be ignored. | `undefined`
### JSON reporter ### JSON reporter
JSON reporter produces an object with all information about the test run. JSON reporter produces an object with all information about the test run.

View File

@ -30,6 +30,7 @@ type BlobReporterOptions = {
configDir: string; configDir: string;
outputDir?: string; outputDir?: string;
fileName?: string; fileName?: string;
outputFile?: string;
_commandHash: string; _commandHash: string;
}; };
@ -48,7 +49,7 @@ export class BlobReporter extends TeleReporterEmitter {
private readonly _attachments: { originalPath: string, zipEntryPath: string }[] = []; private readonly _attachments: { originalPath: string, zipEntryPath: string }[] = [];
private readonly _options: BlobReporterOptions; private readonly _options: BlobReporterOptions;
private readonly _salt: string; private readonly _salt: string;
private _reportName!: string; private _config!: FullConfig;
constructor(options: BlobReporterOptions) { constructor(options: BlobReporterOptions) {
super(message => this._messages.push(message)); super(message => this._messages.push(message));
@ -71,26 +72,22 @@ export class BlobReporter extends TeleReporterEmitter {
params: metadata params: metadata
}); });
this._reportName = this._computeReportName(config); this._config = config;
super.onConfigure(config); super.onConfigure(config);
} }
override async onEnd(result: FullResult): Promise<void> { override async onEnd(result: FullResult): Promise<void> {
await super.onEnd(result); await super.onEnd(result);
const outputDir = resolveReporterOutputPath('blob-report', this._options.configDir, this._options.outputDir); const zipFileName = await this._prepareOutputFile();
if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE)
await removeFolders([outputDir]);
await fs.promises.mkdir(outputDir, { recursive: true });
const zipFile = new yazl.ZipFile(); const zipFile = new yazl.ZipFile();
const zipFinishPromise = new ManualPromise<undefined>(); const zipFinishPromise = new ManualPromise<undefined>();
const finishPromise = zipFinishPromise.catch(e => { 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)); (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', () => { zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', () => {
zipFinishPromise.resolve(undefined); zipFinishPromise.resolve(undefined);
}).on('error', error => zipFinishPromise.reject(error)); }).on('error', error => zipFinishPromise.reject(error));
@ -109,11 +106,23 @@ export class BlobReporter extends TeleReporterEmitter {
await finishPromise; await finishPromise;
} }
private _computeReportName(config: FullConfig) { private async _prepareOutputFile() {
if (this._options.fileName) let outputFile = reportOutputFileFromEnv();
return this._options.fileName; if (!outputFile && this._options.outputFile)
if (process.env.PLAYWRIGHT_BLOB_FILE_NAME) outputFile = path.resolve(this._options.configDir, this._options.outputFile);
return process.env.PLAYWRIGHT_BLOB_FILE_NAME; // Explicit `outputFile` overrides `outputDir` and `fileName` options.
if (!outputFile) {
const reportName = this._options.fileName || process.env[`PLAYWRIGHT_BLOB_OUTPUT_NAME`] || this._defaultReportName(this._config);
const outputDir = resolveReporterOutputPath('blob-report', this._options.configDir, this._options.outputDir ?? reportOutputDirFromEnv());
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'; let reportName = 'report';
if (this._options._commandHash) if (this._options._commandHash)
reportName += '-' + sanitizeForFilePath(this._options._commandHash); reportName += '-' + sanitizeForFilePath(this._options._commandHash);
@ -140,3 +149,15 @@ export class BlobReporter extends TeleReporterEmitter {
}); });
} }
} }
function reportOutputDirFromEnv(): string | undefined {
if (process.env[`PLAYWRIGHT_BLOB_OUTPUT_DIR`])
return path.resolve(process.cwd(), process.env[`PLAYWRIGHT_BLOB_OUTPUT_DIR`]);
return undefined;
}
function reportOutputFileFromEnv(): string | undefined {
if (process.env[`PLAYWRIGHT_BLOB_OUTPUT_FILE`])
return path.resolve(process.cwd(), process.env[`PLAYWRIGHT_BLOB_OUTPUT_FILE`]);
return undefined;
}

View File

@ -1198,7 +1198,82 @@ test('support fileName option', async ({ runInlineTest, mergeReports }) => {
expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']); expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']);
}); });
test('support PLAYWRIGHT_BLOB_FILE_NAME environment variable', async ({ runInlineTest, mergeReports }) => { test('support PLAYWRIGHT_BLOB_OUTPUT_DIR env variable', async ({ runInlineTest, mergeReports }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30091' });
const files = {
'playwright.config.ts': `
module.exports = {
reporter: [['blob']],
projects: [
{ name: 'foo' },
]
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('math 1 @smoke', async ({}) => {});
`,
};
await runInlineTest(files, undefined, { PLAYWRIGHT_BLOB_OUTPUT_DIR: 'my/dir' });
const reportDir = test.info().outputPath('my', 'dir');
const reportFiles = await fs.promises.readdir(reportDir);
expect(reportFiles.sort()).toEqual(['report.zip']);
});
test('support PLAYWRIGHT_BLOB_OUTPUT_NAME env variable', async ({ runInlineTest, mergeReports }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30091' });
const files = {
'playwright.config.ts': `
module.exports = {
reporter: [['blob']],
projects: [
{ name: 'foo' },
]
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('math 1 @smoke', async ({}) => {});
`,
};
await runInlineTest(files, undefined, { PLAYWRIGHT_BLOB_OUTPUT_NAME: 'report-one.zip' });
await runInlineTest(files, undefined, { PLAYWRIGHT_BLOB_OUTPUT_NAME: 'report-two.zip', PWTEST_BLOB_DO_NOT_REMOVE: '1' });
const reportDir = test.info().outputPath('blob-report');
const reportFiles = await fs.promises.readdir(reportDir);
expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']);
});
test('support outputFile option', async ({ runInlineTest, mergeReports }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30091' });
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 }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/30091' });
const files = { const files = {
'playwright.config.ts': ` 'playwright.config.ts': `
module.exports = { module.exports = {
@ -1218,9 +1293,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: `1/2` }, { PLAYWRIGHT_BLOB_OUTPUT_FILE: 'subdir/report-one.zip' });
await runInlineTest(files, { shard: `2/2` }, { PLAYWRIGHT_BLOB_FILE_NAME: 'report-two.zip', PWTEST_BLOB_DO_NOT_REMOVE: '1' }); await runInlineTest(files, { shard: `2/2` }, { PLAYWRIGHT_BLOB_OUTPUT_FILE: test.info().outputPath('subdir/report-two.zip') });
const reportDir = test.info().outputPath('blob-report'); const reportDir = test.info().outputPath('subdir');
const reportFiles = await fs.promises.readdir(reportDir); const reportFiles = await fs.promises.readdir(reportDir);
expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']); expect(reportFiles.sort()).toEqual(['report-one.zip', 'report-two.zip']);
}); });