flatten fields

This commit is contained in:
Yury Semikhatsky 2024-10-16 16:42:59 -07:00
parent f4ce289715
commit 87fb05ddb2
9 changed files with 125 additions and 89 deletions

View file

@ -4,29 +4,42 @@
Information about an error thrown during test execution. Information about an error thrown during test execution.
## property: TestInfoError.actual
* since: v1.49
- type: ?<[any]>
Actual value.
## property: TestInfoError.expected
* since: v1.49
- type: ?<[any]>
Expected value.
## property: TestInfoError.log
* since: v1.49
- type: ?<[Array]<[string]>>
Call log.
## property: TestInfoError.message ## property: TestInfoError.message
* since: v1.10 * since: v1.10
- type: ?<[string]> - type: ?<[string]>
Error message. Set when [Error] (or its subclass) has been thrown. Error message. Set when [Error] (or its subclass) has been thrown.
## property: TestInfoError.shortMessage
* since: v1.49
- type: ?<[string]>
Failure message.
## property: TestInfoError.stack ## property: TestInfoError.stack
* since: v1.10 * since: v1.10
- type: ?<[string]> - type: ?<[string]>
Error stack. Set when [Error] (or its subclass) has been thrown. Error stack. Set when [Error] (or its subclass) has been thrown.
## property: TestInfoError.matcherResult
* since: v1.49
- type: ?<[Object]>
- `name` <[string]>
- `message` ?<[string]> Failure message
- `log` ?<[Array]<[string]>> Call log
- `expected` ?<[any]>
- `actual` ?<[any]>
Expect matcher result.
## property: TestInfoError.value ## property: TestInfoError.value
* since: v1.10 * since: v1.10
- type: ?<[string]> - type: ?<[string]>

View file

