Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s committed Oct 18, 2024
1 parent 32b4821 commit 8a5212c
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 70 deletions.
10 changes: 8 additions & 2 deletions docs/src/test-api/class-testinfoerror.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ Information about an error thrown during test execution.

## property: TestInfoError.actual
* since: v1.49
- type: ?<[any]>
- type: ?<[string]>

Actual value.

## property: TestInfoError.expected
* since: v1.49
- type: ?<[any]>
- type: ?<[string]>

Expected value.

## property: TestInfoError.locator
* since: v1.49
- type: ?<[string]>

Receiver's locator.

## property: TestInfoError.log
* since: v1.49
- type: ?<[Array]<[string]>>
Expand Down
10 changes: 8 additions & 2 deletions docs/src/test-reporter-api/class-testerror.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ Information about an error thrown during test execution.

## property: TestError.actual
* since: v1.49
- type: ?<[any]>
- type: ?<[string]>

Actual value.

## property: TestError.expected
* since: v1.49
- type: ?<[any]>
- type: ?<[string]>

Expected value.

## property: TestError.locator
* since: v1.49
- type: ?<[string]>

Receiver's locator.

## property: TestError.log
* since: v1.49
- type: ?<[Array]<[string]>>
Expand Down
52 changes: 45 additions & 7 deletions packages/html-reporter/src/testErrorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,50 @@ import * as React from 'react';
import './testErrorView.css';
import type { ImageDiff } from '@web/shared/imageDiffView';
import { ImageDiffView } from '@web/shared/imageDiffView';
import { ErrorDetails } from './types';

Check failure on line 22 in packages/html-reporter/src/testErrorView.tsx

View workflow job for this annotation

GitHub Actions / docs & lint

All imports in the declaration are only used as types. Use `import type`

export const TestErrorView: React.FC<{
error: string;
error: ErrorDetails;
testId?: string;
}> = ({ error, testId }) => {
const html = React.useMemo(() => ansiErrorToHtml(error), [error]);
const html = React.useMemo(() => {
const formattedError = [];
if (error.shortMessage)
formattedError.push('Error: ' + error.shortMessage);
if (error.locator)
formattedError.push(`Locator: ${error.locator}`);
if (error.expected)
formattedError.push(`Expected: ${error.expected}`);
if (error.actual)
formattedError.push(`Received: ${error.actual}`);
// if (error.diff)
if (error.log) {
formattedError.push('Call log:');
formattedError.push(...(error.log?.map(line => ' - ' + line) || []));
}
if (error.snippet)
formattedError.push('', error.snippet);
return ansiErrorToHtml(formattedError.join('\n'));
}, [error]);

return <div className='test-error-view test-error-text' data-testId={testId} dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
};

