fix(test runner): testInfo.attach api review changes (#11211)

Remove overload, require name, merge options.
This commit is contained in:
Dmitry Gozman 2022-01-05 16:39:33 -08:00 committed by GitHub
parent 1857a16381
commit 3ecac56cc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 189 deletions

View file

@ -40,10 +40,11 @@ Learn more about [test annotations](./test-annotations.md).
The list of files or buffers attached to the current test. Some reporters show test attachments. The list of files or buffers attached to the current test. Some reporters show test attachments.
To safely add a file from disk as an attachment, please use [`method: TestInfo.attach#1`] instead of directly pushing onto this array. For inline attachments, use [`method: TestInfo.attach#1`]. To add an attachment, use [`method: TestInfo.attach`] instead of directly pushing onto this array.
## method: TestInfo.attach#1 ## method: TestInfo.attach
Attach a file from disk to the current test. Some reporters show test attachments. The [`option: name`] and [`option: contentType`] will be inferred by default from the [`param: path`], but you can optionally override either of these.
Attach a value or a file from disk to the current test. Some reporters show test attachments. Either [`option: path`] or [`option: body`] must be specified, but not both.
For example, you can attach a screenshot to the test: For example, you can attach a screenshot to the test:
@ -52,15 +53,8 @@ const { test, expect } = require('@playwright/test');
test('basic test', async ({ page }, testInfo) => { test('basic test', async ({ page }, testInfo) => {
await page.goto('https://playwright.dev'); await page.goto('https://playwright.dev');
const screenshot = await page.screenshot();
// Capture a screenshot and attach it. await testInfo.attach('screenshot', { body: screenshot, contentType: 'image/png' });
const path = testInfo.outputPath('screenshot.png');
await page.screenshot({ path });
await testInfo.attach(path);
// Optionally override the name.
await testInfo.attach(path, { name: 'example.png' });
// Optionally override the contentType.
await testInfo.attach(path, { name: 'example.custom-file', contentType: 'x-custom-content-type' });
}); });
``` ```
@ -69,15 +63,8 @@ import { test, expect } from '@playwright/test';
test('basic test', async ({ page }, testInfo) => { test('basic test', async ({ page }, testInfo) => {
await page.goto('https://playwright.dev'); await page.goto('https://playwright.dev');
const screenshot = await page.screenshot();
// Capture a screenshot and attach it. await testInfo.attach('screenshot', { body: screenshot, contentType: 'image/png' });
const path = testInfo.outputPath('screenshot.png');
await page.screenshot({ path });
await testInfo.attach(path);
// Optionally override the name.
await testInfo.attach(path, { name: 'example.png' });
// Optionally override the contentType.
await testInfo.attach(path, { name: 'example.custom-file', contentType: 'x-custom-content-type' });
}); });
``` ```
@ -89,7 +76,7 @@ const { test, expect } = require('@playwright/test');
test('basic test', async ({}, testInfo) => { test('basic test', async ({}, testInfo) => {
const { download } = require('./my-custom-helpers'); const { download } = require('./my-custom-helpers');
const tmpPath = await download('a'); const tmpPath = await download('a');
await testInfo.attach(tmpPath, { name: 'example.json' }); await testInfo.attach('downloaded', { path: tmpPath });
}); });
``` ```
@ -99,38 +86,28 @@ import { test, expect } from '@playwright/test';
test('basic test', async ({}, testInfo) => { test('basic test', async ({}, testInfo) => {
const { download } = require('./my-custom-helpers'); const { download } = require('./my-custom-helpers');
const tmpPath = await download('a'); const tmpPath = await download('a');
await testInfo.attach(tmpPath, { name: 'example.json' }); await testInfo.attach('downloaded', { path: tmpPath });
}); });
``` ```
:::note :::note
[`method: TestInfo.attach#1`] automatically takes care of copying attachments to a [`method: TestInfo.attach`] automatically takes care of copying attached files to a
location that is accessible to reporters, even if you were to delete the attachment location that is accessible to reporters. You can safely remove the attachment
after awaiting the attach call. after awaiting the attach call.
::: :::
### param: TestInfo.attach#1.path ### param: TestInfo.attach.name
- `path` <[string]> Path on the filesystem to the attached file.
### option: TestInfo.attach#1.name
- `name` <[void]|[string]> Optional attachment name. If omitted, this will be inferred from [`param: path`].
### option: TestInfo.attach#1.contentType
- `contentType` <[void]|[string]> Optional content type of this attachment to properly present in the report, for example `'application/json'` or `'image/png'`. If omitted, this falls back to an inferred type based on the [`param: name`] (if set) or [`param: path`]'s extension; it will be set to `application/octet-stream` if the type cannot be inferred from the file extension.
## method: TestInfo.attach#2
Attach data to the current test, either a `string` or a `Buffer`. Some reporters show test attachments.
### param: TestInfo.attach#2.body
- `body` <[string]|[Buffer]> Attachment body.
### param: TestInfo.attach#2.name
- `name` <[string]> Attachment name. - `name` <[string]> Attachment name.
### option: TestInfo.attach#2.contentType ### option: TestInfo.attach.body
- `contentType` <[void]|[string]> Optional content type of this attachment to properly present in the report, for example `'application/json'` or `'application/xml'`. If omitted, this falls back to an inferred type based on the [`param: name`]'s extension; if the type cannot be inferred from the name's extension, it will be set to `text/plain` (if [`param: body`] is a `string`) or `application/octet-stream` (if [`param: body`] is a `Buffer`). - `body` <[string]|[Buffer]> Attachment body. Mutually exclusive with [`option: path`].
### option: TestInfo.attach.contentType
- `contentType` <[void]|[string]> Optional content type of this attachment to properly present in the report, for example `'application/json'` or `'image/png'`. If omitted, content type is inferred based on the [`option: path`], or defaults to `text/plain` for [string] attachments and `application/octet-stream` for [Buffer] attachments.
### option: TestInfo.attach.path
- `path` <[string]> Path on the filesystem to the attached file. Mutually exclusive with [`option: body`].
## property: TestInfo.column ## property: TestInfo.column
- type: <[int]> - type: <[int]>

View file

@ -264,39 +264,20 @@ export class WorkerRunner extends EventEmitter {
expectedStatus: test.expectedStatus, expectedStatus: test.expectedStatus,
annotations: [], annotations: [],
attachments: [], attachments: [],
attach: async (...args) => { attach: async (name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) => {
const [ pathOrBody, nameOrFileOptions, inlineOptions ] = args as [string | Buffer, string | { contentType?: string, name?: string} | undefined, { contentType?: string } | undefined]; if ((options.path !== undefined ? 1 : 0) + (options.body !== undefined ? 1 : 0) !== 1)
let attachment: { name: string, contentType: string, body?: Buffer, path?: string } | undefined; throw new Error(`Exactly one of "path" and "body" must be specified`);
if (typeof nameOrFileOptions === 'string') { // inline attachment if (options.path) {
const body = pathOrBody; const hash = await calculateFileSha1(options.path);
const name = nameOrFileOptions; const dest = testInfo.outputPath('attachments', hash + path.extname(options.path));
attachment = {
name,
contentType: inlineOptions?.contentType ?? (mime.getType(name) || (typeof body === 'string' ? 'text/plain' : 'application/octet-stream')),
body: typeof body === 'string' ? Buffer.from(body) : body,
};
} else { // path based attachment
const options = nameOrFileOptions;
const thePath = pathOrBody as string;
const name = options?.name ?? path.basename(thePath);
attachment = {
name,
path: thePath,
contentType: options?.contentType ?? (mime.getType(name) || 'application/octet-stream')
};
}
const tmpAttachment = { ...attachment };
if (attachment.path) {
const hash = await calculateFileSha1(attachment.path);
const dest = testInfo.outputPath('attachments', hash + path.extname(attachment.path));
await fs.promises.mkdir(path.dirname(dest), { recursive: true }); await fs.promises.mkdir(path.dirname(dest), { recursive: true });
await fs.promises.copyFile(attachment.path, dest); await fs.promises.copyFile(options.path, dest);
tmpAttachment.path = dest; const contentType = options.contentType ?? (mime.getType(path.basename(options.path)) || 'application/octet-stream');
testInfo.attachments.push({ name, contentType, path: dest });
} else {
const contentType = options.contentType ?? (typeof options.body === 'string' ? 'text/plain' : 'application/octet-stream');
testInfo.attachments.push({ name, contentType, body: typeof options.body === 'string' ? Buffer.from(options.body) : options.body });
} }
testInfo.attachments.push(tmpAttachment);
}, },
duration: 0, duration: 0,
status: 'passed', status: 'passed',

View file

@ -1389,15 +1389,14 @@ export interface TestInfo {
/** /**
* The list of files or buffers attached to the current test. Some reporters show test attachments. * The list of files or buffers attached to the current test. Some reporters show test attachments.
* *
* To safely add a file from disk as an attachment, please use * To add an attachment, use
* [testInfo.attach(path[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach-1) instead of * [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach) instead of directly
* directly pushing onto this array. For inline attachments, use * pushing onto this array.
* [testInfo.attach(path[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach-1).
*/ */
attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; attachments: { name: string, path?: string, body?: Buffer, contentType: string }[];
/** /**
* Attach a file from disk to the current test. Some reporters show test attachments. The `name` and `contentType` will be * Attach a value or a file from disk to the current test. Some reporters show test attachments. Either `path` or `body`
* inferred by default from the `path`, but you can optionally override either of these. * must be specified, but not both.
* *
* For example, you can attach a screenshot to the test: * For example, you can attach a screenshot to the test:
* *
@ -1406,15 +1405,8 @@ export interface TestInfo {
* *
* test('basic test', async ({ page }, testInfo) => { * test('basic test', async ({ page }, testInfo) => {
* await page.goto('https://playwright.dev'); * await page.goto('https://playwright.dev');
* * const screenshot = await page.screenshot();
* // Capture a screenshot and attach it. * await testInfo.attach('screenshot', { body: screenshot, contentType: 'image/png' });
* const path = testInfo.outputPath('screenshot.png');
* await page.screenshot({ path });
* await testInfo.attach(path);
* // Optionally override the name.
* await testInfo.attach(path, { name: 'example.png' });
* // Optionally override the contentType.
* await testInfo.attach(path, { name: 'example.custom-file', contentType: 'x-custom-content-type' });
* }); * });
* ``` * ```
* *
@ -1426,24 +1418,17 @@ export interface TestInfo {
* test('basic test', async ({}, testInfo) => { * test('basic test', async ({}, testInfo) => {
* const { download } = require('./my-custom-helpers'); * const { download } = require('./my-custom-helpers');
* const tmpPath = await download('a'); * const tmpPath = await download('a');
* await testInfo.attach(tmpPath, { name: 'example.json' }); * await testInfo.attach('downloaded', { path: tmpPath });
* }); * });
* ``` * ```
* *
* > NOTE: [testInfo.attach(path[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach-1) * > NOTE: [testInfo.attach(name[, options])](https://playwright.dev/docs/api/class-testinfo#test-info-attach)
* automatically takes care of copying attachments to a location that is accessible to reporters, even if you were to * automatically takes care of copying attached files to a location that is accessible to reporters. You can safely remove
* delete the attachment after awaiting the attach call. * the attachment after awaiting the attach call.
* @param path
* @param options
*/
attach(path: string, options?: { contentType?: string, name?: string}): Promise<void>;
/**
* Attach data to the current test, either a `string` or a `Buffer`. Some reporters show test attachments.
* @param body
* @param name * @param name
* @param options * @param options
*/ */
attach(body: string | Buffer, name: string, options?: { contentType?: string }): Promise<void>; attach(name: string, options?: { contentType?: string, path?: string, body?: string | Buffer }): Promise<void>;
/** /**
* Specifies a unique repeat index when running in "repeat each" mode. This mode is enabled by passing `--repeat-each` to * Specifies a unique repeat index when running in "repeat each" mode. This mode is enabled by passing `--repeat-each` to
* the [command line](https://playwright.dev/docs/test-cli). * the [command line](https://playwright.dev/docs/test-cli).

View file

@ -81,33 +81,25 @@ test('render trace attachment', async ({ runInlineTest }) => {
}); });
test(`testInfo.attach throws an error when attaching a non-existent attachment`, async ({ runInlineTest }) => { test(`testInfo.attach errors`, async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.test.js': ` 'a.test.js': `
const { test } = pwt; const { test } = pwt;
test('all options specified', async ({}, testInfo) => { test('fail1', async ({}, testInfo) => {
await testInfo.attach('non-existent-path-all-options', { contentType: 'text/plain', name: 'foo.txt'}); await testInfo.attach('name', { path: 'foo.txt' });
}); });
test('fail2', async ({}, testInfo) => {
test('no options specified', async ({}, testInfo) => { await testInfo.attach('name', { path: 'foo.txt', body: 'bar' });
await testInfo.attach('non-existent-path-no-options');
}); });
test('fail3', async ({}, testInfo) => {
test('partial options - contentType', async ({}, testInfo) => { await testInfo.attach('name', {});
await testInfo.attach('non-existent-path-partial-options-content-type', { contentType: 'text/plain'});
});
test('partial options - name', async ({}, testInfo) => {
await testInfo.attach('non-existent-path-partial-options-name', { name: 'foo.txt'});
}); });
`, `,
}, { reporter: 'line', workers: 1 }); }, { reporter: 'line', workers: 1 });
const text = stripAscii(result.output).replace(/\\/g, '/'); const text = stripAscii(result.output).replace(/\\/g, '/');
expect(text).toMatch(/Error: ENOENT: no such file or directory, open '.*non-existent-path-all-options.*'/); expect(text).toMatch(/Error: ENOENT: no such file or directory, open '.*foo.txt.*'/);
expect(text).toMatch(/Error: ENOENT: no such file or directory, open '.*non-existent-path-no-options.*'/); expect(text).toContain(`Exactly one of "path" and "body" must be specified`);
expect(text).toMatch(/Error: ENOENT: no such file or directory, open '.*non-existent-path-partial-options-content-type.*'/);
expect(text).toMatch(/Error: ENOENT: no such file or directory, open '.*non-existent-path-partial-options-name.*'/);
expect(result.passed).toBe(0); expect(result.passed).toBe(0);
expect(result.failed).toBe(4); expect(result.failed).toBe(3);
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });

View file

@ -119,15 +119,7 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
test('infer contentType from path', async ({}, testInfo) => { test('infer contentType from path', async ({}, testInfo) => {
const tmpPath = testInfo.outputPath('example.json'); const tmpPath = testInfo.outputPath('example.json');
await fs.promises.writeFile(tmpPath, 'We <3 Playwright!'); await fs.promises.writeFile(tmpPath, 'We <3 Playwright!');
await testInfo.attach(tmpPath); await testInfo.attach('foo', { path: tmpPath });
// Forcibly remove the tmp file to ensure attach is actually automagically copying it
await fs.promises.unlink(tmpPath);
});
test('infer contentType from name (over extension)', async ({}, testInfo) => {
const tmpPath = testInfo.outputPath('example.json');
await fs.promises.writeFile(tmpPath, 'We <3 Playwright!');
await testInfo.attach(tmpPath, { name: 'example.png' });
// Forcibly remove the tmp file to ensure attach is actually automagically copying it // Forcibly remove the tmp file to ensure attach is actually automagically copying it
await fs.promises.unlink(tmpPath); await fs.promises.unlink(tmpPath);
}); });
@ -135,7 +127,7 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
test('explicit contentType (over extension)', async ({}, testInfo) => { test('explicit contentType (over extension)', async ({}, testInfo) => {
const tmpPath = testInfo.outputPath('example.json'); const tmpPath = testInfo.outputPath('example.json');
await fs.promises.writeFile(tmpPath, 'We <3 Playwright!'); await fs.promises.writeFile(tmpPath, 'We <3 Playwright!');
await testInfo.attach(tmpPath, { contentType: 'image/png' }); await testInfo.attach('foo', { path: tmpPath, contentType: 'image/png' });
// Forcibly remove the tmp file to ensure attach is actually automagically copying it // Forcibly remove the tmp file to ensure attach is actually automagically copying it
await fs.promises.unlink(tmpPath); await fs.promises.unlink(tmpPath);
}); });
@ -143,15 +135,15 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
test('explicit contentType (over extension and name)', async ({}, testInfo) => { test('explicit contentType (over extension and name)', async ({}, testInfo) => {
const tmpPath = testInfo.outputPath('example.json'); const tmpPath = testInfo.outputPath('example.json');
await fs.promises.writeFile(tmpPath, 'We <3 Playwright!'); await fs.promises.writeFile(tmpPath, 'We <3 Playwright!');
await testInfo.attach(tmpPath, { name: 'example.png', contentType: 'x-playwright/custom' }); await testInfo.attach('example.png', { path: tmpPath, contentType: 'x-playwright/custom' });
// Forcibly remove the tmp file to ensure attach is actually automagically copying it // Forcibly remove the tmp file to ensure attach is actually automagically copying it
await fs.promises.unlink(tmpPath); await fs.promises.unlink(tmpPath);
}); });
test('fallback contentType', async ({}, testInfo) => { test('fallback contentType', async ({}, testInfo) => {
const tmpPath = testInfo.outputPath('example.json'); const tmpPath = testInfo.outputPath('example.this-extension-better-not-map-to-an-actual-mimetype');
await fs.promises.writeFile(tmpPath, 'We <3 Playwright!'); await fs.promises.writeFile(tmpPath, 'We <3 Playwright!');
await testInfo.attach(tmpPath, { name: 'example.this-extension-better-not-map-to-an-actual-mimetype' }); await testInfo.attach('foo', { path: tmpPath });
// Forcibly remove the tmp file to ensure attach is actually automagically copying it // Forcibly remove the tmp file to ensure attach is actually automagically copying it
await fs.promises.unlink(tmpPath); await fs.promises.unlink(tmpPath);
}); });
@ -160,7 +152,7 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
const json = JSON.parse(fs.readFileSync(testInfo.outputPath('test-results', 'report', 'project.report'), 'utf-8')); const json = JSON.parse(fs.readFileSync(testInfo.outputPath('test-results', 'report', 'project.report'), 'utf-8'));
{ {
const result = json.suites[0].tests[0].results[0]; const result = json.suites[0].tests[0].results[0];
expect(result.attachments[0].name).toBe('example.json'); expect(result.attachments[0].name).toBe('foo');
expect(result.attachments[0].contentType).toBe('application/json'); expect(result.attachments[0].contentType).toBe('application/json');
const p = result.attachments[0].path; const p = result.attachments[0].path;
expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/); expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/);
@ -169,7 +161,7 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
} }
{ {
const result = json.suites[0].tests[1].results[0]; const result = json.suites[0].tests[1].results[0];
expect(result.attachments[0].name).toBe('example.png'); expect(result.attachments[0].name).toBe('foo');
expect(result.attachments[0].contentType).toBe('image/png'); expect(result.attachments[0].contentType).toBe('image/png');
const p = result.attachments[0].path; const p = result.attachments[0].path;
expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/); expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/);
@ -178,15 +170,6 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
} }
{ {
const result = json.suites[0].tests[2].results[0]; const result = json.suites[0].tests[2].results[0];
expect(result.attachments[0].name).toBe('example.json');
expect(result.attachments[0].contentType).toBe('image/png');
const p = result.attachments[0].path;
expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/);
const contents = fs.readFileSync(p);
expect(contents.toString()).toBe('We <3 Playwright!');
}
{
const result = json.suites[0].tests[3].results[0];
expect(result.attachments[0].name).toBe('example.png'); expect(result.attachments[0].name).toBe('example.png');
expect(result.attachments[0].contentType).toBe('x-playwright/custom'); expect(result.attachments[0].contentType).toBe('x-playwright/custom');
const p = result.attachments[0].path; const p = result.attachments[0].path;
@ -195,11 +178,11 @@ test(`testInfo.attach should save attachments via path`, async ({ runInlineTest
expect(contents.toString()).toBe('We <3 Playwright!'); expect(contents.toString()).toBe('We <3 Playwright!');
} }
{ {
const result = json.suites[0].tests[4].results[0]; const result = json.suites[0].tests[3].results[0];
expect(result.attachments[0].name).toBe('example.this-extension-better-not-map-to-an-actual-mimetype'); expect(result.attachments[0].name).toBe('foo');
expect(result.attachments[0].contentType).toBe('application/octet-stream'); expect(result.attachments[0].contentType).toBe('application/octet-stream');
const p = result.attachments[0].path; const p = result.attachments[0].path;
expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.json$/); expect(p).toMatch(/[/\\]attachments[/\\]01a5667d100fac2200bf40cf43083fae0580c58e\.this-extension-better-not-map-to-an-actual-mimetype$/);
const contents = fs.readFileSync(p); const contents = fs.readFileSync(p);
expect(contents.toString()).toBe('We <3 Playwright!'); expect(contents.toString()).toBe('We <3 Playwright!');
} }
@ -211,32 +194,20 @@ test(`testInfo.attach should save attachments via inline attachment`, async ({ r
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { test } = pwt; const { test } = pwt;
test('infer contentType - string', async ({}, testInfo) => { test('default contentType - string', async ({}, testInfo) => {
await testInfo.attach('We <3 Playwright!', 'example.json'); await testInfo.attach('example.json', { body: 'We <3 Playwright!' });
}); });
test('infer contentType - Buffer', async ({}, testInfo) => { test('default contentType - Buffer', async ({}, testInfo) => {
await testInfo.attach(Buffer.from('We <3 Playwright!'), 'example.json'); await testInfo.attach('example.json', { body: Buffer.from('We <3 Playwright!') });
});
test('fallback contentType - string', async ({}, testInfo) => {
await testInfo.attach('We <3 Playwright!', 'example.this-extension-better-not-map-to-an-actual-mimetype');
});
test('fallback contentType - Buffer', async ({}, testInfo) => {
await testInfo.attach(Buffer.from('We <3 Playwright!'), 'example.this-extension-better-not-map-to-an-actual-mimetype');
});
test('fallback contentType - no extension', async ({}, testInfo) => {
await testInfo.attach('We <3 Playwright!', 'example');
}); });
test('explicit contentType - string', async ({}, testInfo) => { test('explicit contentType - string', async ({}, testInfo) => {
await testInfo.attach('We <3 Playwright!', 'example.json', { contentType: 'x-playwright/custom' }); await testInfo.attach('example.json', { body: 'We <3 Playwright!', contentType: 'x-playwright/custom' });
}); });
test('explicit contentType - Buffer', async ({}, testInfo) => { test('explicit contentType - Buffer', async ({}, testInfo) => {
await testInfo.attach(Buffer.from('We <3 Playwright!'), 'example.json', { contentType: 'x-playwright/custom' }); await testInfo.attach('example.json', { body: Buffer.from('We <3 Playwright!'), contentType: 'x-playwright/custom' });
}); });
`, `,
}, { reporter: 'dot,' + kRawReporterPath, workers: 1 }, {}, { usesCustomOutputDir: true }); }, { reporter: 'dot,' + kRawReporterPath, workers: 1 }, {}, { usesCustomOutputDir: true });
@ -244,41 +215,23 @@ test(`testInfo.attach should save attachments via inline attachment`, async ({ r
{ {
const result = json.suites[0].tests[0].results[0]; const result = json.suites[0].tests[0].results[0];
expect(result.attachments[0].name).toBe('example.json'); expect(result.attachments[0].name).toBe('example.json');
expect(result.attachments[0].contentType).toBe('application/json'); expect(result.attachments[0].contentType).toBe('text/plain');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!')); expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
} }
{ {
const result = json.suites[0].tests[1].results[0]; const result = json.suites[0].tests[1].results[0];
expect(result.attachments[0].name).toBe('example.json'); expect(result.attachments[0].name).toBe('example.json');
expect(result.attachments[0].contentType).toBe('application/json');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
}
{
const result = json.suites[0].tests[2].results[0];
expect(result.attachments[0].name).toBe('example.this-extension-better-not-map-to-an-actual-mimetype');
expect(result.attachments[0].contentType).toBe('text/plain');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
}
{
const result = json.suites[0].tests[3].results[0];
expect(result.attachments[0].name).toBe('example.this-extension-better-not-map-to-an-actual-mimetype');
expect(result.attachments[0].contentType).toBe('application/octet-stream'); expect(result.attachments[0].contentType).toBe('application/octet-stream');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!')); expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
} }
{ {
const result = json.suites[0].tests[4].results[0]; const result = json.suites[0].tests[2].results[0];
expect(result.attachments[0].name).toBe('example');
expect(result.attachments[0].contentType).toBe('text/plain');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
}
{
const result = json.suites[0].tests[5].results[0];
expect(result.attachments[0].name).toBe('example.json'); expect(result.attachments[0].name).toBe('example.json');
expect(result.attachments[0].contentType).toBe('x-playwright/custom'); expect(result.attachments[0].contentType).toBe('x-playwright/custom');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!')); expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));
} }
{ {
const result = json.suites[0].tests[6].results[0]; const result = json.suites[0].tests[3].results[0];
expect(result.attachments[0].name).toBe('example.json'); expect(result.attachments[0].name).toBe('example.json');
expect(result.attachments[0].contentType).toBe('x-playwright/custom'); expect(result.attachments[0].contentType).toBe('x-playwright/custom');
expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!')); expect(Buffer.from(result.attachments[0].body, 'base64')).toEqual(Buffer.from('We <3 Playwright!'));

View file

@ -204,8 +204,7 @@ export interface TestInfo {
timeout: number; timeout: number;
annotations: { type: string, description?: string }[]; annotations: { type: string, description?: string }[];
attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; attachments: { name: string, path?: string, body?: Buffer, contentType: string }[];
attach(path: string, options?: { contentType?: string, name?: string}): Promise<void>; attach(name: string, options?: { contentType?: string, path?: string, body?: string | Buffer }): Promise<void>;
attach(body: string | Buffer, name: string, options?: { contentType?: string }): Promise<void>;
repeatEachIndex: number; repeatEachIndex: number;
retry: number; retry: number;
duration: number; duration: number;