@ -4,12 +4,41 @@
Information about an error thrown during test execution. Information about an error thrown during test execution.
## property: TestError.actual
* since: v1.49
- type: ?<[any]>
Actual value.
## property: TestError.expected
* since: v1.49
- type: ?<[any]>
Expected value.
## property: TestError.log
* since: v1.49
- type: ?<[Array]<[string]>>
Call log.
## property: TestError.message ## property: TestError.message
* since: v1.10 * since: v1.10
- type: ?<[string]> - type: ?<[string]>
Error message. Set when [Error] (or its subclass) has been thrown. Error message. Set when [Error] (or its subclass) has been thrown.
## property: TestError.message
* since: v1.10
- type: ?<[string]>
Error message. Set when [Error] (or its subclass) has been thrown.
## property: TestError.shortMessage
* since: v1.49
- type: ?<[string]>
Failure message.
## property: TestError.stack ## property: TestError.stack
* since: v1.10 * since: v1.10
- type: ?<[string]> - type: ?<[string]>
@ -28,17 +57,6 @@ The value that was thrown. Set when anything except the [Error] (or its subclass
Error location in the source code. Error location in the source code.
## property: TestError.matcherResult
* since: v1.49
- type: ?<[Object]>
- `name` <[string]>
- `message` ?<[string]> Failure message
- `log` ?<[Array]<[string]>> Call log
- `expected` ?<[any]>
- `actual` ?<[any]>
Expect matcher result.
## property: TestError.snippet ## property: TestError.snippet
* since: v1.33 * since: v1.33
- type: ?<[string]> - type: ?<[string]>

View file

@ -152,21 +152,23 @@ export const TestResultView: React.FC<{
function classifyErrors(testErrors: ErrorDetails[], diffs: ImageDiff[]) { function classifyErrors(testErrors: ErrorDetails[], diffs: ImageDiff[]) {
return testErrors.map(error => { return testErrors.map(error => {
if (error.matcherResult?.name === 'toHaveScreenshot' && error.matcherResult?.actual && error.matcherResult?.expected) { if (error.shortMessage?.includes('Screenshot comparison failed:') && error.actual && error.expected) {
const matchingDiff = diffs.find(diff => { const matchingDiff = diffs.find(diff => {
const attachmentName = diff.actual?.attachment.name; const attachmentName = diff.actual?.attachment.name;
return attachmentName && error.matcherResult?.actual.endsWith(attachmentName); return attachmentName && error.actual.endsWith(attachmentName);
}); });
const errorSuffix = ['Call log:', const errorSuffix = ['Call log:',
...(error.matcherResult.log?.map(line => ' - ' + line) || []), ...(error.log?.map(line => ' - ' + line) || []),
'', '',
error.snippet, error.snippet,
'',
error.callStack,
].join('\n'); ].join('\n');
if (matchingDiff) { if (matchingDiff) {
return { return {
type: 'screenshot', type: 'screenshot',
diff: matchingDiff, diff: matchingDiff,
errorPrefix: error.matcherResult?.message, errorPrefix: error.shortMessage,
errorSuffix errorSuffix
}; };
} }

View file

@ -83,19 +83,15 @@ export type TestCase = Omit<TestCaseSummary, 'results'> & {
results: TestResult[]; results: TestResult[];
}; };
type MatcherResult = {
name: string;
message?: string;
log?: string[];
expected?: any;
actual?: any;
};
export type ErrorDetails = { export type ErrorDetails = {
message: string; message: string;
location?: Location; location?: Location;
matcherResult?: MatcherResult; shortMessage?: string;
log?: string[];
expected?: any;
actual?: any;
snippet?: string; snippet?: string;
callStack?: string;
}; };
export type TestAttachment = { export type TestAttachment = {

View file

@ -29,19 +29,20 @@ type Annotation = {
location?: Location; location?: Location;
}; };
type MatcherResult = {
name: string;
message?: string;
log?: string[];
expected?: any;
actual?: any;
};
type ErrorDetails = { type ErrorDetails = {
message: string; message: string;
location?: Location; location?: Location;
matcherResult?: MatcherResult; callStack?: string;
};
type TestResultErrorDetails = ErrorDetails & {
shortMessage?: string;
log?: string[];
expected?: any;
actual?: any;
snippet?: string; snippet?: string;
stack?: string;
}; };
type TestSummary = { type TestSummary = {
@ -374,8 +375,8 @@ function quotePathIfNeeded(path: string): string {
return path; return path;
} }
export function formatResultFailure(test: TestCase, result: TestResult, initialIndent: string, highlightCode: boolean): ErrorDetails[] { export function formatResultFailure(test: TestCase, result: TestResult, initialIndent: string, highlightCode: boolean): TestResultErrorDetails[] {
const errorDetails: ErrorDetails[] = []; const errorDetails: TestResultErrorDetails[] = [];
if (result.status === 'passed' && test.expectedStatus === 'failed') { if (result.status === 'passed' && test.expectedStatus === 'failed') {
errorDetails.push({ errorDetails.push({
@ -393,8 +394,12 @@ export function formatResultFailure(test: TestCase, result: TestResult, initialI
errorDetails.push({ errorDetails.push({
message: indent(formattedError.message, initialIndent), message: indent(formattedError.message, initialIndent),
location: formattedError.location, location: formattedError.location,
matcherResult: error.matcherResult, shortMessage: error.shortMessage,
log: error.log,
expected: error.expected,
actual: error.actual,
snippet: error.snippet, snippet: error.snippet,
callStack: formattedError.callStack,
}); });
} }
return errorDetails; return errorDetails;
@ -474,9 +479,12 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta
tokens.push(snippet); tokens.push(snippet);
} }
let callStack;
if (parsedStack && parsedStack.stackLines.length) { if (parsedStack && parsedStack.stackLines.length) {
tokens.push(''); tokens.push('');
tokens.push(colors.dim(parsedStack.stackLines.join('\n'))); tokens.push(colors.dim(parsedStack.stackLines.join('\n')));
// TODO: pass raw lines.
callStack = colors.dim(parsedStack.stackLines.join('\n'));
} }
let location = error.location; let location = error.location;
@ -486,6 +494,7 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta
return { return {
location, location,
message: tokens.join('\n'), message: tokens.join('\n'),
callStack,
}; };
} }

View file

@ -222,7 +222,8 @@ class JSONReporter implements ReporterV2 {
} }
private _serializeError(error: TestError): JSONReportError { private _serializeError(error: TestError): JSONReportError {
return formatError(error, true); const { message, location } = formatError(error, true);
return { message, location };
} }
private _serializeTestStep(step: TestStep): JSONReportTestStep { private _serializeTestStep(step: TestStep): JSONReportTestStep {

View file

@ -30,26 +30,25 @@ import type { MatcherResult } from './matchers/matcherHint';
const PLAYWRIGHT_TEST_PATH = path.join(__dirname, '..'); const PLAYWRIGHT_TEST_PATH = path.join(__dirname, '..');
const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/package.json')); const PLAYWRIGHT_CORE_PATH = path.dirname(require.resolve('playwright-core/package.json'));
export function filterStackTrace(e: Error): { message: string, stack: string, matcherResult: TestInfoError['matcherResult'] } { export function filterStackTrace(e: Error): Omit<TestInfoError, 'value'> {
const name = e.name ? e.name + ': ' : ''; const name = e.name ? e.name + ': ' : '';
if (process.env.PWDEBUGIMPL) if (process.env.PWDEBUGIMPL)
return { message: name + e.message, stack: e.stack || '', matcherResult: filterMatcherResult(e) }; return { message: name + e.message, stack: e.stack || '', ...filterExpectDetails(e) };
const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || [])); const stackLines = stringifyStackFrames(filteredStackTrace(e.stack?.split('\n') || []));
return { return {
matcherResult: filterMatcherResult(e), ...filterExpectDetails(e),
message: name + e.message, message: name + e.message,
stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}` stack: `${name}${e.message}${stackLines.map(line => '\n' + line).join('')}`
}; };
} }
function filterMatcherResult(e: Error): TestInfoError['matcherResult'] | undefined { function filterExpectDetails(e: Error): Pick<TestInfoError, 'shortMessage'|'log'|'expected'|'actual'> {
const matcherResult = (e as any).matcherResult as MatcherResult<unknown, unknown>; const matcherResult = (e as any).matcherResult as MatcherResult<unknown, unknown>;
if (!matcherResult) if (!matcherResult)
return undefined; return {};
return { return {
name: matcherResult.name, shortMessage: matcherResult.shortMessage,
message: matcherResult.shortMessage,
log: matcherResult.log, log: matcherResult.log,
expected: matcherResult.expected, expected: matcherResult.expected,
actual: matcherResult.actual, actual: matcherResult.actual,

View file

@ -8380,31 +8380,30 @@ export interface TestInfo {
*/ */
export interface TestInfoError { export interface TestInfoError {
/** /**
* Expect matcher result. * Actual value.
*/ */
matcherResult?: { actual?: any;
name: string;
/** /**
* Failure message * Expected value.
*/ */
message?: string; expected?: any;
/** /**
* Call log * Call log.
*/ */
log?: Array<string>; log?: Array<string>;
expected?: any;
actual?: any;
};
/** /**
* Error message. Set when [Error] (or its subclass) has been thrown. * Error message. Set when [Error] (or its subclass) has been thrown.
*/ */
message?: string; message?: string;
/**
* Failure message.
*/
shortMessage?: string;
/** /**
* Error stack. Set when [Error] (or its subclass) has been thrown. * Error stack. Set when [Error] (or its subclass) has been thrown.
*/ */

View file

@ -554,37 +554,36 @@ export interface TestCase {
* Information about an error thrown during test execution. * Information about an error thrown during test execution.
*/ */
export interface TestError { export interface TestError {
/**
* Actual value.
*/
actual?: any;
/**
* Expected value.
*/
expected?: any;
/** /**
* Error location in the source code. * Error location in the source code.
*/ */
location?: Location; location?: Location;
/** /**
* Expect matcher result. * Call log.
*/ */
matcherResult?: { log?: Array<string>;
name: string;
/**
* Failure message
*/
message?: string;
/**
* Call log
*/
log?: Array<string>;
expected?: any;
actual?: any;
};
/** /**
* Error message. Set when [Error] (or its subclass) has been thrown. * Error message. Set when [Error] (or its subclass) has been thrown.
*/ */
message?: string; message?: string;
/**
* Failure message.
*/
shortMessage?: string;
/** /**
* Source code snippet with highlighted error. * Source code snippet with highlighted error.
*/ */