feat: split up static and dynamic annotations (#35292)
Signed-off-by: Simon Knott <info@simonknott.de> Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
This commit is contained in:
parent
cbed3f73e1
commit
45fa3d17fc
|
@ -12,8 +12,7 @@
|
||||||
|
|
||||||
The list of annotations applicable to the current test. Includes:
|
The list of annotations applicable to the current test. Includes:
|
||||||
* annotations defined on the test or suite via [`method: Test.(call)`] and [`method: Test.describe`];
|
* annotations defined on the test or suite via [`method: Test.(call)`] and [`method: Test.describe`];
|
||||||
* annotations implicitly added by methods [`method: Test.skip`], [`method: Test.fixme`] and [`method: Test.fail`];
|
* annotations implicitly added by methods [`method: Test.skip`], [`method: Test.fixme`] and [`method: Test.fail`] prior to test execution.
|
||||||
* annotations appended to [`property: TestInfo.annotations`] during the test execution.
|
|
||||||
|
|
||||||
Annotations are available during test execution through [`property: TestInfo.annotations`].
|
Annotations are available during test execution through [`property: TestInfo.annotations`].
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,20 @@ A result of a single [TestCase] run.
|
||||||
|
|
||||||
The list of files or buffers attached during the test execution through [`property: TestInfo.attachments`].
|
The list of files or buffers attached during the test execution through [`property: TestInfo.attachments`].
|
||||||
|
|
||||||
|
## property: TestResult.annotations
|
||||||
|
* since: v1.52
|
||||||
|
- type: <[Array]<[Object]>>
|
||||||
|
- `type` <[string]> Annotation type, for example `'skip'` or `'fail'`.
|
||||||
|
- `description` ?<[string]> Optional description.
|
||||||
|
|
||||||
|
The list of annotations appended during test execution. Includes:
|
||||||
|
* annotations implicitly added by methods [`method: Test.skip`], [`method: Test.fixme`] and [`method: Test.fail`] during test execution;
|
||||||
|
* annotations appended to [`property: TestInfo.annotations`].
|
||||||
|
|
||||||
|
Annotations are available during test execution through [`property: TestInfo.annotations`].
|
||||||
|
|
||||||
|
Learn more about [test annotations](../test-annotations.md).
|
||||||
|
|
||||||
## property: TestResult.duration
|
## property: TestResult.duration
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: <[float]>
|
- type: <[float]>
|
||||||
|
|
|
@ -30,14 +30,18 @@ export const TabbedPane: React.FunctionComponent<{
|
||||||
selectedTab: string,
|
selectedTab: string,
|
||||||
setSelectedTab: (tab: string) => void
|
setSelectedTab: (tab: string) => void
|
||||||
}> = ({ tabs, selectedTab, setSelectedTab }) => {
|
}> = ({ tabs, selectedTab, setSelectedTab }) => {
|
||||||
|
const idPrefix = React.useId();
|
||||||
return <div className='tabbed-pane'>
|
return <div className='tabbed-pane'>
|
||||||
<div className='vbox'>
|
<div className='vbox'>
|
||||||
<div className='hbox' style={{ flex: 'none' }}>
|
<div className='hbox' style={{ flex: 'none' }}>
|
||||||
<div className='tabbed-pane-tab-strip'>{
|
<div className='tabbed-pane-tab-strip' role='tablist'>{
|
||||||
tabs.map(tab => (
|
tabs.map(tab => (
|
||||||
<div className={clsx('tabbed-pane-tab-element', selectedTab === tab.id && 'selected')}
|
<div className={clsx('tabbed-pane-tab-element', selectedTab === tab.id && 'selected')}
|
||||||
onClick={() => setSelectedTab(tab.id)}
|
onClick={() => setSelectedTab(tab.id)}
|
||||||
key={tab.id}>
|
id={`${idPrefix}-${tab.id}`}
|
||||||
|
key={tab.id}
|
||||||
|
role='tab'
|
||||||
|
aria-selected={selectedTab === tab.id}>
|
||||||
<div className='tabbed-pane-tab-label'>{tab.title}</div>
|
<div className='tabbed-pane-tab-label'>{tab.title}</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -46,7 +50,7 @@ export const TabbedPane: React.FunctionComponent<{
|
||||||
{
|
{
|
||||||
tabs.map(tab => {
|
tabs.map(tab => {
|
||||||
if (selectedTab === tab.id)
|
if (selectedTab === tab.id)
|
||||||
return <div key={tab.id} className='tab-content'>{tab.render()}</div>;
|
return <div key={tab.id} className='tab-content' role='tabpanel' aria-labelledby={`${idPrefix}-${tab.id}`}>{tab.render()}</div>;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,6 +42,7 @@ const result: TestResult = {
|
||||||
}],
|
}],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
}],
|
}],
|
||||||
|
annotations: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
status: 'passed',
|
status: 'passed',
|
||||||
};
|
};
|
||||||
|
@ -151,6 +152,7 @@ const resultWithAttachment: TestResult = {
|
||||||
name: 'attachment with inline link https://github.com/microsoft/playwright/issues/31284',
|
name: 'attachment with inline link https://github.com/microsoft/playwright/issues/31284',
|
||||||
contentType: 'text/plain'
|
contentType: 'text/plain'
|
||||||
}],
|
}],
|
||||||
|
annotations: [],
|
||||||
status: 'passed',
|
status: 'passed',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -238,13 +240,15 @@ test('total duration is selected run duration', async ({ mount, page }) => {
|
||||||
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCaseWithTwoAttempts} prev={undefined} next={undefined} run={0}></TestCaseView>);
|
const component = await mount(<TestCaseView projectNames={['chromium', 'webkit']} test={testCaseWithTwoAttempts} prev={undefined} next={undefined} run={0}></TestCaseView>);
|
||||||
await expect(component).toMatchAriaSnapshot(`
|
await expect(component).toMatchAriaSnapshot(`
|
||||||
- text: "My test test.spec.ts:42 200ms"
|
- text: "My test test.spec.ts:42 200ms"
|
||||||
- text: "Run 50ms Retry #1 150ms"
|
- tablist:
|
||||||
|
- tab "Run 50ms"
|
||||||
|
- 'tab "Retry #1 150ms"'
|
||||||
`);
|
`);
|
||||||
await page.locator('.tabbed-pane-tab-label', { hasText: 'Run50ms' }).click();
|
await page.getByRole('tab', { name: 'Run' }).click();
|
||||||
await expect(component).toMatchAriaSnapshot(`
|
await expect(component).toMatchAriaSnapshot(`
|
||||||
- text: "My test test.spec.ts:42 200ms"
|
- text: "My test test.spec.ts:42 200ms"
|
||||||
`);
|
`);
|
||||||
await page.locator('.tabbed-pane-tab-label', { hasText: 'Retry #1150ms' }).click();
|
await page.getByRole('tab', { name: 'Retry' }).click();
|
||||||
await expect(component).toMatchAriaSnapshot(`
|
await expect(component).toMatchAriaSnapshot(`
|
||||||
- text: "My test test.spec.ts:42 200ms"
|
- text: "My test test.spec.ts:42 200ms"
|
||||||
`);
|
`);
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestCase, TestCaseAnnotation, TestCaseSummary } from './types';
|
import type { TestCase, TestAnnotation, TestCaseSummary } from './types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TabbedPane } from './tabbedPane';
|
import { TabbedPane } from './tabbedPane';
|
||||||
import { AutoChip } from './chip';
|
import { AutoChip } from './chip';
|
||||||
|
@ -46,8 +46,13 @@ export const TestCaseView: React.FC<{
|
||||||
}, [test]);
|
}, [test]);
|
||||||
|
|
||||||
const visibleAnnotations = React.useMemo(() => {
|
const visibleAnnotations = React.useMemo(() => {
|
||||||
return test?.annotations?.filter(annotation => !annotation.type.startsWith('_')) || [];
|
if (!test)
|
||||||
}, [test?.annotations]);
|
return [];
|
||||||
|
const annotations = [...test.annotations];
|
||||||
|
if (test.results[selectedResultIndex])
|
||||||
|
annotations.push(...test.results[selectedResultIndex].annotations);
|
||||||
|
return annotations.filter(annotation => !annotation.type.startsWith('_'));
|
||||||
|
}, [test, selectedResultIndex]);
|
||||||
|
|
||||||
return <div className='test-case-column vbox'>
|
return <div className='test-case-column vbox'>
|
||||||
{test && <div className='hbox'>
|
{test && <div className='hbox'>
|
||||||
|
@ -71,7 +76,7 @@ export const TestCaseView: React.FC<{
|
||||||
{test && !!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
|
{test && !!test.projectName && <ProjectLink projectNames={projectNames} projectName={test.projectName}></ProjectLink>}
|
||||||
{labels && <LabelsLinkView labels={labels} />}
|
{labels && <LabelsLinkView labels={labels} />}
|
||||||
</div>}
|
</div>}
|
||||||
{!!visibleAnnotations.length && <AutoChip header='Annotations'>
|
{!!visibleAnnotations.length && <AutoChip header='Annotations' dataTestId='test-case-annotations'>
|
||||||
{visibleAnnotations.map((annotation, index) => <TestCaseAnnotationView key={index} annotation={annotation} />)}
|
{visibleAnnotations.map((annotation, index) => <TestCaseAnnotationView key={index} annotation={annotation} />)}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
{test && <TabbedPane tabs={
|
{test && <TabbedPane tabs={
|
||||||
|
@ -86,7 +91,7 @@ export const TestCaseView: React.FC<{
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function TestCaseAnnotationView({ annotation: { type, description } }: { annotation: TestCaseAnnotation }) {
|
function TestCaseAnnotationView({ annotation: { type, description } }: { annotation: TestAnnotation }) {
|
||||||
return (
|
return (
|
||||||
<div className='test-case-annotation'>
|
<div className='test-case-annotation'>
|
||||||
<span style={{ fontWeight: 'bold' }}>{type}</span>
|
<span style={{ fontWeight: 'bold' }}>{type}</span>
|
||||||
|
|
|
@ -59,7 +59,7 @@ export type TestFileSummary = {
|
||||||
stats: Stats;
|
stats: Stats;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestCaseAnnotation = { type: string, description?: string };
|
export type TestAnnotation = { type: string, description?: string };
|
||||||
|
|
||||||
export type TestCaseSummary = {
|
export type TestCaseSummary = {
|
||||||
testId: string,
|
testId: string,
|
||||||
|
@ -67,7 +67,7 @@ export type TestCaseSummary = {
|
||||||
path: string[];
|
path: string[];
|
||||||
projectName: string;
|
projectName: string;
|
||||||
location: Location;
|
location: Location;
|
||||||
annotations: TestCaseAnnotation[];
|
annotations: TestAnnotation[];
|
||||||
tags: string[];
|
tags: string[];
|
||||||
outcome: 'skipped' | 'expected' | 'unexpected' | 'flaky';
|
outcome: 'skipped' | 'expected' | 'unexpected' | 'flaky';
|
||||||
duration: number;
|
duration: number;
|
||||||
|
@ -98,6 +98,7 @@ export type TestResult = {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
attachments: TestAttachment[];
|
attachments: TestAttachment[];
|
||||||
status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
|
status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
|
||||||
|
annotations: TestAnnotation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestStep = {
|
export type TestStep = {
|
||||||
|
|
|
@ -64,10 +64,9 @@ export function bindFileSuiteToProject(project: FullProjectInternal, suite: Suit
|
||||||
// Inherit properties from parent suites.
|
// Inherit properties from parent suites.
|
||||||
let inheritedRetries: number | undefined;
|
let inheritedRetries: number | undefined;
|
||||||
let inheritedTimeout: number | undefined;
|
let inheritedTimeout: number | undefined;
|
||||||
test.annotations = [];
|
|
||||||
for (let parentSuite: Suite | undefined = suite; parentSuite; parentSuite = parentSuite.parent) {
|
for (let parentSuite: Suite | undefined = suite; parentSuite; parentSuite = parentSuite.parent) {
|
||||||
if (parentSuite._staticAnnotations.length)
|
if (parentSuite._staticAnnotations.length)
|
||||||
test.annotations = [...parentSuite._staticAnnotations, ...test.annotations];
|
test.annotations.unshift(...parentSuite._staticAnnotations);
|
||||||
if (inheritedRetries === undefined && parentSuite._retries !== undefined)
|
if (inheritedRetries === undefined && parentSuite._retries !== undefined)
|
||||||
inheritedRetries = parentSuite._retries;
|
inheritedRetries = parentSuite._retries;
|
||||||
if (inheritedTimeout === undefined && parentSuite._timeout !== undefined)
|
if (inheritedTimeout === undefined && parentSuite._timeout !== undefined)
|
||||||
|
@ -75,7 +74,6 @@ export function bindFileSuiteToProject(project: FullProjectInternal, suite: Suit
|
||||||
}
|
}
|
||||||
test.retries = inheritedRetries ?? project.project.retries;
|
test.retries = inheritedRetries ?? project.project.retries;
|
||||||
test.timeout = inheritedTimeout ?? project.project.timeout;
|
test.timeout = inheritedTimeout ?? project.project.timeout;
|
||||||
test.annotations.push(...test._staticAnnotations);
|
|
||||||
|
|
||||||
// Skip annotations imply skipped expectedStatus.
|
// Skip annotations imply skipped expectedStatus.
|
||||||
if (test.annotations.some(a => a.type === 'skip' || a.type === 'fixme'))
|
if (test.annotations.some(a => a.type === 'skip' || a.type === 'fixme'))
|
||||||
|
|
|
@ -262,8 +262,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
_poolDigest = '';
|
_poolDigest = '';
|
||||||
_workerHash = '';
|
_workerHash = '';
|
||||||
_projectId = '';
|
_projectId = '';
|
||||||
// Annotations known statically before running the test, e.g. `test.skip()` or `test(title, { annotation }, body)`.
|
|
||||||
_staticAnnotations: Annotation[] = [];
|
|
||||||
// Explicitly declared tags that are not a part of the title.
|
// Explicitly declared tags that are not a part of the title.
|
||||||
_tags: string[] = [];
|
_tags: string[] = [];
|
||||||
|
|
||||||
|
@ -306,7 +304,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
requireFile: this._requireFile,
|
requireFile: this._requireFile,
|
||||||
poolDigest: this._poolDigest,
|
poolDigest: this._poolDigest,
|
||||||
workerHash: this._workerHash,
|
workerHash: this._workerHash,
|
||||||
staticAnnotations: this._staticAnnotations.slice(),
|
|
||||||
annotations: this.annotations.slice(),
|
annotations: this.annotations.slice(),
|
||||||
tags: this._tags.slice(),
|
tags: this._tags.slice(),
|
||||||
projectId: this._projectId,
|
projectId: this._projectId,
|
||||||
|
@ -323,7 +320,6 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
test._requireFile = data.requireFile;
|
test._requireFile = data.requireFile;
|
||||||
test._poolDigest = data.poolDigest;
|
test._poolDigest = data.poolDigest;
|
||||||
test._workerHash = data.workerHash;
|
test._workerHash = data.workerHash;
|
||||||
test._staticAnnotations = data.staticAnnotations;
|
|
||||||
test.annotations = data.annotations;
|
test.annotations = data.annotations;
|
||||||
test._tags = data.tags;
|
test._tags = data.tags;
|
||||||
test._projectId = data.projectId;
|
test._projectId = data.projectId;
|
||||||
|
@ -351,6 +347,7 @@ export class TestCase extends Base implements reporterTypes.TestCase {
|
||||||
status: 'skipped',
|
status: 'skipped',
|
||||||
steps: [],
|
steps: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
|
annotations: [],
|
||||||
};
|
};
|
||||||
this.results.push(result);
|
this.results.push(result);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -107,16 +107,16 @@ export class TestTypeImpl {
|
||||||
const validatedDetails = validateTestDetails(details);
|
const validatedDetails = validateTestDetails(details);
|
||||||
const test = new TestCase(title, body, this, location);
|
const test = new TestCase(title, body, this, location);
|
||||||
test._requireFile = suite._requireFile;
|
test._requireFile = suite._requireFile;
|
||||||
test._staticAnnotations.push(...validatedDetails.annotations);
|
test.annotations.push(...validatedDetails.annotations);
|
||||||
test._tags.push(...validatedDetails.tags);
|
test._tags.push(...validatedDetails.tags);
|
||||||
suite._addTest(test);
|
suite._addTest(test);
|
||||||
|
|
||||||
if (type === 'only' || type === 'fail.only')
|
if (type === 'only' || type === 'fail.only')
|
||||||
test._only = true;
|
test._only = true;
|
||||||
if (type === 'skip' || type === 'fixme' || type === 'fail')
|
if (type === 'skip' || type === 'fixme' || type === 'fail')
|
||||||
test._staticAnnotations.push({ type });
|
test.annotations.push({ type });
|
||||||
else if (type === 'fail.only')
|
else if (type === 'fail.only')
|
||||||
test._staticAnnotations.push({ type: 'fail' });
|
test.annotations.push({ type: 'fail' });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, titleOrFn: string | Function, fnOrDetails?: TestDetails | Function, fn?: Function) {
|
private _describe(type: 'default' | 'only' | 'serial' | 'serial.only' | 'parallel' | 'parallel.only' | 'skip' | 'fixme', location: Location, titleOrFn: string | Function, fnOrDetails?: TestDetails | Function, fn?: Function) {
|
||||||
|
|
|
@ -68,14 +68,14 @@ export type JsonTestCase = {
|
||||||
retries: number;
|
retries: number;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
repeatEachIndex: number;
|
repeatEachIndex: number;
|
||||||
annotations?: { type: string, description?: string }[];
|
annotations?: Annotation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestEnd = {
|
export type JsonTestEnd = {
|
||||||
testId: string;
|
testId: string;
|
||||||
expectedStatus: reporterTypes.TestStatus;
|
expectedStatus: reporterTypes.TestStatus;
|
||||||
timeout: number;
|
timeout: number;
|
||||||
annotations: { type: string, description?: string }[];
|
annotations: Annotation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestResultStart = {
|
export type JsonTestResultStart = {
|
||||||
|
@ -94,6 +94,7 @@ export type JsonTestResultEnd = {
|
||||||
status: reporterTypes.TestStatus;
|
status: reporterTypes.TestStatus;
|
||||||
errors: reporterTypes.TestError[];
|
errors: reporterTypes.TestError[];
|
||||||
attachments: JsonAttachment[];
|
attachments: JsonAttachment[];
|
||||||
|
annotations?: Annotation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestStepStart = {
|
export type JsonTestStepStart = {
|
||||||
|
@ -241,6 +242,8 @@ export class TeleReporterReceiver {
|
||||||
result.errors = payload.errors;
|
result.errors = payload.errors;
|
||||||
result.error = result.errors?.[0];
|
result.error = result.errors?.[0];
|
||||||
result.attachments = this._parseAttachments(payload.attachments);
|
result.attachments = this._parseAttachments(payload.attachments);
|
||||||
|
if (payload.annotations)
|
||||||
|
result.annotations = payload.annotations;
|
||||||
this._reporter.onTestEnd?.(test, result);
|
this._reporter.onTestEnd?.(test, result);
|
||||||
// Free up the memory as won't see these step ids.
|
// Free up the memory as won't see these step ids.
|
||||||
result._stepMap = new Map();
|
result._stepMap = new Map();
|
||||||
|
@ -562,6 +565,7 @@ export class TeleTestResult implements reporterTypes.TestResult {
|
||||||
stdout: reporterTypes.TestResult['stdout'] = [];
|
stdout: reporterTypes.TestResult['stdout'] = [];
|
||||||
stderr: reporterTypes.TestResult['stderr'] = [];
|
stderr: reporterTypes.TestResult['stderr'] = [];
|
||||||
attachments: reporterTypes.TestResult['attachments'] = [];
|
attachments: reporterTypes.TestResult['attachments'] = [];
|
||||||
|
annotations: reporterTypes.TestResult['annotations'] = [];
|
||||||
status: reporterTypes.TestStatus = 'skipped';
|
status: reporterTypes.TestStatus = 'skipped';
|
||||||
steps: TeleTestStep[] = [];
|
steps: TeleTestStep[] = [];
|
||||||
errors: reporterTypes.TestResult['errors'] = [];
|
errors: reporterTypes.TestResult['errors'] = [];
|
||||||
|
|
|
@ -291,10 +291,13 @@ export class TerminalReporter implements ReporterV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _printWarnings() {
|
private _printWarnings() {
|
||||||
const warningTests = this.suite.allTests().filter(test => test.annotations.some(a => a.type === 'warning'));
|
const warningTests = this.suite.allTests().filter(test => {
|
||||||
|
const annotations = [...test.annotations, ...test.results.flatMap(r => r.annotations)];
|
||||||
|
return annotations.some(a => a.type === 'warning');
|
||||||
|
});
|
||||||
const encounteredWarnings = new Map<string, Array<TestCase>>();
|
const encounteredWarnings = new Map<string, Array<TestCase>>();
|
||||||
for (const test of warningTests) {
|
for (const test of warningTests) {
|
||||||
for (const annotation of test.annotations) {
|
for (const annotation of [...test.annotations, ...test.results.flatMap(r => r.annotations)]) {
|
||||||
if (annotation.type !== 'warning' || annotation.description === undefined)
|
if (annotation.type !== 'warning' || annotation.description === undefined)
|
||||||
continue;
|
continue;
|
||||||
let tests = encounteredWarnings.get(annotation.description);
|
let tests = encounteredWarnings.get(annotation.description);
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { resolveReporterOutputPath, stripAnsiEscapes } from '../util';
|
||||||
import type { ReporterV2 } from './reporterV2';
|
import type { ReporterV2 } from './reporterV2';
|
||||||
import type { Metadata } from '../../types/test';
|
import type { Metadata } from '../../types/test';
|
||||||
import type * as api from '../../types/testReporter';
|
import type * as api from '../../types/testReporter';
|
||||||
import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types';
|
import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep, TestAnnotation } from '@html-reporter/types';
|
||||||
import type { ZipFile } from 'playwright-core/lib/zipBundle';
|
import type { ZipFile } from 'playwright-core/lib/zipBundle';
|
||||||
import type { TransformCallback } from 'stream';
|
import type { TransformCallback } from 'stream';
|
||||||
|
|
||||||
|
@ -404,8 +404,7 @@ class HtmlBuilder {
|
||||||
projectName,
|
projectName,
|
||||||
location,
|
location,
|
||||||
duration,
|
duration,
|
||||||
// Annotations can be pushed directly, with a wrong type.
|
annotations: this._serializeAnnotations(test.annotations),
|
||||||
annotations: test.annotations.map(a => ({ type: a.type, description: a.description ? String(a.description) : a.description })),
|
|
||||||
tags: test.tags,
|
tags: test.tags,
|
||||||
outcome: test.outcome(),
|
outcome: test.outcome(),
|
||||||
path,
|
path,
|
||||||
|
@ -418,8 +417,7 @@ class HtmlBuilder {
|
||||||
projectName,
|
projectName,
|
||||||
location,
|
location,
|
||||||
duration,
|
duration,
|
||||||
// Annotations can be pushed directly, with a wrong type.
|
annotations: this._serializeAnnotations([...test.annotations, ...results.flatMap(r => r.annotations)]),
|
||||||
annotations: test.annotations.map(a => ({ type: a.type, description: a.description ? String(a.description) : a.description })),
|
|
||||||
tags: test.tags,
|
tags: test.tags,
|
||||||
outcome: test.outcome(),
|
outcome: test.outcome(),
|
||||||
path,
|
path,
|
||||||
|
@ -503,6 +501,11 @@ class HtmlBuilder {
|
||||||
}).filter(Boolean) as TestAttachment[];
|
}).filter(Boolean) as TestAttachment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _serializeAnnotations(annotations: api.TestCase['annotations']): TestAnnotation[] {
|
||||||
|
// Annotations can be pushed directly, with a wrong type.
|
||||||
|
return annotations.map(a => ({ type: a.type, description: a.description ? String(a.description) : a.description }));
|
||||||
|
}
|
||||||
|
|
||||||
private _createTestResult(test: api.TestCase, result: api.TestResult): TestResult {
|
private _createTestResult(test: api.TestCase, result: api.TestResult): TestResult {
|
||||||
return {
|
return {
|
||||||
duration: result.duration,
|
duration: result.duration,
|
||||||
|
@ -511,6 +514,7 @@ class HtmlBuilder {
|
||||||
steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)),
|
steps: dedupeSteps(result.steps).map(s => this._createTestStep(s, result)),
|
||||||
errors: formatResultFailure(internalScreen, test, result, '').map(error => error.message),
|
errors: formatResultFailure(internalScreen, test, result, '').map(error => error.message),
|
||||||
status: result.status,
|
status: result.status,
|
||||||
|
annotations: this._serializeAnnotations(result.annotations),
|
||||||
attachments: this._serializeAttachments([
|
attachments: this._serializeAttachments([
|
||||||
...result.attachments,
|
...result.attachments,
|
||||||
...result.stdout.map(m => stdioAttachment(m, 'stdout')),
|
...result.stdout.map(m => stdioAttachment(m, 'stdout')),
|
||||||
|
|
|
@ -213,6 +213,7 @@ class JSONReporter implements ReporterV2 {
|
||||||
retry: result.retry,
|
retry: result.retry,
|
||||||
steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined,
|
steps: steps.length ? steps.map(s => this._serializeTestStep(s)) : undefined,
|
||||||
startTime: result.startTime.toISOString(),
|
startTime: result.startTime.toISOString(),
|
||||||
|
annotations: result.annotations,
|
||||||
attachments: result.attachments.map(a => ({
|
attachments: result.attachments.map(a => ({
|
||||||
name: a.name,
|
name: a.name,
|
||||||
contentType: a.contentType,
|
contentType: a.contentType,
|
||||||
|
|
|
@ -166,7 +166,8 @@ class JUnitReporter implements ReporterV2 {
|
||||||
children: [] as XMLEntry[]
|
children: [] as XMLEntry[]
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const annotation of test.annotations) {
|
const annotations = [...test.annotations, ...test.results.flatMap(r => r.annotations)];
|
||||||
|
for (const annotation of annotations) {
|
||||||
const property: XMLEntry = {
|
const property: XMLEntry = {
|
||||||
name: 'property',
|
name: 'property',
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
@ -237,6 +237,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
||||||
status: result.status,
|
status: result.status,
|
||||||
errors: result.errors,
|
errors: result.errors,
|
||||||
attachments: this._serializeAttachments(result.attachments),
|
attachments: this._serializeAttachments(result.attachments),
|
||||||
|
annotations: result.annotations?.length ? result.annotations : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -325,8 +325,8 @@ class JobDispatcher {
|
||||||
result.errors = params.errors;
|
result.errors = params.errors;
|
||||||
result.error = result.errors[0];
|
result.error = result.errors[0];
|
||||||
result.status = params.status;
|
result.status = params.status;
|
||||||
|
result.annotations = params.annotations;
|
||||||
test.expectedStatus = params.expectedStatus;
|
test.expectedStatus = params.expectedStatus;
|
||||||
test.annotations = params.annotations;
|
|
||||||
test.timeout = params.timeout;
|
test.timeout = params.timeout;
|
||||||
const isFailure = result.status !== 'skipped' && result.status !== test.expectedStatus;
|
const isFailure = result.status !== 'skipped' && result.status !== test.expectedStatus;
|
||||||
if (isFailure)
|
if (isFailure)
|
||||||
|
|
|
@ -292,6 +292,8 @@ export class WorkerMain extends ProcessRunner {
|
||||||
for (const annotation of test.annotations)
|
for (const annotation of test.annotations)
|
||||||
processAnnotation(annotation);
|
processAnnotation(annotation);
|
||||||
|
|
||||||
|
const staticAnnotations = new Set(testInfo.annotations);
|
||||||
|
|
||||||
// Process existing annotations dynamically set for parent suites.
|
// Process existing annotations dynamically set for parent suites.
|
||||||
for (const suite of suites) {
|
for (const suite of suites) {
|
||||||
const extraAnnotations = this._activeSuites.get(suite) || [];
|
const extraAnnotations = this._activeSuites.get(suite) || [];
|
||||||
|
@ -310,7 +312,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) {
|
if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) {
|
||||||
// Fast path - this test is skipped, and there are more tests that will handle cleanup.
|
// Fast path - this test is skipped, and there are more tests that will handle cleanup.
|
||||||
testInfo.status = 'skipped';
|
testInfo.status = 'skipped';
|
||||||
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo, staticAnnotations));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,7 +494,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
|
|
||||||
this._currentTest = null;
|
this._currentTest = null;
|
||||||
setCurrentTestInfo(null);
|
setCurrentTestInfo(null);
|
||||||
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo, staticAnnotations));
|
||||||
|
|
||||||
const preserveOutput = this._config.config.preserveOutput === 'always' ||
|
const preserveOutput = this._config.config.preserveOutput === 'always' ||
|
||||||
(this._config.config.preserveOutput === 'failures-only' && testInfo._isFailure());
|
(this._config.config.preserveOutput === 'failures-only' && testInfo._isFailure());
|
||||||
|
@ -612,7 +614,7 @@ function buildTestBeginPayload(testInfo: TestInfoImpl): TestBeginPayload {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTestEndPayload(testInfo: TestInfoImpl): TestEndPayload {
|
function buildTestEndPayload(testInfo: TestInfoImpl, staticAnnotations: Set<Annotation>): TestEndPayload {
|
||||||
return {
|
return {
|
||||||
testId: testInfo.testId,
|
testId: testInfo.testId,
|
||||||
duration: testInfo.duration,
|
duration: testInfo.duration,
|
||||||
|
@ -620,7 +622,7 @@ function buildTestEndPayload(testInfo: TestInfoImpl): TestEndPayload {
|
||||||
errors: testInfo.errors,
|
errors: testInfo.errors,
|
||||||
hasNonRetriableError: testInfo._hasNonRetriableError,
|
hasNonRetriableError: testInfo._hasNonRetriableError,
|
||||||
expectedStatus: testInfo.expectedStatus,
|
expectedStatus: testInfo.expectedStatus,
|
||||||
annotations: testInfo.annotations,
|
annotations: testInfo.annotations.filter(a => !staticAnnotations.has(a)),
|
||||||
timeout: testInfo.timeout,
|
timeout: testInfo.timeout,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,6 +307,7 @@ export interface JSONReportTestResult {
|
||||||
body?: string;
|
body?: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
}[];
|
}[];
|
||||||
|
annotations: { type: string, description?: string }[];
|
||||||
errorLocation?: Location;
|
errorLocation?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,10 +446,8 @@ export interface TestCase {
|
||||||
* [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip),
|
* [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip),
|
||||||
* [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme)
|
* [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme)
|
||||||
* and
|
* and
|
||||||
* [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail);
|
* [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail)
|
||||||
* - annotations appended to
|
* prior to test execution.
|
||||||
* [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations) during the test
|
|
||||||
* execution.
|
|
||||||
*
|
*
|
||||||
* Annotations are available during test execution through
|
* Annotations are available during test execution through
|
||||||
* [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
|
* [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
|
||||||
|
@ -592,6 +591,34 @@ export interface TestError {
|
||||||
* A result of a single [TestCase](https://playwright.dev/docs/api/class-testcase) run.
|
* A result of a single [TestCase](https://playwright.dev/docs/api/class-testcase) run.
|
||||||
*/
|
*/
|
||||||
export interface TestResult {
|
export interface TestResult {
|
||||||
|
/**
|
||||||
|
* The list of annotations appended during test execution. Includes:
|
||||||
|
* - annotations implicitly added by methods
|
||||||
|
* [test.skip([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-skip),
|
||||||
|
* [test.fixme([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fixme)
|
||||||
|
* and
|
||||||
|
* [test.fail([title, details, body, condition, callback, description])](https://playwright.dev/docs/api/class-test#test-fail)
|
||||||
|
* during test execution;
|
||||||
|
* - annotations appended to
|
||||||
|
* [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
|
||||||
|
*
|
||||||
|
* Annotations are available during test execution through
|
||||||
|
* [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations).
|
||||||
|
*
|
||||||
|
* Learn more about [test annotations](https://playwright.dev/docs/test-annotations).
|
||||||
|
*/
|
||||||
|
annotations: Array<{
|
||||||
|
/**
|
||||||
|
* Annotation type, for example `'skip'` or `'fail'`.
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional description.
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of files or buffers attached during the test execution through
|
* The list of files or buffers attached during the test execution through
|
||||||
* [testInfo.attachments](https://playwright.dev/docs/api/class-testinfo#test-info-attachments).
|
* [testInfo.attachments](https://playwright.dev/docs/api/class-testinfo#test-info-attachments).
|
||||||
|
|
|
@ -86,6 +86,8 @@ export const TraceView: React.FC<{
|
||||||
};
|
};
|
||||||
}, [outputDir, item, setModel, counter, setCounter, pathSeparator]);
|
}, [outputDir, item, setModel, counter, setCounter, pathSeparator]);
|
||||||
|
|
||||||
|
const annotations = item.testCase ? [...item.testCase.annotations, ...(item.testCase.results[0]?.annotations ?? [])] : [];
|
||||||
|
|
||||||
return <Workbench
|
return <Workbench
|
||||||
key='workbench'
|
key='workbench'
|
||||||
model={model?.model}
|
model={model?.model}
|
||||||
|
@ -94,7 +96,7 @@ export const TraceView: React.FC<{
|
||||||
fallbackLocation={item.testFile}
|
fallbackLocation={item.testFile}
|
||||||
isLive={model?.isLive}
|
isLive={model?.isLive}
|
||||||
status={item.treeItem?.status}
|
status={item.treeItem?.status}
|
||||||
annotations={item.testCase?.annotations || []}
|
annotations={annotations}
|
||||||
onOpenExternally={onOpenExternally}
|
onOpenExternally={onOpenExternally}
|
||||||
revealSource={revealSource}
|
revealSource={revealSource}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -60,7 +60,7 @@ test('should access annotations in fixture', async ({ runInlineTest }) => {
|
||||||
});
|
});
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
const test = report.suites[0].specs[0].tests[0];
|
const test = report.suites[0].specs[0].tests[0];
|
||||||
expect(test.annotations).toEqual([{ type: 'slow', description: 'just slow' }, { type: 'myname', description: 'hello' }]);
|
expect(test.results[0].annotations).toEqual([{ type: 'slow', description: 'just slow' }, { type: 'myname', description: 'hello' }]);
|
||||||
expect(test.results[0].stdout).toEqual([{ text: 'console.log\n' }]);
|
expect(test.results[0].stdout).toEqual([{ text: 'console.log\n' }]);
|
||||||
expect(test.results[0].stderr).toEqual([{ text: 'console.error\n' }]);
|
expect(test.results[0].stderr).toEqual([{ text: 'console.error\n' }]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -434,7 +434,7 @@ export function expectTestHelper(result: RunResult) {
|
||||||
for (const test of tests) {
|
for (const test of tests) {
|
||||||
expect(test.expectedStatus, `title: ${title}`).toBe(expectedStatus);
|
expect(test.expectedStatus, `title: ${title}`).toBe(expectedStatus);
|
||||||
expect(test.status, `title: ${title}`).toBe(status);
|
expect(test.status, `title: ${title}`).toBe(status);
|
||||||
expect(test.annotations.map(a => a.type), `title: ${title}`).toEqual(annotations);
|
expect([...test.annotations, ...test.results.flatMap(r => r.annotations)].map(a => a.type), `title: ${title}`).toEqual(annotations);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -829,6 +829,33 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
await expect(page.locator('.test-case-annotation')).toHaveText('issue: I am not interested in this test');
|
await expect(page.locator('.test-case-annotation')).toHaveText('issue: I am not interested in this test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should render dynamic annotations at test result level', async ({ runInlineTest, page, showReport }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.js': `
|
||||||
|
module.exports = { timeout: 1500, retries: 3 };
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('annotated test', async ({}) => {
|
||||||
|
test.info().annotations.push({ type: 'foo', description: 'retry #' + test.info().retry });
|
||||||
|
test.info().annotations.push({ type: 'bar', description: 'static value' });
|
||||||
|
throw new Error('fail');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' });
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
|
||||||
|
await showReport();
|
||||||
|
await page.getByRole('link', { name: 'annotated test' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Retry #1' }).click();
|
||||||
|
await expect(page.getByTestId('test-case-annotations')).toContainText('foo: retry #1');
|
||||||
|
|
||||||
|
await page.getByRole('tab', { name: 'Retry #3' }).click();
|
||||||
|
await expect(page.getByTestId('test-case-annotations')).toContainText('foo: retry #3');
|
||||||
|
|
||||||
|
await expect(page.getByTestId('test-case-annotations').getByText('static value')).toHaveCount(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => {
|
test('should render annotations as link if needed', async ({ runInlineTest, page, showReport, server }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
|
@ -2431,12 +2458,13 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
test('annotated test',{ annotation :[{type:'key',description:'value'}]}, async ({}) => {expect(1).toBe(1);});
|
test('annotated test',{ annotation :[{type:'key',description:'value'}]}, async ({}) => {expect(1).toBe(1);});
|
||||||
|
test('slow test', () => { test.slow(); });
|
||||||
test('non-annotated test', async ({}) => {expect(1).toBe(2);});
|
test('non-annotated test', async ({}) => {expect(1).toBe(2);});
|
||||||
`,
|
`,
|
||||||
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(2);
|
||||||
expect(result.failed).toBe(1);
|
expect(result.failed).toBe(1);
|
||||||
|
|
||||||
await showReport();
|
await showReport();
|
||||||
|
@ -2456,6 +2484,11 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
await expect(page.getByText('non-annotated test')).not.toBeVisible();
|
await expect(page.getByText('non-annotated test')).not.toBeVisible();
|
||||||
await expect(page.getByText('annotated test')).toBeVisible();
|
await expect(page.getByText('annotated test')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await test.step('filter by result annotation', async () => {
|
||||||
|
await searchInput.fill('annot:slow');
|
||||||
|
await expect(page.getByText('slow test')).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tests should filter by fileName:line/column', async ({ runInlineTest, showReport, page }) => {
|
test('tests should filter by fileName:line/column', async ({ runInlineTest, showReport, page }) => {
|
||||||
|
|
|
@ -260,5 +260,5 @@ test('failed and skipped on retry should be marked as flaky', async ({ runInline
|
||||||
expect(result.failed).toBe(0);
|
expect(result.failed).toBe(0);
|
||||||
expect(result.flaky).toBe(1);
|
expect(result.flaky).toBe(1);
|
||||||
expect(result.output).toContain('Failed on first run');
|
expect(result.output).toContain('Failed on first run');
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'Skipped on first retry' }]);
|
expect(result.report.suites[0].specs[0].tests[0].results[1].annotations).toEqual([{ type: 'skip', description: 'Skipped on first retry' }]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -106,7 +106,7 @@ test('test modifiers should work', async ({ runInlineTest }) => {
|
||||||
const test = spec.tests[0];
|
const test = spec.tests[0];
|
||||||
expect(test.expectedStatus).toBe(expectedStatus);
|
expect(test.expectedStatus).toBe(expectedStatus);
|
||||||
expect(test.results[0].status).toBe(status);
|
expect(test.results[0].status).toBe(status);
|
||||||
expect(test.annotations).toEqual(annotations);
|
expect([...test.annotations, ...test.results.flatMap(r => r.annotations)]).toEqual(annotations);
|
||||||
};
|
};
|
||||||
expectTest('passed1', 'passed', 'passed', []);
|
expectTest('passed1', 'passed', 'passed', []);
|
||||||
expectTest('passed2', 'passed', 'passed', []);
|
expectTest('passed2', 'passed', 'passed', []);
|
||||||
|
@ -306,23 +306,6 @@ test.describe('test modifier annotations', () => {
|
||||||
expectTest('focused fail by suite', 'failed', 'expected', ['fail']);
|
expectTest('focused fail by suite', 'failed', 'expected', ['fail']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not multiple on retry', async ({ runInlineTest }) => {
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'a.test.ts': `
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
test('retry', () => {
|
|
||||||
test.info().annotations.push({ type: 'example' });
|
|
||||||
expect(1).toBe(2);
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
}, { retries: 3 });
|
|
||||||
const expectTest = expectTestHelper(result);
|
|
||||||
|
|
||||||
expect(result.exitCode).toBe(1);
|
|
||||||
expect(result.passed).toBe(0);
|
|
||||||
expectTest('retry', 'passed', 'unexpected', ['example']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not multiply on repeat-each', async ({ runInlineTest }) => {
|
test('should not multiply on repeat-each', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
@ -424,7 +407,7 @@ test('should skip inside fixture', async ({ runInlineTest }) => {
|
||||||
});
|
});
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.skipped).toBe(1);
|
expect(result.skipped).toBe(1);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
expect(result.report.suites[0].specs[0].tests[0].results[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('modifier with a function should throw in the test', async ({ runInlineTest }) => {
|
test('modifier with a function should throw in the test', async ({ runInlineTest }) => {
|
||||||
|
@ -477,8 +460,8 @@ test('test.skip with worker fixtures only should skip before hooks and tests', a
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.skipped).toBe(2);
|
expect(result.skipped).toBe(2);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([]);
|
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([]);
|
||||||
expect(result.report.suites[0].suites![0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
expect(result.report.suites[0].suites![0].specs[0].tests[0].results[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
||||||
expect(result.report.suites[0].suites![0].suites![0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
expect(result.report.suites[0].suites![0].suites![0].specs[0].tests[0].results[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'beforeEach',
|
'beforeEach',
|
||||||
'passed',
|
'passed',
|
||||||
|
@ -615,8 +598,8 @@ test('should skip all tests from beforeAll', async ({ runInlineTest }) => {
|
||||||
'beforeAll',
|
'beforeAll',
|
||||||
'afterAll',
|
'afterAll',
|
||||||
]);
|
]);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
expect(result.report.suites[0].specs[0].tests[0].results[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
||||||
expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
expect(result.report.suites[0].specs[1].tests[0].results[0].annotations).toEqual([{ type: 'skip', description: 'reason' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should report skipped tests in-order with correct properties', async ({ runInlineTest }) => {
|
test('should report skipped tests in-order with correct properties', async ({ runInlineTest }) => {
|
||||||
|
@ -712,7 +695,7 @@ test('static modifiers should be added in serial mode', async ({ runInlineTest }
|
||||||
expect(result.passed).toBe(0);
|
expect(result.passed).toBe(0);
|
||||||
expect(result.skipped).toBe(2);
|
expect(result.skipped).toBe(2);
|
||||||
expect(result.didNotRun).toBe(1);
|
expect(result.didNotRun).toBe(1);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].annotations).toEqual([{ type: 'slow' }]);
|
expect(result.report.suites[0].specs[0].tests[0].results[0].annotations).toEqual([{ type: 'slow' }]);
|
||||||
expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'fixme' }]);
|
expect(result.report.suites[0].specs[1].tests[0].annotations).toEqual([{ type: 'fixme' }]);
|
||||||
expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]);
|
expect(result.report.suites[0].specs[2].tests[0].annotations).toEqual([{ type: 'skip' }]);
|
||||||
expect(result.report.suites[0].specs[3].tests[0].annotations).toEqual([]);
|
expect(result.report.suites[0].specs[3].tests[0].annotations).toEqual([]);
|
||||||
|
|
|
@ -125,6 +125,7 @@ export interface JSONReportTestResult {
|
||||||
body?: string;
|
body?: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
}[];
|
}[];
|
||||||
|
annotations: { type: string, description?: string }[];
|
||||||
errorLocation?: Location;
|
errorLocation?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue