Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/playwright/src/common/testType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export class TestTypeImpl {
const testInfo = currentTestInfo();
if (!testInfo)
throw new Error(`test.step() can only be called from a test`);
await testInfo._onUserStepBegin?.(title);
await testInfo._callbacks.onUserStepBegin?.(title);
const step = testInfo._addStep({ category: 'test.step', title, location: options.location, box: options.box });
return await currentZone().with('stepZone', step).run(async () => {
try {
Expand All @@ -302,7 +302,7 @@ export class TestTypeImpl {
step.complete({ error });
throw error;
} finally {
await testInfo._onUserStepEnd?.();
await testInfo._callbacks.onUserStepEnd?.();
}
});
}
Expand Down
250 changes: 112 additions & 138 deletions packages/playwright/src/index.ts

Large diffs are not rendered by default.

50 changes: 32 additions & 18 deletions packages/playwright/src/worker/testInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { testInfoError } from './util';
import { ipc, transform } from '../common';

import type { RunnableDescription } from './timeoutManager';
import type { FullProject, TestInfo, TestInfoError, TestStatus, TestStepInfo, TestAnnotation } from '../../types/test';
import type { FullProject, TestInfo, TestInfoError, TestStatus, TestStepInfo, TestAnnotation, _TestInfoEx } from '../../types/test';
import type { FullConfig, Location } from '../../types/testReporter';
import type { config as commonConfig, FullConfigInternal, test as testNs } from '../common';
import type { StackFrame } from '@utils/stackTrace';
Expand Down Expand Up @@ -82,15 +82,14 @@ export const emtpyTestInfoCallbacks: TestInfoCallbacks = {
onTestPaused: () => Promise.reject(new Error('TestInfoImpl not initialized')),
};

export class TestInfoImpl implements TestInfo {
private _callbacks: TestInfoCallbacks;
export class TestInfoImpl implements _TestInfoEx {
private _ipcCallbacks: TestInfoCallbacks;
private _snapshotNames: SnapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
private _ariaSnapshotNames: SnapshotNames = { lastAnonymousSnapshotIndex: 0, lastNamedSnapshotIndex: {} };
readonly _timeoutManager: TimeoutManager;
readonly _startTime: number;
readonly _startWallTime: number;
readonly _tracing: TestTracing;
readonly _uniqueSymbol;

private _interruptedPromise = new ManualPromise<void>();
_lastStepId = 0;
Expand All @@ -99,10 +98,7 @@ export class TestInfoImpl implements TestInfo {
readonly _configInternal: FullConfigInternal;
private readonly _steps: TestStepInternal[] = [];
private readonly _stepMap = new Map<string, TestStepInternal>();
_onDidFinishTestFunctionCallbacks = new Set<() => Promise<void>>();
_onCustomMessageCallback?: (data: any) => Promise<any>;
_onUserStepBegin?: (title: string) => Promise<void>;
_onUserStepEnd?: () => Promise<void>;
readonly _callbacks: _TestInfoEx['_callbacks'] = {};
_hasNonRetriableError = false;
_hasUnhandledError = false;
_allowSkips = false;
Expand Down Expand Up @@ -172,11 +168,10 @@ export class TestInfoImpl implements TestInfo {
callbacks: TestInfoCallbacks
) {
this.testId = test?.id ?? '';
this._callbacks = callbacks;
this._ipcCallbacks = callbacks;
this._startTime = monotonicTime();
this._startWallTime = Date.now();
this._requireFile = test?._requireFile ?? '';
this._uniqueSymbol = Symbol('testInfoUniqueSymbol');
this._workerParams = workerParams;

this.repeatEachIndex = workerParams.repeatEachIndex;
Expand Down Expand Up @@ -357,7 +352,7 @@ export class TestInfoImpl implements TestInfo {
suggestedRebaseline: result.suggestedRebaseline,
annotations: step.info.annotations,
};
this._callbacks.onStepEnd(payload);
this._ipcCallbacks.onStepEnd(payload);
}
if (step.group !== 'internal') {
const errorForTrace = step.error ? { name: '', message: step.error.message || '', stack: step.error.stack } : undefined;
Expand All @@ -380,7 +375,7 @@ export class TestInfoImpl implements TestInfo {
wallTime: Date.now(),
location: step.location,
};
this._callbacks.onStepBegin(payload);
this._ipcCallbacks.onStepBegin(payload);
}
if (step.group !== 'internal') {
this._tracing.appendBeforeActionForStep({
Expand Down Expand Up @@ -478,7 +473,7 @@ export class TestInfoImpl implements TestInfo {

_currentHookType() {
const type = this._timeoutManager.currentSlotType();
return ['beforeAll', 'afterAll', 'beforeEach', 'afterEach'].includes(type) ? type : undefined;
return (['beforeAll', 'afterAll', 'beforeEach', 'afterEach'] as const).find(t => t === type);
}

_setIgnoreTimeouts(ignoreTimeouts: boolean) {
Expand All @@ -490,12 +485,11 @@ export class TestInfoImpl implements TestInfo {
const shouldPause = (this._workerParams.pauseAtEnd && !this._isFailure()) || (this._workerParams.pauseOnError && this._isFailure());
if (shouldPause) {
await Promise.race([
this._callbacks.onTestPaused({ testId: this.testId, errors: this._isFailure() ? this.errors.map(ipc.toTestInfoErrorPayload) : [], status: this.status }),
this._ipcCallbacks.onTestPaused({ testId: this.testId, errors: this._isFailure() ? this.errors.map(ipc.toTestInfoErrorPayload) : [], status: this.status }),
this._interruptedPromise,
]);
}
for (const cb of this._onDidFinishTestFunctionCallbacks)
await cb();
await this._callbacks.onDidFinishTestFunction?.();
}

// ------------ TestInfo methods ------------
Expand Down Expand Up @@ -527,7 +521,7 @@ export class TestInfoImpl implements TestInfo {
this._tracing.appendAfterActionForStep(stepId, undefined, [attachment]);
}

this._callbacks.onAttach({
this._ipcCallbacks.onAttach({
testId: this.testId,
name: attachment.name,
contentType: attachment.contentType,
Expand Down Expand Up @@ -664,9 +658,29 @@ export class TestInfoImpl implements TestInfo {
this._timeoutManager.setTimeout(timeout);
}

artifactsDir(): string {
_artifactsDir(): string {
return this._workerParams.artifactsDir;
}

_tracesDir(): string {
return this._tracing.tracesDir();
}

_traceOptions() {
return this._tracing.traceOptions();
}

_traceTitle(): string {
return this._tracing.traceTitle();
}

_shouldKeepTrace(): boolean {
return this._tracing.shouldKeepTrace();
}

_appendTraceFile(file: string) {
this._tracing.appendTraceFile(file);
}
}

export class TestStepInfoImpl implements TestStepInfo {
Expand Down
24 changes: 7 additions & 17 deletions packages/playwright/src/worker/testTracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ import type EventEmitter from 'events';
export type Attachment = TestInfo['attachments'][0];
export const testTraceEntryName = 'test.trace';
const version: trace.VERSION = 9;
let traceOrdinal = 0;

type TraceFixtureValue = PlaywrightWorkerOptions['trace'] | undefined;
type TraceOptions = { screencast?: boolean | { size?: { width: number, height: number }, quality?: number }, screenshots: boolean, snapshots: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, sources: boolean, attachments: boolean, live: boolean, mode: TraceMode };
type TraceOptions = Exclude<TraceFixtureValue, string | undefined> & { live: boolean };

export class TestTracing {
private _testInfo: TestInfoImpl;
Expand Down Expand Up @@ -134,14 +133,6 @@ export class TestTracing {
return [path.relative(this._testInfo.project.testDir, this._testInfo.file) + ':' + this._testInfo.line, ...this._testInfo.titlePath.slice(1)].join(' › ');
}

generateNextTraceRecordingName() {
const ordinalSuffix = traceOrdinal ? `-recording${traceOrdinal}` : '';
++traceOrdinal;
const retrySuffix = this._testInfo.retry ? `-retry${this._testInfo.retry}` : '';
// Note that trace name must start with testId for live tracing to work.
return `${this._testInfo.testId}${retrySuffix}${ordinalSuffix}`;
}

private _generateNextTraceRecordingPath() {
const file = path.join(this._artifactsDir, createGuid() + '.zip');
this._temporaryTraceFiles.push(file);
Expand All @@ -152,13 +143,15 @@ export class TestTracing {
return this._options;
}

maybeGenerateNextTraceRecordingPath() {
shouldKeepTrace() {
// Forget about traces that should be saved on failure, when no failure happened
// during the test and beforeEach/afterEach hooks.
// This avoids downloading traces over the wire when not really needed.
if (this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace())
return;
return this._generateNextTraceRecordingPath();
return !(this._didFinishTestFunctionAndAfterEachHooks && this._shouldAbandonTrace());
}

appendTraceFile(file: string) {
this._temporaryTraceFiles.push(file);
}

private _shouldAbandonTrace() {
Expand All @@ -174,9 +167,6 @@ export class TestTracing {
this._contextCreatedEvent.testTimeout = this._testInfo.timeout;
this._contextCreatedEvent.annotations = this._testInfo.annotations.map(({ type, description }) => ({ type, description }));

if (!this._options)
return;

await this._liveTraceFile?.fs.sync();

if (this._shouldAbandonTrace()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/worker/workerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export class WorkerMain extends ProcessRunner {
try {
if (this._currentTest?.testId !== payload.testId)
throw new Error('Test has already stopped');
const response = await this._currentTest._onCustomMessageCallback?.(payload.request);
const response = await this._currentTest._callbacks.onCustomMessage?.(payload.request);
return { response };
} catch (error) {
return { response: {}, error: ipc.toTestInfoErrorPayload(testInfoError(error)) };
Expand Down
17 changes: 17 additions & 0 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8963,6 +8963,23 @@ export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect<{}>;

export interface _TestInfoEx extends TestInfo {
readonly _callbacks: {
onDidFinishTestFunction?: () => Promise<void>;
onCustomMessage?: (data: any) => Promise<any>;
onUserStepBegin?: (title: string) => Promise<void>;
onUserStepEnd?: () => Promise<void>;
};
_setIgnoreTimeouts(ignoreTimeouts: boolean): void;
_currentHookType(): 'beforeAll' | 'afterAll' | 'beforeEach' | 'afterEach' | undefined;
_artifactsDir(): string;
_tracesDir(): string;
_traceOptions(): (Exclude<PlaywrightWorkerOptions['trace'], string> & { live: boolean }) | undefined;
_traceTitle(): string;
_shouldKeepTrace(): boolean;
_appendTraceFile(file: string): void;
}

/**
* Defines Playwright config
*/
Expand Down
1 change: 1 addition & 0 deletions utils/generate_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ class TypesGenerator {
'PlaywrightWorkerOptions.defaultBrowserType',
'PlaywrightWorkerOptions.reuseContext',
'Project',
'_TestInfoEx',
]),
doNotExportClassNames: assertionClasses,
});
Expand Down
17 changes: 17 additions & 0 deletions utils/generate_types/overrides-test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,23 @@ export default test;
export const _baseTest: TestType<{}, {}>;
export const expect: Expect<{}>;

export interface _TestInfoEx extends TestInfo {
readonly _callbacks: {
onDidFinishTestFunction?: () => Promise<void>;
onCustomMessage?: (data: any) => Promise<any>;
onUserStepBegin?: (title: string) => Promise<void>;
onUserStepEnd?: () => Promise<void>;
};
_setIgnoreTimeouts(ignoreTimeouts: boolean): void;
_currentHookType(): 'beforeAll' | 'afterAll' | 'beforeEach' | 'afterEach' | undefined;
_artifactsDir(): string;
_tracesDir(): string;
_traceOptions(): (Exclude<PlaywrightWorkerOptions['trace'], string> & { live: boolean }) | undefined;
_traceTitle(): string;
_shouldKeepTrace(): boolean;
_appendTraceFile(file: string): void;
}

/**
* Defines Playwright config
*/
Expand Down
Loading