export const TestScreenshotErrorView: React.FC<{
errorPrefix?: string,
error: ErrorDetails,
diff: ImageDiff,
errorSuffix?: string,
}> = ({ errorPrefix, diff, errorSuffix }) => {
const prefixHtml = React.useMemo(() => ansiErrorToHtml(errorPrefix), [errorPrefix]);
const suffixHtml = React.useMemo(() => ansiErrorToHtml(errorSuffix), [errorSuffix]);
}> = ({ error, diff }) => {
const prefixHtml = React.useMemo(() => ansiErrorToHtml(error.shortMessage), [error]);
const suffixHtml = React.useMemo(() => {
const errorSuffix = ['Call log:',
...(error.log?.map(line => ' - ' + line) || []),
'',
error.snippet,
'',
error.callStack,
].join('\n');
return ansiErrorToHtml(errorSuffix)

Check failure on line 64 in packages/html-reporter/src/testErrorView.tsx

View workflow job for this annotation

GitHub Actions / docs & lint

Missing semicolon
}, [error]);
return <div data-testid='test-screenshot-error-view' className='test-error-view'>
<div dangerouslySetInnerHTML={{ __html: prefixHtml || '' }} className='test-error-text' style={{ marginBottom: 20 }}></div>
<ImageDiffView key='image-diff' diff={diff} hideDetails={true}></ImageDiffView>
Expand Down Expand Up @@ -73,3 +101,13 @@ const ansiColors = {
function escapeHTML(text: string): string {
return text.replace(/[&"<>]/g, c => ({ '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' }[c]!));
}

export function formatCallLog(log: string[] | undefined): string {
if (!log || !log.some(l => !!l))
return '';
return `
Call log:
${'- ' + (log || []).join('\n - ')}
`;
}

29 changes: 11 additions & 18 deletions packages/html-reporter/src/testResultView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const TestResultView: React.FC<{
{!!errors.length && <AutoChip header='Errors'>
{errors.map((error, index) => {
if (error.type === 'screenshot')
return <TestScreenshotErrorView key={'test-result-error-message-' + index} errorPrefix={error.errorPrefix} diff={error.diff!} errorSuffix={error.errorSuffix}></TestScreenshotErrorView>;
return <TestScreenshotErrorView key={'test-result-error-message-' + index} error={error.error} diff={error.diff!}></TestScreenshotErrorView>;
return <TestErrorView key={'test-result-error-message-' + index} error={error.error!}></TestErrorView>;
})}
</AutoChip>}
Expand Down Expand Up @@ -155,25 +155,18 @@ function classifyErrors(testErrors: ErrorDetails[], diffs: ImageDiff[]) {
if (error.shortMessage?.includes('Screenshot comparison failed:') && error.actual && error.expected) {
const matchingDiff = diffs.find(diff => {
const attachmentName = diff.actual?.attachment.name;
return attachmentName && error.actual.endsWith(attachmentName);
return attachmentName && error.actual?.endsWith(attachmentName);
});
const errorSuffix = ['Call log:',
...(error.log?.map(line => ' - ' + line) || []),
'',
error.snippet,
'',
error.callStack,
].join('\n');
if (matchingDiff) {
return {
type: 'screenshot',
diff: matchingDiff,
errorPrefix: error.shortMessage,
errorSuffix
};
}
return {
type: 'screenshot',
diff: matchingDiff,
error,
};
}
return { type: 'regular', error: error.message };
return {
type: 'regular',
error
};
});
}

Expand Down
5 changes: 3 additions & 2 deletions packages/html-reporter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ export type ErrorDetails = {
message: string;
location?: Location;
shortMessage?: string;
locator?: string;
log?: string[];
expected?: any;
actual?: any;
expected?: string;
actual?: string;
snippet?: string;
callStack?: string;
};
Expand Down
10 changes: 9 additions & 1 deletion packages/playwright/bundles/expect/third_party/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,18 @@ const matchers: MatchersObject = {
);
};

const header = matcherHint(matcherName, undefined, undefined, options) + '\n';

Check failure on line 138 in packages/playwright/bundles/expect/third_party/matchers.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Expected indentation of 4 spaces but found 6
const printedExpected = `${pass ? 'not ' : ''}${printExpected(expected)}`

Check failure on line 139 in packages/playwright/bundles/expect/third_party/matchers.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Expected indentation of 4 spaces but found 6

Check failure on line 139 in packages/playwright/bundles/expect/third_party/matchers.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Missing semicolon
const printedReceived = printReceived(received);

Check failure on line 140 in packages/playwright/bundles/expect/third_party/matchers.ts

View workflow job for this annotation

GitHub Actions / docs & lint

Expected indentation of 4 spaces but found 6

// Passing the actual and expected objects so that a custom reporter
// could access them, for example in order to display a custom visual diff,
// or create a different error message
return { actual: received, expected, message, name: matcherName, pass };
return { actual: received, expected, message, name: matcherName, pass,
header,
printedReceived,
printedExpected,
};
},

toBeCloseTo(received: number, expected: number, precision = 2) {
Expand Down
6 changes: 6 additions & 0 deletions packages/playwright/src/matchers/matcherHint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export type MatcherResult<E, A> = {
actual?: A;
log?: string[];
timeout?: number;

locator?: string;
header?: string;
printedReceived?: string;
printedExpected?: string;

};

export class ExpectError extends Error {
Expand Down
54 changes: 32 additions & 22 deletions packages/playwright/src/matchers/toMatchText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,30 +58,35 @@ export async function toMatchText(
const timeout = options.timeout ?? this.timeout;

const { matches: pass, received, log, timedOut } = await query(!!this.isNot, timeout);
if (pass === !this.isNot) {
return {
name: matcherName,
message: () => '',
pass,
expected
};
}

const stringSubstring = options.matchSubstring ? 'substring' : 'string';
const receivedString = received || '';
const messagePrefix = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
const headerWithLocator = matcherHint(this, receiver, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
// Pass timeout and locator as fields on the error?
const header = matcherHint(this, undefined, matcherName, 'locator', undefined, matcherOptions, timedOut ? timeout : undefined);
const notFound = received === kNoElementsFoundError;
const message = () => {
if (pass) {
if (typeof expected === 'string') {
if (notFound)
return messagePrefix + `Expected ${stringSubstring}: not ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
const printedReceived = printReceivedStringContainExpectedSubstring(receivedString, receivedString.indexOf(expected), expected.length);
return messagePrefix + `Expected ${stringSubstring}: not ${this.utils.printExpected(expected)}\nReceived string: ${printedReceived}` + callLogText(log);
} else {
if (notFound)
return messagePrefix + `Expected pattern: not ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
const printedReceived = printReceivedStringContainExpectedResult(receivedString, typeof expected.exec === 'function' ? expected.exec(receivedString) : null);
return messagePrefix + `Expected pattern: not ${this.utils.printExpected(expected)}\nReceived string: ${printedReceived}` + callLogText(log);
}
} else {
const labelExpected = `Expected ${typeof expected === 'string' ? stringSubstring : 'pattern'}`;
if (notFound)
return messagePrefix + `${labelExpected}: ${this.utils.printExpected(expected)}\nReceived: ${received}` + callLogText(log);
return messagePrefix + this.utils.printDiffOrStringify(expected, receivedString, labelExpected, 'Received string', false) + callLogText(log);
}
};
let printedReceived;
// {not} {string/substring/pattern} {formatted}
let printedExpected = `${typeof expected === 'string' ? stringSubstring : 'pattern'} ${this.utils.printExpected(expected)}`;
if (pass) {
printedExpected = `not ${printedExpected}`;
const receivedString = received || '';
printedReceived = notFound
? received
: typeof expected === 'string'
? printReceivedStringContainExpectedSubstring(receivedString, receivedString.indexOf(expected), expected.length)
: printReceivedStringContainExpectedResult(receivedString, typeof expected.exec === 'function' ? expected.exec(receivedString) : null);
} else {
printedReceived = notFound ? received : `string ${this.utils.printReceived(received)}`;
}
const message = () => `${headerWithLocator}Expected: ${printedExpected}\nReceived: ${printedReceived}` + callLogText(log);

return {
name: matcherName,
Expand All @@ -91,5 +96,10 @@ export async function toMatchText(
actual: received,
log,
timeout: timedOut ? timeout : undefined,

locator: receiver.toString(),

Check failure on line 100 in packages/playwright/src/matchers/toMatchText.ts

View workflow job for this annotation

GitHub Actions / docs & lint

'receiver' will use Object's default stringification format ('[object Object]') when stringified
header,
printedReceived,
printedExpected,
};
}
7 changes: 4 additions & 3 deletions packages/playwright/src/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ type ErrorDetails = {
type TestResultErrorDetails = ErrorDetails & {
shortMessage?: string;
log?: string[];
expected?: any;
actual?: any;

locator?: string;
expected?: string;
actual?: string;
snippet?: string;
stack?: string;
};
Expand Down Expand Up @@ -396,6 +396,7 @@ export function formatResultFailure(test: TestCase, result: TestResult, initialI
location: formattedError.location,
shortMessage: error.shortMessage,
log: error.log,
locator: error.locator,
expected: error.expected,
actual: error.actual,
snippet: error.snippet,
Expand Down
9 changes: 5 additions & 4 deletions packages/playwright/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@ export function filterStackTrace(e: Error): Omit<TestInfoError, 'value'> {
};
}

function filterExpectDetails(e: Error): Pick<TestInfoError, 'shortMessage'|'log'|'expected'|'actual'> {
function filterExpectDetails(e: Error): Pick<TestInfoError, 'shortMessage'|'log'|'expected'|'actual'|'locator'> {
const matcherResult = (e as any).matcherResult as MatcherResult<unknown, unknown>;
if (!matcherResult)
return {};
return {
shortMessage: matcherResult.shortMessage,
shortMessage: matcherResult.header,
log: matcherResult.log,
expected: matcherResult.expected,
actual: matcherResult.actual,
locator: matcherResult.locator,
expected: matcherResult.printedExpected,
actual: matcherResult.printedReceived,
};
}

Expand Down
9 changes: 5 additions & 4 deletions packages/playwright/src/worker/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ export function serializeWorkerError(error: Error | any): TestInfoError {
};
}

function serializeExpectDetails(e: Error): Pick<TestInfoError, 'shortMessage'|'log'|'expected'|'actual'> {
function serializeExpectDetails(e: Error): Pick<TestInfoError, 'shortMessage'|'log'|'expected'|'actual'|'locator'> {
const matcherResult = (e as any).matcherResult as MatcherResult<unknown, unknown>;
if (!matcherResult)
return {};
return {
shortMessage: matcherResult.shortMessage,
shortMessage: matcherResult.header,
log: matcherResult.log,
expected: matcherResult.expected,
actual: matcherResult.actual,
expected: matcherResult.printedExpected,
actual: matcherResult.printedReceived,
locator: matcherResult.locator,
};
}

9 changes: 7 additions & 2 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9152,12 +9152,17 @@ export interface TestInfoError {
/**
* Actual value.
*/
actual?: any;
actual?: string;

/**
* Expected value.
*/
expected?: any;
expected?: string;

/**
* Receiver's locator.
*/
locator?: string;

/**
* Call log.
Expand Down
9 changes: 7 additions & 2 deletions packages/playwright/types/testReporter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,18 +557,23 @@ export interface TestError {
/**
* Actual value.
*/
actual?: any;
actual?: string;

/**
* Expected value.
*/
expected?: any;
expected?: string;

/**
* Error location in the source code.
*/
location?: Location;

/**
* Receiver's locator.
*/
locator?: string;

/**
* Call log.
*/
Expand Down
Loading

0 comments on commit 8a5212c

Please sign in to comment.