feat(html-report): add attachmentsBaseURL option (#22212)

Fixes https://github.com/microsoft/playwright/issues/21636
This commit is contained in:
mindaugasm 2023-04-18 21:25:11 +03:00 committed by GitHub
parent d45efe881f
commit be79ee0450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 8 deletions

View File

@ -183,6 +183,16 @@ export default defineConfig({
});
```
If you are uploading attachments from data folder to other location, you can use `attachmentsBaseURL` option to let html report where to look for them.
```js
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [['html', { attachmentsBaseURL: 'https://external-storage.com/' }]],
});
```
A quick way of opening the last test run report is:
```bash

View File

@ -46,6 +46,7 @@ type HtmlReporterOptions = {
open?: HtmlReportOpenOption,
host?: string,
port?: number,
attachmentsBaseURL?: string,
};
class HtmlReporter implements Reporter {
@ -54,6 +55,7 @@ class HtmlReporter implements Reporter {
private _montonicStartTime: number = 0;
private _options: HtmlReporterOptions;
private _outputFolder!: string;
private _attachmentsBaseURL!: string;
private _open: string | undefined;
private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined;
@ -68,9 +70,10 @@ class HtmlReporter implements Reporter {
onBegin(config: FullConfig, suite: Suite) {
this._montonicStartTime = monotonicTime();
this.config = config;
const { outputFolder, open } = this._resolveOptions();
const { outputFolder, open, attachmentsBaseURL } = this._resolveOptions();
this._outputFolder = outputFolder;
this._open = open;
this._attachmentsBaseURL = attachmentsBaseURL;
const reportedWarnings = new Set<string>();
for (const project of config.projects) {
if (outputFolder.startsWith(project.outputDir) || project.outputDir.startsWith(outputFolder)) {
@ -90,13 +93,14 @@ class HtmlReporter implements Reporter {
this.suite = suite;
}
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption } {
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string } {
let { outputFolder } = this._options;
if (outputFolder)
outputFolder = path.resolve(this._options.configDir, outputFolder);
return {
outputFolder: reportFolderFromEnv() ?? outputFolder ?? defaultReportFolder(this._options.configDir),
open: process.env.PW_TEST_HTML_REPORT_OPEN as any || this._options.open || 'on-failure',
attachmentsBaseURL: this._options.attachmentsBaseURL || 'data/'
};
}
@ -109,7 +113,7 @@ class HtmlReporter implements Reporter {
return report;
});
await removeFolders([this._outputFolder]);
const builder = new HtmlBuilder(this._outputFolder);
const builder = new HtmlBuilder(this._outputFolder, this._attachmentsBaseURL);
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
}
@ -198,11 +202,13 @@ class HtmlBuilder {
private _testPath = new Map<string, string[]>();
private _dataZipFile: ZipFile;
private _hasTraces = false;
private _attachmentsBaseURL: string;
constructor(outputDir: string) {
constructor(outputDir: string, attachmentsBaseURL: string) {
this._reportFolder = outputDir;
fs.mkdirSync(this._reportFolder, { recursive: true });
this._dataZipFile = new yazl.ZipFile();
this._attachmentsBaseURL = attachmentsBaseURL;
}
async build(metadata: Metadata & { duration: number }, rawReports: JsonReport[]): Promise<{ ok: boolean, singleTestId: string | undefined }> {
@ -389,7 +395,7 @@ class HtmlBuilder {
try {
const buffer = fs.readFileSync(a.path);
const sha1 = calculateSha1(buffer) + path.extname(a.path);
fileName = 'data/' + sha1;
fileName = this._attachmentsBaseURL + sha1;
fs.mkdirSync(path.join(this._reportFolder, 'data'), { recursive: true });
fs.writeFileSync(path.join(this._reportFolder, 'data', sha1), buffer);
} catch (e) {
@ -430,7 +436,7 @@ class HtmlBuilder {
return {
name: a.name,
contentType: a.contentType,
path: 'data/' + sha1,
path: this._attachmentsBaseURL + sha1,
};
}

View File

@ -25,7 +25,7 @@ export type ReporterDescription =
['github'] |
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
['json'] | ['json', { outputFile?: string }] |
['html'] | ['html', { outputFolder?: string, open?: 'always' | 'never' | 'on-failure' }] |
['html'] | ['html', { outputFolder?: string, open?: 'always' | 'never' | 'on-failure', attachmentsBaseURL?: string }] |
['null'] |
[string] | [string, any];

View File

@ -329,6 +329,42 @@ test('should include screenshot on failure', async ({ runInlineTest, page, showR
expect(src).toBeTruthy();
});
test('should use different path if attachments base url option is provided', async ({ runInlineTest, page, showReport }, testInfo) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = {
use: {
viewport: { width: 200, height: 200 },
screenshot: 'on',
video: 'on',
trace: 'on',
},
reporter: [['html', { attachmentsBaseURL: 'https://some-url.com/' }]]
};
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('passes', async ({ page }) => {
await page.evaluate('2 + 2');
});
`
}, {}, { PW_TEST_HTML_REPORT_OPEN: 'never' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
await showReport();
await page.click('text=passes');
await expect(page.locator('div').filter({ hasText: /^Screenshotsscreenshot$/ }).getByRole('img')).toHaveAttribute('src', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.getByRole('link', { name: 'screenshot' })).toHaveAttribute('href', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.locator('video').locator('source')).toHaveAttribute('src', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.getByRole('link', { name: 'video' })).toHaveAttribute('href', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.getByRole('link', { name: 'trace' })).toHaveAttribute('href', /(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
await expect(page.locator('div').filter({ hasText: /^Tracestrace$/ }).getByRole('link').first()).toHaveAttribute('href', /trace=(https:\/\/some-url\.com\/)[^/\s]+?\.[^/\s]+/);
});
test('should include stdio', async ({ runInlineTest, page, showReport }) => {
const result = await runInlineTest({
'a.test.js': `

View File

@ -24,7 +24,7 @@ export type ReporterDescription =
['github'] |
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
['json'] | ['json', { outputFile?: string }] |
['html'] | ['html', { outputFolder?: string, open?: 'always' | 'never' | 'on-failure' }] |
['html'] | ['html', { outputFolder?: string, open?: 'always' | 'never' | 'on-failure', attachmentsBaseURL?: string }] |
['null'] |
[string] | [string, any];