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:
|
||||
|
||||
```bash
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
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'] |
|
||||
['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];
|
||||
|
||||
|
@ -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': `
|
||||
|
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'] |
|
||||
['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];
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user