chore: always use TestInfoErrorImpl (impl) in @playwright/test (#33255)

This commit is contained in:
Pavel Feldman 2024-10-23 17:36:05 -07:00 committed by GitHub
parent 9a0a6cec10
commit 6ae6b4865c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 54 additions and 67 deletions

View File

@ -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][];

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -3,3 +3,4 @@
../transform/
../util.ts
../utilBundle.ts
../matchers/**

View File

@ -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')}`;

View File

@ -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({

View File

@ -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,
};
}

View File

@ -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 = {