feat(step.stack): expose step.stack for better current line in IDE
This commit is contained in:
parent
eff5cd6dbb
commit
78a84a262d
|
|
@ -38,6 +38,12 @@ Error thrown during the step execution, if any.
|
||||||
|
|
||||||
Parent step, if any.
|
Parent step, if any.
|
||||||
|
|
||||||
|
## property: TestStep.stack
|
||||||
|
* since: v1.51
|
||||||
|
- type: <[Array]<[Location]>>
|
||||||
|
|
||||||
|
Call stack for this step.
|
||||||
|
|
||||||
## property: TestStep.startTime
|
## property: TestStep.startTime
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: <[Date]>
|
- type: <[Date]>
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,19 @@
|
||||||
import { monotonicTime } from './';
|
import { monotonicTime } from './';
|
||||||
|
|
||||||
export async function raceAgainstDeadline<T>(cb: () => Promise<T>, deadline: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> {
|
export async function raceAgainstDeadline<T>(cb: () => Promise<T>, deadline: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> {
|
||||||
|
// Avoid indirections to preserve better stacks.
|
||||||
|
if (deadline === 0) {
|
||||||
|
const result = await cb();
|
||||||
|
return { result, timedOut: false };
|
||||||
|
}
|
||||||
|
|
||||||
let timer: NodeJS.Timeout | undefined;
|
let timer: NodeJS.Timeout | undefined;
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
cb().then(result => {
|
cb().then(result => {
|
||||||
return { result, timedOut: false };
|
return { result, timedOut: false };
|
||||||
}),
|
}),
|
||||||
new Promise<{ timedOut: true }>(resolve => {
|
new Promise<{ timedOut: true }>(resolve => {
|
||||||
const kMaxDeadline = 2147483647; // 2^31-1
|
const timeout = deadline - monotonicTime();
|
||||||
const timeout = (deadline || kMaxDeadline) - monotonicTime();
|
|
||||||
timer = setTimeout(() => resolve({ timedOut: true }), timeout);
|
timer = setTimeout(() => resolve({ timedOut: true }), timeout);
|
||||||
}),
|
}),
|
||||||
]).finally(() => {
|
]).finally(() => {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ export type StepBeginPayload = {
|
||||||
title: string;
|
title: string;
|
||||||
category: string;
|
category: string;
|
||||||
wallTime: number; // milliseconds since unix epoch
|
wallTime: number; // milliseconds since unix epoch
|
||||||
location?: { file: string, line: number, column: number };
|
stack: { file: string, line: number, column: number }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StepEndPayload = {
|
export type StepEndPayload = {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export class TestTypeImpl {
|
||||||
this.test = test;
|
this.test = test;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _currentSuite(location: Location, title: string): Suite | undefined {
|
private _currentSuite(title: string): Suite | undefined {
|
||||||
const suite = currentlyLoadingFileSuite();
|
const suite = currentlyLoadingFileSuite();
|
||||||
if (!suite) {
|
if (!suite) {
|
||||||
throw new Error([
|
throw new Error([
|
||||||
|
|
@ -86,7 +86,7 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail' | 'fail.only', location: Location, title: string, fnOrDetails: Function | TestDetails, fn?: Function) {
|
private _createTest(type: 'default' | 'only' | 'skip' | 'fixme' | 'fail' | 'fail.only', location: Location, title: string, fnOrDetails: Function | TestDetails, fn?: Function) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, 'test()');
|
const suite = this._currentSuite('test()');
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
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) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, 'test.describe()');
|
const suite = this._currentSuite('test.describe()');
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, title: string | Function, fn?: Function) {
|
private _hook(name: 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll', location: Location, title: string | Function, fn?: Function) {
|
||||||
const suite = this._currentSuite(location, `test.${name}()`);
|
const suite = this._currentSuite(`test.${name}()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
if (typeof title === 'function') {
|
if (typeof title === 'function') {
|
||||||
|
|
@ -182,7 +182,7 @@ export class TestTypeImpl {
|
||||||
|
|
||||||
private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
private _configure(location: Location, options: { mode?: 'default' | 'parallel' | 'serial', retries?: number, timeout?: number }) {
|
||||||
throwIfRunningInsideJest();
|
throwIfRunningInsideJest();
|
||||||
const suite = this._currentSuite(location, `test.describe.configure()`);
|
const suite = this._currentSuite(`test.describe.configure()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -252,7 +252,7 @@ export class TestTypeImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _use(location: Location, fixtures: Fixtures) {
|
private _use(location: Location, fixtures: Fixtures) {
|
||||||
const suite = this._currentSuite(location, `test.use()`);
|
const suite = this._currentSuite(`test.use()`);
|
||||||
if (!suite)
|
if (!suite)
|
||||||
return;
|
return;
|
||||||
suite._use.push({ fixtures, location });
|
suite._use.push({ fixtures, location });
|
||||||
|
|
@ -263,11 +263,11 @@ export class TestTypeImpl {
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
throw new Error(`test.step() can only be called from a test`);
|
throw new Error(`test.step() can only be called from a test`);
|
||||||
if (expectation === 'skip') {
|
if (expectation === 'skip') {
|
||||||
const step = testInfo._addStep({ category: 'test.step.skip', title, location: options.location, box: options.box });
|
const step = testInfo._addStep({ category: 'test.step.skip', title, box: options.box }, undefined, options.location ? [options.location] : undefined);
|
||||||
step.complete({});
|
step.complete({});
|
||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
|
const step = testInfo._addStep({ category: 'test.step', title, box: options.box }, undefined, options.location ? [options.location] : undefined);
|
||||||
return await zones.run('stepZone', step, async () => {
|
return await zones.run('stepZone', step, async () => {
|
||||||
try {
|
try {
|
||||||
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
|
let result: Awaited<ReturnType<typeof raceAgainstDeadline<T>>> | undefined = undefined;
|
||||||
|
|
|
||||||
|
|
@ -273,12 +273,11 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
}
|
}
|
||||||
// In the general case, create a step for each api call and connect them through the stepId.
|
// In the general case, create a step for each api call and connect them through the stepId.
|
||||||
const step = testInfo._addStep({
|
const step = testInfo._addStep({
|
||||||
location: data.frames[0],
|
|
||||||
category: 'pw:api',
|
category: 'pw:api',
|
||||||
title: renderApiCall(data.apiName, data.params),
|
title: renderApiCall(data.apiName, data.params),
|
||||||
apiName: data.apiName,
|
apiName: data.apiName,
|
||||||
params: data.params,
|
params: data.params,
|
||||||
}, tracingGroupSteps[tracingGroupSteps.length - 1]);
|
}, tracingGroupSteps[tracingGroupSteps.length - 1], data.frames);
|
||||||
data.userData = step;
|
data.userData = step;
|
||||||
data.stepId = step.stepId;
|
data.stepId = step.stepId;
|
||||||
if (data.apiName === 'tracing.group')
|
if (data.apiName === 'tracing.group')
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,8 @@ export type JsonTestStepStart = {
|
||||||
title: string;
|
title: string;
|
||||||
category: string,
|
category: string,
|
||||||
startTime: number;
|
startTime: number;
|
||||||
location?: reporterTypes.Location;
|
// Best effort to keep step struct small.
|
||||||
|
stack?: reporterTypes.Location | reporterTypes.Location[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonTestStepEnd = {
|
export type JsonTestStepEnd = {
|
||||||
|
|
@ -249,8 +250,8 @@ export class TeleReporterReceiver {
|
||||||
const result = test.results.find(r => r._id === resultId)!;
|
const result = test.results.find(r => r._id === resultId)!;
|
||||||
const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : undefined;
|
const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : undefined;
|
||||||
|
|
||||||
const location = this._absoluteLocation(payload.location);
|
const stack = Array.isArray(payload.stack) ? payload.stack.map(l => this._absoluteLocation(l)) : this._absoluteLocation(payload.stack);
|
||||||
const step = new TeleTestStep(payload, parentStep, location, result);
|
const step = new TeleTestStep(payload, parentStep, stack, result);
|
||||||
if (parentStep)
|
if (parentStep)
|
||||||
parentStep.steps.push(step);
|
parentStep.steps.push(step);
|
||||||
else
|
else
|
||||||
|
|
@ -426,8 +427,8 @@ export class TeleSuite implements reporterTypes.Suite {
|
||||||
}
|
}
|
||||||
|
|
||||||
allTests(): reporterTypes.TestCase[] {
|
allTests(): reporterTypes.TestCase[] {
|
||||||
const result: reporterTypes.TestCase[] = [];
|
const result: TeleTestCase[] = [];
|
||||||
const visit = (suite: reporterTypes.Suite) => {
|
const visit = (suite: TeleSuite) => {
|
||||||
for (const entry of suite.entries()) {
|
for (const entry of suite.entries()) {
|
||||||
if (entry.type === 'test')
|
if (entry.type === 'test')
|
||||||
result.push(entry);
|
result.push(entry);
|
||||||
|
|
@ -511,6 +512,7 @@ class TeleTestStep implements reporterTypes.TestStep {
|
||||||
title: string;
|
title: string;
|
||||||
category: string;
|
category: string;
|
||||||
location: reporterTypes.Location | undefined;
|
location: reporterTypes.Location | undefined;
|
||||||
|
stack: reporterTypes.Location[];
|
||||||
parent: reporterTypes.TestStep | undefined;
|
parent: reporterTypes.TestStep | undefined;
|
||||||
duration: number = -1;
|
duration: number = -1;
|
||||||
steps: reporterTypes.TestStep[] = [];
|
steps: reporterTypes.TestStep[] = [];
|
||||||
|
|
@ -521,10 +523,11 @@ class TeleTestStep implements reporterTypes.TestStep {
|
||||||
|
|
||||||
private _startTime: number = 0;
|
private _startTime: number = 0;
|
||||||
|
|
||||||
constructor(payload: JsonTestStepStart, parentStep: reporterTypes.TestStep | undefined, location: reporterTypes.Location | undefined, result: TeleTestResult) {
|
constructor(payload: JsonTestStepStart, parentStep: reporterTypes.TestStep | undefined, stackOrLocation: reporterTypes.Location | reporterTypes.Location[] | undefined, result: TeleTestResult) {
|
||||||
this.title = payload.title;
|
this.title = payload.title;
|
||||||
this.category = payload.category;
|
this.category = payload.category;
|
||||||
this.location = location;
|
this.stack = Array.isArray(stackOrLocation) ? stackOrLocation : (stackOrLocation ? [stackOrLocation] : []);
|
||||||
|
this.location = this.stack[0];
|
||||||
this.parent = parentStep;
|
this.parent = parentStep;
|
||||||
this._startTime = payload.startTime;
|
this._startTime = payload.startTime;
|
||||||
this._result = result;
|
this._result = result;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class WebSocketTestServerTransport implements TestServerTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
onmessage(listener: (message: string) => void) {
|
onmessage(listener: (message: string) => void) {
|
||||||
this._ws.addEventListener('message', event => listener(event.data));
|
this._ws.addEventListener('message', event => listener(String(event.data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
onopen(listener: () => void) {
|
onopen(listener: () => void) {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ type BlobReporterOptions = {
|
||||||
_commandHash: string;
|
_commandHash: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const currentBlobReportVersion = 2;
|
export const currentBlobReportVersion = 3;
|
||||||
|
|
||||||
export type BlobReportMetadata = {
|
export type BlobReportMetadata = {
|
||||||
version: number;
|
version: number;
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ export async function createMergedReport(config: FullConfigInternal, dir: string
|
||||||
await dispatchEvents(eventData.prologue);
|
await dispatchEvents(eventData.prologue);
|
||||||
for (const { reportFile, eventPatchers, metadata } of eventData.reports) {
|
for (const { reportFile, eventPatchers, metadata } of eventData.reports) {
|
||||||
const reportJsonl = await fs.promises.readFile(reportFile);
|
const reportJsonl = await fs.promises.readFile(reportFile);
|
||||||
const events = parseTestEvents(reportJsonl);
|
let events = parseTestEvents(reportJsonl);
|
||||||
|
events = modernizer.modernize(metadata.version, events);
|
||||||
new JsonStringInternalizer(stringPool).traverse(events);
|
new JsonStringInternalizer(stringPool).traverse(events);
|
||||||
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
|
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
|
||||||
if (metadata.name)
|
if (metadata.name)
|
||||||
|
|
@ -480,7 +481,8 @@ class PathSeparatorPatcher {
|
||||||
}
|
}
|
||||||
if (jsonEvent.method === 'onStepBegin') {
|
if (jsonEvent.method === 'onStepBegin') {
|
||||||
const step = jsonEvent.params.step as JsonTestStepStart;
|
const step = jsonEvent.params.step as JsonTestStepStart;
|
||||||
this._updateLocation(step.location);
|
for (const stackFrame of Array.isArray(step.stack) ? step.stack : [step.stack])
|
||||||
|
this._updateLocation(stackFrame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (jsonEvent.method === 'onStepEnd') {
|
if (jsonEvent.method === 'onStepEnd') {
|
||||||
|
|
@ -589,6 +591,14 @@ class BlobModernizer {
|
||||||
return event;
|
return event;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_modernize_2_to_3(events: JsonEvent[]): JsonEvent[] {
|
||||||
|
return events.map(event => {
|
||||||
|
if (event.method === 'onStepBegin')
|
||||||
|
(event.params.step as JsonTestStepStart).stack = event.params.step.location;
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const modernizer = new BlobModernizer();
|
const modernizer = new BlobModernizer();
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@ export class TeleReporterEmitter implements ReporterV2 {
|
||||||
title: step.title,
|
title: step.title,
|
||||||
category: step.category,
|
category: step.category,
|
||||||
startTime: +step.startTime,
|
startTime: +step.startTime,
|
||||||
location: this._relativeLocation(step.location),
|
stack: this._relativeStack(step.stack),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,6 +260,14 @@ export class TeleReporterEmitter implements ReporterV2 {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _relativeStack(stack: reporterTypes.Location[]): undefined | reporterTypes.Location | reporterTypes.Location[] {
|
||||||
|
if (!stack.length)
|
||||||
|
return undefined;
|
||||||
|
if (stack.length === 1)
|
||||||
|
return this._relativeLocation(stack[0]);
|
||||||
|
return stack.map(frame => this._relativeLocation(frame));
|
||||||
|
}
|
||||||
|
|
||||||
private _relativeLocation(location: reporterTypes.Location): reporterTypes.Location;
|
private _relativeLocation(location: reporterTypes.Location): reporterTypes.Location;
|
||||||
private _relativeLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined;
|
private _relativeLocation(location?: reporterTypes.Location): reporterTypes.Location | undefined;
|
||||||
private _relativeLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined {
|
private _relativeLocation(location: reporterTypes.Location | undefined): reporterTypes.Location | undefined {
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,8 @@ class JobDispatcher {
|
||||||
duration: -1,
|
duration: -1,
|
||||||
steps: [],
|
steps: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
location: params.location,
|
location: params.stack[0],
|
||||||
|
stack: params.stack,
|
||||||
};
|
};
|
||||||
steps.set(params.stepId, step);
|
steps.set(params.stepId, step);
|
||||||
(parentStep || result).steps.push(step);
|
(parentStep || result).steps.push(step);
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export function filterStackFile(file: string) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
|
export function filteredStackTrace(rawStack: RawStack): Location[] {
|
||||||
const frames: StackFrame[] = [];
|
const frames: StackFrame[] = [];
|
||||||
for (const line of rawStack) {
|
for (const line of rawStack) {
|
||||||
const frame = parseStackTraceLine(line);
|
const frame = parseStackTraceLine(line);
|
||||||
|
|
@ -59,7 +59,7 @@ export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
|
||||||
continue;
|
continue;
|
||||||
if (!filterStackFile(frame.file))
|
if (!filterStackFile(frame.file))
|
||||||
continue;
|
continue;
|
||||||
frames.push(frame);
|
frames.push({ file: frame.file, line: frame.line, column: frame.column });
|
||||||
}
|
}
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import type { Annotation, FullConfigInternal, FullProjectInternal } from '../com
|
||||||
import type { FullConfig, Location } from '../../types/testReporter';
|
import type { FullConfig, Location } from '../../types/testReporter';
|
||||||
import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util';
|
import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normalizeAndSaveAttachment, trimLongString, windowsFilesystemFriendlyLength } from '../util';
|
||||||
import { TestTracing } from './testTracing';
|
import { TestTracing } from './testTracing';
|
||||||
import type { StackFrame } from '@protocol/channels';
|
|
||||||
import { testInfoError } from './util';
|
import { testInfoError } from './util';
|
||||||
|
|
||||||
export interface TestStepInternal {
|
export interface TestStepInternal {
|
||||||
|
|
@ -35,8 +34,8 @@ export interface TestStepInternal {
|
||||||
stepId: string;
|
stepId: string;
|
||||||
title: string;
|
title: string;
|
||||||
category: string;
|
category: string;
|
||||||
location?: Location;
|
stack: Location[];
|
||||||
boxedStack?: StackFrame[];
|
boxedStack?: Location[];
|
||||||
steps: TestStepInternal[];
|
steps: TestStepInternal[];
|
||||||
endWallTime?: number;
|
endWallTime?: number;
|
||||||
apiName?: string;
|
apiName?: string;
|
||||||
|
|
@ -244,7 +243,7 @@ export class TestInfoImpl implements TestInfo {
|
||||||
?? this._findLastStageStep(this._steps); // If no parent step on stack, assume the current stage as parent.
|
?? this._findLastStageStep(this._steps); // If no parent step on stack, assume the current stage as parent.
|
||||||
}
|
}
|
||||||
|
|
||||||
_addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachmentIndices'>, parentStep?: TestStepInternal): TestStepInternal {
|
_addStep(data: Omit<TestStepInternal, 'complete' | 'stepId' | 'steps' | 'attachmentIndices' | 'stack'>, parentStep?: TestStepInternal, stackOverride?: Location[]): TestStepInternal {
|
||||||
const stepId = `${data.category}@${++this._lastStepId}`;
|
const stepId = `${data.category}@${++this._lastStepId}`;
|
||||||
|
|
||||||
if (data.isStage) {
|
if (data.isStage) {
|
||||||
|
|
@ -255,18 +254,20 @@ export class TestInfoImpl implements TestInfo {
|
||||||
parentStep = this._parentStep();
|
parentStep = this._parentStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredStack = filteredStackTrace(captureRawStack());
|
const filteredStack = stackOverride ? stackOverride : filteredStackTrace(captureRawStack());
|
||||||
data.boxedStack = parentStep?.boxedStack;
|
data.boxedStack = parentStep?.boxedStack;
|
||||||
|
let stack = filteredStack;
|
||||||
if (!data.boxedStack && data.box) {
|
if (!data.boxedStack && data.box) {
|
||||||
data.boxedStack = filteredStack.slice(1);
|
data.boxedStack = filteredStack.slice(1);
|
||||||
data.location = data.location || data.boxedStack[0];
|
// Only steps with box: true get boxed stack. Inner steps have original stack for better traceability.
|
||||||
|
stack = data.boxedStack;
|
||||||
}
|
}
|
||||||
data.location = data.location || filteredStack[0];
|
|
||||||
|
|
||||||
const attachmentIndices: number[] = [];
|
const attachmentIndices: number[] = [];
|
||||||
const step: TestStepInternal = {
|
const step: TestStepInternal = {
|
||||||
stepId,
|
stepId,
|
||||||
...data,
|
...data,
|
||||||
|
stack,
|
||||||
steps: [],
|
steps: [],
|
||||||
attachmentIndices,
|
attachmentIndices,
|
||||||
complete: result => {
|
complete: result => {
|
||||||
|
|
@ -319,10 +320,10 @@ export class TestInfoImpl implements TestInfo {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
category: data.category,
|
category: data.category,
|
||||||
wallTime: Date.now(),
|
wallTime: Date.now(),
|
||||||
location: data.location,
|
stack,
|
||||||
};
|
};
|
||||||
this._onStepBegin(payload);
|
this._onStepBegin(payload);
|
||||||
this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, data.category, data.apiName || data.title, data.params, data.location ? [data.location] : []);
|
this._tracing.appendBeforeActionForStep(stepId, parentStep?.stepId, data.category, data.apiName || data.title, data.params, stack);
|
||||||
return step;
|
return step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,7 +352,7 @@ export class TestInfoImpl implements TestInfo {
|
||||||
const location = stage.runnable?.location ? ` at "${formatLocation(stage.runnable.location)}"` : ``;
|
const location = stage.runnable?.location ? ` at "${formatLocation(stage.runnable.location)}"` : ``;
|
||||||
debugTest(`started stage "${stage.title}"${location}`);
|
debugTest(`started stage "${stage.title}"${location}`);
|
||||||
}
|
}
|
||||||
stage.step = stage.stepInfo ? this._addStep({ ...stage.stepInfo, title: stage.title, isStage: true }) : undefined;
|
stage.step = stage.stepInfo ? this._addStep({ category: stage.stepInfo.category, title: stage.title, isStage: true }, undefined, stage.stepInfo.location ? [stage.stepInfo.location] : undefined) : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._timeoutManager.withRunnable(stage.runnable, async () => {
|
await this._timeoutManager.withRunnable(stage.runnable, async () => {
|
||||||
|
|
|
||||||
|
|
@ -555,7 +555,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
let firstError: Error | undefined;
|
let firstError: Error | undefined;
|
||||||
const hooks = suites.map(suite => this._collectHooksAndModifiers(suite, type, testInfo)).flat();
|
const hooks = suites.map(suite => this._collectHooksAndModifiers(suite, type, testInfo)).flat();
|
||||||
for (const hook of hooks) {
|
for (const hook of hooks) {
|
||||||
const runnable = { type: hook.type, location: hook.location, slot };
|
const runnable = { type: hook.type, stack: [hook.location], slot };
|
||||||
if (testInfo._timeoutManager.isTimeExhaustedFor(runnable)) {
|
if (testInfo._timeoutManager.isTimeExhaustedFor(runnable)) {
|
||||||
// Do not run hooks that will timeout right away.
|
// Do not run hooks that will timeout right away.
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
5
packages/playwright/types/testReporter.d.ts
vendored
5
packages/playwright/types/testReporter.d.ts
vendored
|
|
@ -747,6 +747,11 @@ export interface TestStep {
|
||||||
*/
|
*/
|
||||||
parent?: TestStep;
|
parent?: TestStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call stack for this step.
|
||||||
|
*/
|
||||||
|
stack: Array<Location>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start time of this particular test step.
|
* Start time of this particular test step.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1432,7 +1432,7 @@ test('blob report should include version', async ({ runInlineTest }) => {
|
||||||
|
|
||||||
const events = await extractReport(test.info().outputPath('blob-report', 'report.zip'), test.info().outputPath('tmp'));
|
const events = await extractReport(test.info().outputPath('blob-report', 'report.zip'), test.info().outputPath('tmp'));
|
||||||
const metadataEvent = events.find(e => e.method === 'onBlobReportMetadata');
|
const metadataEvent = events.find(e => e.method === 'onBlobReportMetadata');
|
||||||
expect(metadataEvent.params.version).toBe(2);
|
expect(metadataEvent.params.version).toBe(3);
|
||||||
expect(metadataEvent.params.userAgent).toBe(getUserAgent());
|
expect(metadataEvent.params.userAgent).toBe(getUserAgent());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@ function formatLocation(location?: Location) {
|
||||||
return ' @ ' + path.basename(location.file) + ':' + location.line;
|
return ' @ ' + path.basename(location.file) + ':' + location.line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatStackFrames(stack: Location[]) {
|
||||||
|
if (stack.length < 2)
|
||||||
|
return '';
|
||||||
|
return ' @stack [' + stack.map(l => path.basename(l.file) + ':' + l.line).join(' | ') + ']';
|
||||||
|
}
|
||||||
|
|
||||||
function formatStack(indent: string, rawStack: string) {
|
function formatStack(indent: string, rawStack: string) {
|
||||||
let stack = rawStack.split('\\n').filter(s => s.startsWith(' at '));
|
let stack = rawStack.split('\\n').filter(s => s.startsWith(' at '));
|
||||||
stack = stack.map(s => {
|
stack = stack.map(s => {
|
||||||
|
|
@ -75,7 +81,8 @@ export default class MyReporter implements Reporter {
|
||||||
let location = '';
|
let location = '';
|
||||||
if (step.location)
|
if (step.location)
|
||||||
location = formatLocation(step.location);
|
location = formatLocation(step.location);
|
||||||
console.log(formatPrefix(step.category) + indent + step.title + location);
|
let stack = formatStackFrames(step.stack);
|
||||||
|
console.log(formatPrefix(step.category) + indent + step.title + location + stack);
|
||||||
if (step.error) {
|
if (step.error) {
|
||||||
const errorLocation = this.printErrorLocation ? formatLocation(step.error.location) : '';
|
const errorLocation = this.printErrorLocation ? formatLocation(step.error.location) : '';
|
||||||
console.log(formatPrefix(step.category) + indent + '↪ error: ' + this.trimError(step.error.message!) + errorLocation);
|
console.log(formatPrefix(step.category) + indent + '↪ error: ' + this.trimError(step.error.message!) + errorLocation);
|
||||||
|
|
@ -143,11 +150,11 @@ pw:api | browser.newContext
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
test.step |outer step 1 @ a.test.ts:4
|
test.step |outer step 1 @ a.test.ts:4
|
||||||
test.step | inner step 1.1 @ a.test.ts:5
|
test.step | inner step 1.1 @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
test.step | inner step 1.2 @ a.test.ts:6
|
test.step | inner step 1.2 @ a.test.ts:6 @stack [a.test.ts:6 | a.test.ts:4]
|
||||||
test.step |outer step 2 @ a.test.ts:8
|
test.step |outer step 2 @ a.test.ts:8
|
||||||
test.step | inner step 2.1 @ a.test.ts:9
|
test.step | inner step 2.1 @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:8]
|
||||||
test.step | inner step 2.2 @ a.test.ts:10
|
test.step | inner step 2.2 @ a.test.ts:10 @stack [a.test.ts:10 | a.test.ts:8]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
|
|
@ -485,9 +492,9 @@ test('should mark step as failed when soft expect fails', async ({ runInlineTest
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step |outer @ a.test.ts:4
|
test.step |outer @ a.test.ts:4
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step | inner @ a.test.ts:5
|
test.step | inner @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.soft.toBe @ a.test.ts:6
|
expect | expect.soft.toBe @ a.test.ts:6 @stack [a.test.ts:6 | a.test.ts:5 | a.test.ts:4]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step |passing @ a.test.ts:9
|
test.step |passing @ a.test.ts:9
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
|
|
@ -555,12 +562,12 @@ pw:api | browser.newContext
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
test.step |grand @ a.test.ts:20
|
test.step |grand @ a.test.ts:20
|
||||||
test.step | parent1 @ a.test.ts:22
|
test.step | parent1 @ a.test.ts:22 @stack [a.test.ts:22 | a.test.ts:20]
|
||||||
test.step | child1 @ a.test.ts:23
|
test.step | child1 @ a.test.ts:23 @stack [a.test.ts:23 | a.test.ts:22 | a.test.ts:20]
|
||||||
pw:api | page.click(body) @ a.test.ts:24
|
pw:api | page.click(body) @ a.test.ts:24 @stack [a.test.ts:24 | a.test.ts:23 | a.test.ts:22 | a.test.ts:20]
|
||||||
test.step | parent2 @ a.test.ts:27
|
test.step | parent2 @ a.test.ts:27 @stack [a.test.ts:27 | a.test.ts:20]
|
||||||
test.step | child2 @ a.test.ts:28
|
test.step | child2 @ a.test.ts:28 @stack [a.test.ts:28 | a.test.ts:27 | a.test.ts:20]
|
||||||
expect | expect.toBeVisible @ a.test.ts:29
|
expect | expect.toBeVisible @ a.test.ts:29 @stack [a.test.ts:29 | a.test.ts:28 | a.test.ts:27 | a.test.ts:20]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
hook | afterEach hook @ a.test.ts:15
|
hook | afterEach hook @ a.test.ts:15
|
||||||
test.step | in afterEach @ a.test.ts:16
|
test.step | in afterEach @ a.test.ts:16
|
||||||
|
|
@ -701,15 +708,15 @@ test('should propagate nested soft errors', async ({ runInlineTest }) => {
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step |first outer @ a.test.ts:4
|
test.step |first outer @ a.test.ts:4
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step | first inner @ a.test.ts:5
|
test.step | first inner @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.soft.toBe @ a.test.ts:6
|
expect | expect.soft.toBe @ a.test.ts:6 @stack [a.test.ts:6 | a.test.ts:5 | a.test.ts:4]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step |second outer @ a.test.ts:10
|
test.step |second outer @ a.test.ts:10
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step | second inner @ a.test.ts:11
|
test.step | second inner @ a.test.ts:11 @stack [a.test.ts:11 | a.test.ts:10]
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toBe @ a.test.ts:12
|
expect | expect.toBe @ a.test.ts:12 @stack [a.test.ts:12 | a.test.ts:11 | a.test.ts:10]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
hook |Worker Cleanup
|
hook |Worker Cleanup
|
||||||
|
|
@ -747,14 +754,14 @@ test('should not propagate nested hard errors', async ({ runInlineTest }) => {
|
||||||
expect(stripAnsi(result.output)).toBe(`
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step |first outer @ a.test.ts:4
|
test.step |first outer @ a.test.ts:4
|
||||||
test.step | first inner @ a.test.ts:5
|
test.step | first inner @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
expect | expect.toBe @ a.test.ts:7
|
expect | expect.toBe @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:5 | a.test.ts:4]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step |second outer @ a.test.ts:13
|
test.step |second outer @ a.test.ts:13
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step | second inner @ a.test.ts:14
|
test.step | second inner @ a.test.ts:14 @stack [a.test.ts:14 | a.test.ts:13]
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toBe @ a.test.ts:15
|
expect | expect.toBe @ a.test.ts:15 @stack [a.test.ts:15 | a.test.ts:14 | a.test.ts:13]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
hook |Worker Cleanup
|
hook |Worker Cleanup
|
||||||
|
|
@ -783,7 +790,7 @@ test.step |boxed step @ a.test.ts:3
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:4
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:4
|
||||||
test.step | at a.test.ts:4:27
|
test.step | at a.test.ts:4:27
|
||||||
test.step | at a.test.ts:3:26
|
test.step | at a.test.ts:3:26
|
||||||
expect | expect.toBe @ a.test.ts:4
|
expect | expect.toBe @ a.test.ts:4 @stack [a.test.ts:4 | a.test.ts:3]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:4
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:4
|
||||||
expect | at a.test.ts:4:27
|
expect | at a.test.ts:4:27
|
||||||
expect | at a.test.ts:3:26
|
expect | at a.test.ts:3:26
|
||||||
|
|
@ -818,7 +825,7 @@ hook |Before Hooks
|
||||||
test.step |boxed step @ a.test.ts:8
|
test.step |boxed step @ a.test.ts:8
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
||||||
test.step | at a.test.ts:8:21
|
test.step | at a.test.ts:8:21
|
||||||
expect | expect.toBe @ a.test.ts:5
|
expect | expect.toBe @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4 | a.test.ts:8]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
||||||
expect | at a.test.ts:8:21
|
expect | at a.test.ts:8:21
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
|
|
@ -851,7 +858,7 @@ hook |Before Hooks
|
||||||
test.step |boxed step @ a.test.ts:8
|
test.step |boxed step @ a.test.ts:8
|
||||||
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
test.step |↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
||||||
test.step | at a.test.ts:8:21
|
test.step | at a.test.ts:8:21
|
||||||
expect | expect.soft.toBe @ a.test.ts:5
|
expect | expect.soft.toBe @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4 | a.test.ts:8]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality @ a.test.ts:8
|
||||||
expect | at a.test.ts:8:21
|
expect | at a.test.ts:8:21
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
|
|
@ -930,16 +937,16 @@ test('step inside expect.toPass', async ({ runInlineTest }) => {
|
||||||
expect(stripAnsi(result.output)).toBe(`
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step |step 1 @ a.test.ts:4
|
test.step |step 1 @ a.test.ts:4
|
||||||
step | expect.toPass @ a.test.ts:11
|
step | expect.toPass @ a.test.ts:11 @stack [a.test.ts:11 | a.test.ts:4]
|
||||||
test.step | step 2, attempt: 0 @ a.test.ts:7
|
test.step | step 2, attempt: 0 @ a.test.ts:7
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toBe @ a.test.ts:9
|
expect | expect.toBe @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:7]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
test.step | step 2, attempt: 1 @ a.test.ts:7
|
test.step | step 2, attempt: 1 @ a.test.ts:7
|
||||||
expect | expect.toBe @ a.test.ts:9
|
expect | expect.toBe @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:7]
|
||||||
test.step | step 3 @ a.test.ts:12
|
test.step | step 3 @ a.test.ts:12 @stack [a.test.ts:12 | a.test.ts:4]
|
||||||
test.step | step 4 @ a.test.ts:13
|
test.step | step 4 @ a.test.ts:13 @stack [a.test.ts:13 | a.test.ts:12 | a.test.ts:4]
|
||||||
expect | expect.toBe @ a.test.ts:14
|
expect | expect.toBe @ a.test.ts:14 @stack [a.test.ts:14 | a.test.ts:13 | a.test.ts:12 | a.test.ts:4]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
@ -979,13 +986,13 @@ fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
step |expect.toPass @ a.test.ts:11
|
step |expect.toPass @ a.test.ts:11
|
||||||
pw:api | page.goto(about:blank) @ a.test.ts:6
|
pw:api | page.goto(about:blank) @ a.test.ts:6
|
||||||
test.step | inner step attempt: 0 @ a.test.ts:7
|
test.step | inner step attempt: 0 @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:5]
|
||||||
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
test.step | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toBe @ a.test.ts:9
|
expect | expect.toBe @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:7 | a.test.ts:5]
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
pw:api | page.goto(about:blank) @ a.test.ts:6
|
pw:api | page.goto(about:blank) @ a.test.ts:6
|
||||||
test.step | inner step attempt: 1 @ a.test.ts:7
|
test.step | inner step attempt: 1 @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:5]
|
||||||
expect | expect.toBe @ a.test.ts:9
|
expect | expect.toBe @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:7 | a.test.ts:5]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
|
|
@ -1030,13 +1037,13 @@ fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
step |expect.poll.toHaveLength @ a.test.ts:14
|
step |expect.poll.toHaveLength @ a.test.ts:14
|
||||||
pw:api | page.goto(about:blank) @ a.test.ts:7
|
pw:api | page.goto(about:blank) @ a.test.ts:7
|
||||||
test.step | inner step attempt: 0 @ a.test.ts:8
|
test.step | inner step attempt: 0 @ a.test.ts:8 @stack [a.test.ts:8 | a.test.ts:6]
|
||||||
expect | expect.toBe @ a.test.ts:10
|
expect | expect.toBe @ a.test.ts:10 @stack [a.test.ts:10 | a.test.ts:8 | a.test.ts:6]
|
||||||
expect | expect.toHaveLength @ a.test.ts:6
|
expect | expect.toHaveLength @ a.test.ts:6
|
||||||
expect | ↪ error: Error: expect(received).toHaveLength(expected)
|
expect | ↪ error: Error: expect(received).toHaveLength(expected)
|
||||||
pw:api | page.goto(about:blank) @ a.test.ts:7
|
pw:api | page.goto(about:blank) @ a.test.ts:7
|
||||||
test.step | inner step attempt: 1 @ a.test.ts:8
|
test.step | inner step attempt: 1 @ a.test.ts:8 @stack [a.test.ts:8 | a.test.ts:6]
|
||||||
expect | expect.toBe @ a.test.ts:10
|
expect | expect.toBe @ a.test.ts:10 @stack [a.test.ts:10 | a.test.ts:8 | a.test.ts:6]
|
||||||
expect | expect.toHaveLength @ a.test.ts:6
|
expect | expect.toHaveLength @ a.test.ts:6
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
|
|
@ -1082,13 +1089,13 @@ pw:api | browserContext.newPage
|
||||||
pw:api |page.setContent @ a.test.ts:4
|
pw:api |page.setContent @ a.test.ts:4
|
||||||
step |expect.poll.toBe @ a.test.ts:13
|
step |expect.poll.toBe @ a.test.ts:13
|
||||||
expect | expect.toHaveText @ a.test.ts:7
|
expect | expect.toHaveText @ a.test.ts:7
|
||||||
test.step | iteration 1 @ a.test.ts:9
|
test.step | iteration 1 @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:6]
|
||||||
expect | expect.toBeVisible @ a.test.ts:10
|
expect | expect.toBeVisible @ a.test.ts:10 @stack [a.test.ts:10 | a.test.ts:9 | a.test.ts:6]
|
||||||
expect | expect.toBe @ a.test.ts:6
|
expect | expect.toBe @ a.test.ts:6
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
expect | expect.toHaveText @ a.test.ts:7
|
expect | expect.toHaveText @ a.test.ts:7
|
||||||
test.step | iteration 2 @ a.test.ts:9
|
test.step | iteration 2 @ a.test.ts:9 @stack [a.test.ts:9 | a.test.ts:6]
|
||||||
expect | expect.toBeVisible @ a.test.ts:10
|
expect | expect.toBeVisible @ a.test.ts:10 @stack [a.test.ts:10 | a.test.ts:9 | a.test.ts:6]
|
||||||
expect | expect.toBe @ a.test.ts:6
|
expect | expect.toBe @ a.test.ts:6
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
|
|
@ -1374,10 +1381,10 @@ pw:api | browser.newContext
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
test.step |my step 1 @ a.test.ts:4
|
test.step |my step 1 @ a.test.ts:4
|
||||||
test.step | my step 2 @ a.test.ts:5
|
test.step | my step 2 @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
pw:api | my group 1 @ a.test.ts:6
|
pw:api | my group 1 @ a.test.ts:6 @stack [a.test.ts:6 | a.test.ts:5 | a.test.ts:4]
|
||||||
pw:api | my group 2 @ a.test.ts:7
|
pw:api | my group 2 @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:5 | a.test.ts:4]
|
||||||
pw:api | page.setContent @ a.test.ts:8
|
pw:api | page.setContent @ a.test.ts:8 @stack [a.test.ts:8 | a.test.ts:5 | a.test.ts:4]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
|
|
@ -1426,8 +1433,8 @@ pw:api | browserContext.newPage
|
||||||
pw:api |page.goto(${server.EMPTY_PAGE}) @ a.test.ts:4
|
pw:api |page.goto(${server.EMPTY_PAGE}) @ a.test.ts:4
|
||||||
pw:api |page.setContent @ a.test.ts:5
|
pw:api |page.setContent @ a.test.ts:5
|
||||||
test.step |custom step @ a.test.ts:6
|
test.step |custom step @ a.test.ts:6
|
||||||
pw:api | page.waitForResponse @ a.test.ts:7
|
pw:api | page.waitForResponse @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:6]
|
||||||
pw:api | page.click(div) @ a.test.ts:14
|
pw:api | page.click(div) @ a.test.ts:14 @stack [a.test.ts:14 | a.test.ts:6]
|
||||||
pw:api | page.content @ a.test.ts:8
|
pw:api | page.content @ a.test.ts:8
|
||||||
pw:api | page.content @ a.test.ts:9
|
pw:api | page.content @ a.test.ts:9
|
||||||
expect | expect.toContainText @ a.test.ts:10
|
expect | expect.toContainText @ a.test.ts:10
|
||||||
|
|
@ -1509,8 +1516,8 @@ pw:api | browser.newContext
|
||||||
fixture | fixture: page
|
fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
test.step |custom step @ a.test.ts:4
|
test.step |custom step @ a.test.ts:4
|
||||||
pw:api | page.route @ a.test.ts:5
|
pw:api | page.route @ a.test.ts:5 @stack [a.test.ts:5 | a.test.ts:4]
|
||||||
pw:api | page.goto(${server.EMPTY_PAGE}) @ a.test.ts:12
|
pw:api | page.goto(${server.EMPTY_PAGE}) @ a.test.ts:12 @stack [a.test.ts:12 | a.test.ts:4]
|
||||||
pw:api | apiResponse.text @ a.test.ts:7
|
pw:api | apiResponse.text @ a.test.ts:7
|
||||||
expect | expect.toBe @ a.test.ts:8
|
expect | expect.toBe @ a.test.ts:8
|
||||||
pw:api | apiResponse.text @ a.test.ts:9
|
pw:api | apiResponse.text @ a.test.ts:9
|
||||||
|
|
@ -1551,9 +1558,9 @@ test('test.step.skip should work', async ({ runInlineTest }) => {
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step.skip|outer step 1 @ a.test.ts:4
|
test.step.skip|outer step 1 @ a.test.ts:4
|
||||||
test.step |outer step 2 @ a.test.ts:11
|
test.step |outer step 2 @ a.test.ts:11
|
||||||
test.step.skip| inner step 2.1 @ a.test.ts:12
|
test.step.skip| inner step 2.1 @ a.test.ts:12 @stack [a.test.ts:12 | a.test.ts:11]
|
||||||
test.step | inner step 2.2 @ a.test.ts:13
|
test.step | inner step 2.2 @ a.test.ts:13 @stack [a.test.ts:13 | a.test.ts:11]
|
||||||
expect | expect.toBe @ a.test.ts:14
|
expect | expect.toBe @ a.test.ts:14 @stack [a.test.ts:14 | a.test.ts:13 | a.test.ts:11]
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
@ -1581,7 +1588,7 @@ test('skip test.step.skip body', async ({ runInlineTest }) => {
|
||||||
expect(stripAnsi(result.output)).toBe(`
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
hook |Before Hooks
|
hook |Before Hooks
|
||||||
test.step |outer step 2 @ a.test.ts:5
|
test.step |outer step 2 @ a.test.ts:5
|
||||||
test.step.skip| inner step 2 @ a.test.ts:6
|
test.step.skip| inner step 2 @ a.test.ts:6 @stack [a.test.ts:6 | a.test.ts:5]
|
||||||
expect |expect.toBe @ a.test.ts:10
|
expect |expect.toBe @ a.test.ts:10
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
`);
|
`);
|
||||||
|
|
@ -1627,7 +1634,7 @@ fixture | fixture: page
|
||||||
pw:api | browserContext.newPage
|
pw:api | browserContext.newPage
|
||||||
pw:api |page.setContent @ a.test.ts:16
|
pw:api |page.setContent @ a.test.ts:16
|
||||||
expect |expect.toBeInvisible @ a.test.ts:17
|
expect |expect.toBeInvisible @ a.test.ts:17
|
||||||
step | expect.poll.toBe @ a.test.ts:7
|
step | expect.poll.toBe @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:17]
|
||||||
pw:api | locator.isVisible(div) @ a.test.ts:7
|
pw:api | locator.isVisible(div) @ a.test.ts:7
|
||||||
expect | expect.toBe @ a.test.ts:7
|
expect | expect.toBe @ a.test.ts:7
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
|
|
@ -1641,7 +1648,7 @@ pw:api | locator.isVisible(div) @ a.test.ts:7
|
||||||
expect | expect.toBe @ a.test.ts:7
|
expect | expect.toBe @ a.test.ts:7
|
||||||
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
expect | ↪ error: Error: expect(received).toBe(expected) // Object.is equality
|
||||||
pw:api | locator.isVisible(div) @ a.test.ts:7
|
pw:api | locator.isVisible(div) @ a.test.ts:7
|
||||||
expect | expect.toBe @ a.test.ts:7
|
expect | expect.toBe @ a.test.ts:7 @stack [a.test.ts:7 | a.test.ts:20]
|
||||||
pw:api |page.waitForTimeout @ a.test.ts:18
|
pw:api |page.waitForTimeout @ a.test.ts:18
|
||||||
pw:api |page.setContent @ a.test.ts:19
|
pw:api |page.setContent @ a.test.ts:19
|
||||||
hook |After Hooks
|
hook |After Hooks
|
||||||
|
|
@ -1649,3 +1656,32 @@ fixture | fixture: page
|
||||||
fixture | fixture: context
|
fixture | fixture: context
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should provide stack for steps', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `module.exports = { reporter: './reporter' };`,
|
||||||
|
'reporter.ts': stepIndentReporter,
|
||||||
|
'helper.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
|
export async function helperStep() {
|
||||||
|
await test.step('step', () => {});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { helperStep } from './helper';
|
||||||
|
|
||||||
|
test('pass', async () => {
|
||||||
|
await helperStep();
|
||||||
|
});
|
||||||
|
`
|
||||||
|
}, { reporter: '', workers: 1 });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(stripAnsi(result.output)).toBe(`
|
||||||
|
hook |Before Hooks
|
||||||
|
test.step |step @ helper.ts:5 @stack [helper.ts:5 | a.test.ts:6]
|
||||||
|
hook |After Hooks
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue