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
```
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"
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` }]],
});
```
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 produces an object with all information about the test run.

View File

@ -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<void> {
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<undefined>();
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 || 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';
if (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']);
});
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 = {
'playwright.config.ts': `
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: `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']);
});