mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-20 16:58:47 +03:00
feat(html-report): add attachmentsBaseURL option (#22212)
Fixes https://github.com/microsoft/playwright/issues/21636
This commit is contained in:
parent
d45efe881f
commit
be79ee0450
@ -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:
|
A quick way of opening the last test run report is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -46,6 +46,7 @@ type HtmlReporterOptions = {
|
|||||||
open?: HtmlReportOpenOption,
|
open?: HtmlReportOpenOption,
|
||||||
host?: string,
|
host?: string,
|
||||||
port?: number,
|
port?: number,
|
||||||
|
attachmentsBaseURL?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
class HtmlReporter implements Reporter {
|
class HtmlReporter implements Reporter {
|
||||||
@ -54,6 +55,7 @@ class HtmlReporter implements Reporter {
|
|||||||
private _montonicStartTime: number = 0;
|
private _montonicStartTime: number = 0;
|
||||||
private _options: HtmlReporterOptions;
|
private _options: HtmlReporterOptions;
|
||||||
private _outputFolder!: string;
|
private _outputFolder!: string;
|
||||||
|
private _attachmentsBaseURL!: string;
|
||||||
private _open: string | undefined;
|
private _open: string | undefined;
|
||||||
private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined;
|
private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined;
|
||||||
|
|
||||||
@ -68,9 +70,10 @@ class HtmlReporter implements Reporter {
|
|||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
this._montonicStartTime = monotonicTime();
|
this._montonicStartTime = monotonicTime();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
const { outputFolder, open } = this._resolveOptions();
|
const { outputFolder, open, attachmentsBaseURL } = this._resolveOptions();
|
||||||
this._outputFolder = outputFolder;
|
this._outputFolder = outputFolder;
|
||||||
this._open = open;
|
this._open = open;
|
||||||
|
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||||
const reportedWarnings = new Set<string>();
|
const reportedWarnings = new Set<string>();
|
||||||
for (const project of config.projects) {
|
for (const project of config.projects) {
|
||||||
if (outputFolder.startsWith(project.outputDir) || project.outputDir.startsWith(outputFolder)) {
|
if (outputFolder.startsWith(project.outputDir) || project.outputDir.startsWith(outputFolder)) {
|
||||||
@ -90,13 +93,14 @@ class HtmlReporter implements Reporter {
|
|||||||
this.suite = suite;
|
this.suite = suite;
|
||||||
}
|
}
|
||||||
|
|
||||||
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption } {
|
_resolveOptions(): { outputFolder: string, open: HtmlReportOpenOption, attachmentsBaseURL: string } {
|
||||||
let { outputFolder } = this._options;
|
let { outputFolder } = this._options;
|
||||||
if (outputFolder)
|
if (outputFolder)
|
||||||
outputFolder = path.resolve(this._options.configDir, outputFolder);
|
outputFolder = path.resolve(this._options.configDir, outputFolder);
|
||||||
return {
|
return {
|
||||||
outputFolder: reportFolderFromEnv() ?? outputFolder ?? defaultReportFolder(this._options.configDir),
|
outputFolder: reportFolderFromEnv() ?? outputFolder ?? defaultReportFolder(this._options.configDir),
|
||||||
open: process.env.PW_TEST_HTML_REPORT_OPEN as any || this._options.open || 'on-failure',
|
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;
|
return report;
|
||||||
});
|
});
|
||||||
await removeFolders([this._outputFolder]);
|
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);
|
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,11 +202,13 @@ class HtmlBuilder {
|
|||||||
private _testPath = new Map<string, string[]>();
|
private _testPath = new Map<string, string[]>();
|
||||||
private _dataZipFile: ZipFile;
|
private _dataZipFile: ZipFile;
|
||||||
private _hasTraces = false;
|
private _hasTraces = false;
|
||||||
|
private _attachmentsBaseURL: string;
|
||||||
|
|
||||||
constructor(outputDir: string) {
|
constructor(outputDir: string, attachmentsBaseURL: string) {
|
||||||
this._reportFolder = outputDir;
|
this._reportFolder = outputDir;
|
||||||
fs.mkdirSync(this._reportFolder, { recursive: true });
|
fs.mkdirSync(this._reportFolder, { recursive: true });
|
||||||
this._dataZipFile = new yazl.ZipFile();
|
this._dataZipFile = new yazl.ZipFile();
|
||||||
|
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async build(metadata: Metadata & { duration: number }, rawReports: JsonReport[]): Promise<{ ok: boolean, singleTestId: string | undefined }> {
|
async build(metadata: Metadata & { duration: number }, rawReports: JsonReport[]): Promise<{ ok: boolean, singleTestId: string | undefined }> {
|
||||||
@ -389,7 +395,7 @@ class HtmlBuilder {
|
|||||||
try {
|
try {
|
||||||
const buffer = fs.readFileSync(a.path);
|
const buffer = fs.readFileSync(a.path);
|
||||||
const sha1 = calculateSha1(buffer) + path.extname(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.mkdirSync(path.join(this._reportFolder, 'data'), { recursive: true });
|
||||||
fs.writeFileSync(path.join(this._reportFolder, 'data', sha1), buffer);
|
fs.writeFileSync(path.join(this._reportFolder, 'data', sha1), buffer);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -430,7 +436,7 @@ class HtmlBuilder {
|
|||||||
return {
|
return {
|
||||||
name: a.name,
|
name: a.name,
|
||||||
contentType: a.contentType,
|
contentType: a.contentType,
|
||||||
path: 'data/' + sha1,
|
path: this._attachmentsBaseURL + sha1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
packages/playwright-test/types/test.d.ts
vendored
2
packages/playwright-test/types/test.d.ts
vendored
@ -25,7 +25,7 @@ export type ReporterDescription =
|
|||||||
['github'] |
|
['github'] |
|
||||||
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
|
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
|
||||||
['json'] | ['json', { outputFile?: string }] |
|
['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'] |
|
['null'] |
|
||||||
[string] | [string, any];
|
[string] | [string, any];
|
||||||
|
|
||||||
|
@ -329,6 +329,42 @@ test('should include screenshot on failure', async ({ runInlineTest, page, showR
|
|||||||
expect(src).toBeTruthy();
|
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 }) => {
|
test('should include stdio', async ({ runInlineTest, page, showReport }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
|
2
utils/generate_types/overrides-test.d.ts
vendored
2
utils/generate_types/overrides-test.d.ts
vendored
@ -24,7 +24,7 @@ export type ReporterDescription =
|
|||||||
['github'] |
|
['github'] |
|
||||||
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
|
['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] |
|
||||||
['json'] | ['json', { outputFile?: string }] |
|
['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'] |
|
['null'] |
|
||||||
[string] | [string, any];
|
[string] | [string, any];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user