chore: always use TestInfoErrorImpl (impl) in @playwright/test (#33255)
This commit is contained in:
parent
9a0a6cec10
commit
6ae6b4865c
|
@ -15,9 +15,11 @@
|
|||
*/
|
||||
|
||||
import util from 'util';
|
||||
import { type SerializedCompilationCache, serializeCompilationCache } from '../transform/compilationCache';
|
||||
import { serializeCompilationCache } from '../transform/compilationCache';
|
||||
import type { SerializedCompilationCache } from '../transform/compilationCache';
|
||||
import type { ConfigLocation, FullConfigInternal } from './config';
|
||||
import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test';
|
||||
import type { MatcherResultProperty } from '../matchers/matcherHint';
|
||||
|
||||
export type ConfigCLIOverrides = {
|
||||
debug?: boolean;
|
||||
|
@ -74,11 +76,15 @@ export type AttachmentPayload = {
|
|||
contentType: string;
|
||||
};
|
||||
|
||||
export type TestInfoErrorImpl = TestInfoError & {
|
||||
matcherResult?: MatcherResultProperty;
|
||||
};
|
||||
|
||||
export type TestEndPayload = {
|
||||
testId: string;
|
||||
duration: number;
|
||||
status: TestStatus;
|
||||
errors: TestInfoError[];
|
||||
errors: TestInfoErrorImpl[];
|
||||
hasNonRetriableError: boolean;
|
||||
expectedStatus: TestStatus;
|
||||
annotations: { type: string, description?: string }[];
|
||||
|
@ -99,7 +105,7 @@ export type StepEndPayload = {
|
|||
testId: string;
|
||||
stepId: string;
|
||||
wallTime: number; // milliseconds since unix epoch
|
||||
error?: TestInfoError;
|
||||
error?: TestInfoErrorImpl;
|
||||
};
|
||||
|
||||
export type TestEntry = {
|
||||
|
@ -113,7 +119,7 @@ export type RunPayload = {
|
|||
};
|
||||
|
||||
export type DonePayload = {
|
||||
fatalErrors: TestInfoError[];
|
||||
fatalErrors: TestInfoErrorImpl[];
|
||||
skipTestsDueToSetupFailure: string[]; // test ids
|
||||
fatalUnknownTestIds?: string[];
|
||||
};
|
||||
|
@ -124,7 +130,7 @@ export type TestOutputPayload = {
|
|||
};
|
||||
|
||||
export type TeardownErrorsPayload = {
|
||||
fatalErrors: TestInfoError[];
|
||||
fatalErrors: TestInfoErrorImpl[];
|
||||
};
|
||||
|
||||
export type EnvProducedPayload = [string, string | null][];
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { EnvProducedPayload, ProcessInitParams } from './ipc';
|
||||
import type { EnvProducedPayload, ProcessInitParams, TestInfoErrorImpl } from './ipc';
|
||||
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import { serializeError } from '../util';
|
||||
import { registerESMLoader } from './esmLoaderHost';
|
||||
import { execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils';
|
||||
|
@ -29,7 +28,7 @@ export type ProtocolRequest = {
|
|||
|
||||
export type ProtocolResponse = {
|
||||
id?: number;
|
||||
error?: TestInfoError;
|
||||
error?: TestInfoErrorImpl;
|
||||
method?: string;
|
||||
params?: any;
|
||||
result?: any;
|
||||
|
|
|
@ -45,18 +45,18 @@ export type MatcherResult<E, A> = {
|
|||
printedDiff?: string;
|
||||
};
|
||||
|
||||
export class ExpectError extends Error {
|
||||
matcherResult: {
|
||||
message: string;
|
||||
pass: boolean;
|
||||
name?: string;
|
||||
expected?: any;
|
||||
actual?: any;
|
||||
log?: string[];
|
||||
timeout?: number;
|
||||
};
|
||||
export type MatcherResultProperty = Omit<MatcherResult<unknown, unknown>, 'message'> & {
|
||||
message: string;
|
||||
};
|
||||
|
||||
constructor(jestError: ExpectError, customMessage: string, stackFrames: StackFrame[]) {
|
||||
type JestError = Error & {
|
||||
matcherResult: MatcherResultProperty;
|
||||
};
|
||||
|
||||
export class ExpectError extends Error {
|
||||
matcherResult: MatcherResultProperty;
|
||||
|
||||
constructor(jestError: JestError, customMessage: string, stackFrames: StackFrame[]) {
|
||||
super('');
|
||||
// Copy to erase the JestMatcherError constructor name from the console.log(error).
|
||||
this.name = jestError.name;
|
||||
|
|
|
@ -21,10 +21,10 @@ import path from 'path';
|
|||
import url from 'url';
|
||||
import { debug, mime, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||
import { formatCallLog } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError } from './../types/test';
|
||||
import type { Location } from './../types/testReporter';
|
||||
import { calculateSha1, isRegExp, isString, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils';
|
||||
import type { RawStack } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoErrorImpl } from './common/ipc';
|
||||
|
||||
const PLAYWRIGHT_TEST_PATH = path.join(__dirname, '..');
|
||||
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/package.json'));
|
||||
|
@ -62,7 +62,7 @@ export function filteredStackTrace(rawStack: RawStack): StackFrame[] {
|
|||
return frames;
|
||||
}
|
||||
|
||||
export function serializeError(error: Error | any): TestInfoError {
|
||||
export function serializeError(error: Error | any): TestInfoErrorImpl {
|
||||
if (error instanceof Error)
|
||||
return filterStackTrace(error);
|
||||
return {
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
../transform/
|
||||
../util.ts
|
||||
../utilBundle.ts
|
||||
../matchers/**
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { captureRawStack, monotonicTime, zones, sanitizeForFilePath, stringifyStackFrames } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError, TestInfo, TestStatus, FullProject } from '../../types/test';
|
||||
import type { AttachmentPayload, StepBeginPayload, StepEndPayload, WorkerInitParams } from '../common/ipc';
|
||||
import type { TestInfo, TestStatus, FullProject } from '../../types/test';
|
||||
import type { AttachmentPayload, StepBeginPayload, StepEndPayload, TestInfoErrorImpl, WorkerInitParams } from '../common/ipc';
|
||||
import type { TestCase } from '../common/test';
|
||||
import { TimeoutManager, TimeoutManagerError, kMaxDeadline } from './timeoutManager';
|
||||
import type { RunnableDescription } from './timeoutManager';
|
||||
|
@ -28,7 +28,7 @@ import { debugTest, filteredStackTrace, formatLocation, getContainedPath, normal
|
|||
import { TestTracing } from './testTracing';
|
||||
import type { Attachment } from './testTracing';
|
||||
import type { StackFrame } from '@protocol/channels';
|
||||
import { serializeWorkerError } from './util';
|
||||
import { testInfoError } from './util';
|
||||
|
||||
export interface TestStepInternal {
|
||||
complete(result: { error?: Error | unknown, attachments?: Attachment[] }): void;
|
||||
|
@ -41,7 +41,7 @@ export interface TestStepInternal {
|
|||
endWallTime?: number;
|
||||
apiName?: string;
|
||||
params?: Record<string, any>;
|
||||
error?: TestInfoError;
|
||||
error?: TestInfoErrorImpl;
|
||||
infectParentStepsWithError?: boolean;
|
||||
box?: boolean;
|
||||
isStage?: boolean;
|
||||
|
@ -97,14 +97,14 @@ export class TestInfoImpl implements TestInfo {
|
|||
snapshotSuffix: string = '';
|
||||
readonly outputDir: string;
|
||||
readonly snapshotDir: string;
|
||||
errors: TestInfoError[] = [];
|
||||
errors: TestInfoErrorImpl[] = [];
|
||||
readonly _attachmentsPush: (...items: TestInfo['attachments']) => number;
|
||||
|
||||
get error(): TestInfoError | undefined {
|
||||
get error(): TestInfoErrorImpl | undefined {
|
||||
return this.errors[0];
|
||||
}
|
||||
|
||||
set error(e: TestInfoError | undefined) {
|
||||
set error(e: TestInfoErrorImpl | undefined) {
|
||||
if (e === undefined)
|
||||
throw new Error('Cannot assign testInfo.error undefined value!');
|
||||
this.errors[0] = e;
|
||||
|
@ -273,7 +273,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
if (result.error) {
|
||||
if (typeof result.error === 'object' && !(result.error as any)?.[stepSymbol])
|
||||
(result.error as any)[stepSymbol] = step;
|
||||
const error = serializeWorkerError(result.error);
|
||||
const error = testInfoError(result.error);
|
||||
if (data.boxedStack)
|
||||
error.stack = `${error.message}\n${stringifyStackFrames(data.boxedStack).join('\n')}`;
|
||||
step.error = error;
|
||||
|
@ -331,7 +331,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
_failWithError(error: Error | unknown) {
|
||||
if (this.status === 'passed' || this.status === 'skipped')
|
||||
this.status = error instanceof TimeoutManagerError ? 'timedOut' : 'failed';
|
||||
const serialized = serializeWorkerError(error);
|
||||
const serialized = testInfoError(error);
|
||||
const step: TestStepInternal | undefined = typeof error === 'object' ? (error as any)?.[stepSymbol] : undefined;
|
||||
if (step && step.boxedStack)
|
||||
serialized.stack = `${(error as Error).name}: ${(error as Error).message}\n${stringifyStackFrames(step.boxedStack).join('\n')}`;
|
||||
|
|
|
@ -21,10 +21,10 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import { ManualPromise, calculateSha1, monotonicTime, createGuid, SerializedFS } from 'playwright-core/lib/utils';
|
||||
import { yauzl, yazl } from 'playwright-core/lib/zipBundle';
|
||||
import type { TestInfo, TestInfoError } from '../../types/test';
|
||||
import { filteredStackTrace } from '../util';
|
||||
import type { TraceMode, PlaywrightWorkerOptions } from '../../types/test';
|
||||
import type { TestInfo, TraceMode, PlaywrightWorkerOptions } from '../../types/test';
|
||||
import type { TestInfoImpl } from './testInfo';
|
||||
import type { TestInfoErrorImpl } from '../common/ipc';
|
||||
|
||||
export type Attachment = TestInfo['attachments'][0];
|
||||
export const testTraceEntryName = 'test.trace';
|
||||
|
@ -219,7 +219,7 @@ export class TestTracing {
|
|||
this._testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
||||
}
|
||||
|
||||
appendForError(error: TestInfoError) {
|
||||
appendForError(error: TestInfoErrorImpl) {
|
||||
const rawStack = error.stack?.split('\n') || [];
|
||||
const stack = rawStack ? filteredStackTrace(rawStack) : [];
|
||||
this._appendTraceEvent({
|
||||
|
|
|
@ -14,32 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TestError } from '../../types/testReporter';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import type { MatcherResult } from '../matchers/matcherHint';
|
||||
import type { TestInfoErrorImpl } from '../common/ipc';
|
||||
import { ExpectError } from '../matchers/matcherHint';
|
||||
import { serializeError } from '../util';
|
||||
|
||||
|
||||
type MatcherResultDetails = Pick<TestError, 'timeout'|'matcherName'|'locator'|'expected'|'received'|'log'>;
|
||||
|
||||
export function serializeWorkerError(error: Error | any): TestInfoError & MatcherResultDetails {
|
||||
return {
|
||||
...serializeError(error),
|
||||
...serializeExpectDetails(error),
|
||||
};
|
||||
export function testInfoError(error: Error | any): TestInfoErrorImpl {
|
||||
const result = serializeError(error);
|
||||
if (error instanceof ExpectError)
|
||||
result.matcherResult = error.matcherResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
function serializeExpectDetails(e: Error): MatcherResultDetails {
|
||||
const matcherResult = (e as any).matcherResult as MatcherResult<unknown, unknown>;
|
||||
if (!matcherResult)
|
||||
return {};
|
||||
return {
|
||||
timeout: matcherResult.timeout,
|
||||
matcherName: matcherResult.name,
|
||||
locator: matcherResult.locator,
|
||||
expected: matcherResult.printedExpected,
|
||||
received: matcherResult.printedReceived,
|
||||
log: matcherResult.log,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { debugTest, relativeFilePath } from '../util';
|
||||
import { type TestBeginPayload, type TestEndPayload, type RunPayload, type DonePayload, type WorkerInitParams, type TeardownErrorsPayload, stdioChunkToParams } from '../common/ipc';
|
||||
import type { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, TeardownErrorsPayload, TestInfoErrorImpl } from '../common/ipc';
|
||||
import { stdioChunkToParams } from '../common/ipc';
|
||||
import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals';
|
||||
import { deserializeConfig } from '../common/configLoader';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
|
@ -28,11 +29,10 @@ import { ProcessRunner } from '../common/process';
|
|||
import { loadTestFile } from '../common/testLoader';
|
||||
import { applyRepeatEachIndex, bindFileSuiteToProject, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
|
||||
import { PoolBuilder } from '../common/poolBuilder';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import { inheritFixtureNames } from '../common/fixtures';
|
||||
import { type TimeSlot } from './timeoutManager';
|
||||
import { serializeWorkerError } from './util';
|
||||
import { testInfoError } from './util';
|
||||
|
||||
export class WorkerMain extends ProcessRunner {
|
||||
private _params: WorkerInitParams;
|
||||
|
@ -42,7 +42,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
private _fixtureRunner: FixtureRunner;
|
||||
|
||||
// Accumulated fatal errors that cannot be attributed to a test.
|
||||
private _fatalErrors: TestInfoError[] = [];
|
||||
private _fatalErrors: TestInfoErrorImpl[] = [];
|
||||
// Whether we should skip running remaining tests in this suite because
|
||||
// of a setup error, usually beforeAll hook.
|
||||
private _skipRemainingTestsInSuite: Suite | undefined;
|
||||
|
@ -113,7 +113,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
await fakeTestInfo._runAsStage({ title: 'worker cleanup', runnable }, () => gracefullyCloseAll()).catch(() => {});
|
||||
this._fatalErrors.push(...fakeTestInfo.errors);
|
||||
} catch (e) {
|
||||
this._fatalErrors.push(serializeWorkerError(e));
|
||||
this._fatalErrors.push(testInfoError(e));
|
||||
}
|
||||
|
||||
if (this._fatalErrors.length) {
|
||||
|
@ -123,7 +123,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
}
|
||||
}
|
||||
|
||||
private _appendProcessTeardownDiagnostics(error: TestInfoError) {
|
||||
private _appendProcessTeardownDiagnostics(error: TestInfoErrorImpl) {
|
||||
if (!this._lastRunningTests.length)
|
||||
return;
|
||||
const count = this._totalRunningTests === 1 ? '1 test' : `${this._totalRunningTests} tests`;
|
||||
|
@ -154,7 +154,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
// No current test - fatal error.
|
||||
if (!this._currentTest) {
|
||||
if (!this._fatalErrors.length)
|
||||
this._fatalErrors.push(serializeWorkerError(error));
|
||||
this._fatalErrors.push(testInfoError(error));
|
||||
void this._stop();
|
||||
return;
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
// In theory, we should run above code without any errors.
|
||||
// However, in the case we screwed up, or loadTestFile failed in the worker
|
||||
// but not in the runner, let's do a fatal error.
|
||||
this._fatalErrors.push(serializeWorkerError(e));
|
||||
this._fatalErrors.push(testInfoError(e));
|
||||
void this._stop();
|
||||
} finally {
|
||||
const donePayload: DonePayload = {
|
||||
|
|
Loading…
Reference in New Issue