mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(api): expose step location UI (#9605)
This commit is contained in:
parent
c06a6e1f63
commit
bccd4c8906
@ -90,7 +90,6 @@ svg {
|
||||
}
|
||||
|
||||
.chip-body > .tree-item {
|
||||
max-width: 600px;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
|
@ -217,10 +217,11 @@ const StepTreeItem: React.FC<{
|
||||
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
|
||||
{statusIcon(step.error ? 'failed' : 'passed')}
|
||||
<span>{step.title}</span>
|
||||
</span>} loadChildren={step.steps.length + (step.error ? 1 : 0) ? () => {
|
||||
{step.location && <span className='test-summary-path'>— {step.location.file}:{step.location.line}</span>}
|
||||
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => {
|
||||
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>);
|
||||
if (step.error)
|
||||
children.unshift(<ErrorMessage key={-1} error={step.error}></ErrorMessage>);
|
||||
if (step.snippet)
|
||||
children.unshift(<ErrorMessage key='line' error={step.snippet}></ErrorMessage>);
|
||||
return children;
|
||||
} : undefined} depth={depth}></TreeItem>;
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { codeFrameColumns } from '@babel/code-frame';
|
||||
import { BabelCodeFrameOptions, codeFrameColumns } from '@babel/code-frame';
|
||||
import colors from 'colors/safe';
|
||||
import fs from 'fs';
|
||||
import milliseconds from 'ms';
|
||||
@ -337,7 +337,7 @@ export function formatError(error: TestError, highlightCode: boolean, file?: str
|
||||
positionInFile = position;
|
||||
tokens.push(message);
|
||||
|
||||
const codeFrame = generateCodeFrame(highlightCode, file, position);
|
||||
const codeFrame = generateCodeFrame({ highlightCode }, file, position);
|
||||
if (codeFrame) {
|
||||
tokens.push('');
|
||||
tokens.push(codeFrame);
|
||||
@ -365,7 +365,7 @@ function indent(lines: string, tab: string) {
|
||||
return lines.replace(/^(?=.+$)/gm, tab);
|
||||
}
|
||||
|
||||
function generateCodeFrame(highlightCode: boolean, file?: string, position?: PositionInFile): string | undefined {
|
||||
export function generateCodeFrame(options: BabelCodeFrameOptions, file?: string, position?: PositionInFile): string | undefined {
|
||||
if (!position || !file)
|
||||
return;
|
||||
|
||||
@ -373,7 +373,7 @@ function generateCodeFrame(highlightCode: boolean, file?: string, position?: Pos
|
||||
const codeFrame = codeFrameColumns(
|
||||
source,
|
||||
{ start: position },
|
||||
{ highlightCode }
|
||||
options
|
||||
);
|
||||
|
||||
return codeFrame;
|
||||
|
@ -90,7 +90,8 @@ export type TestStep = {
|
||||
title: string;
|
||||
startTime: string;
|
||||
duration: number;
|
||||
log?: string[];
|
||||
location?: Location;
|
||||
snippet?: string;
|
||||
error?: string;
|
||||
steps: TestStep[];
|
||||
};
|
||||
@ -365,8 +366,9 @@ class HtmlBuilder {
|
||||
title: step.title,
|
||||
startTime: step.startTime,
|
||||
duration: step.duration,
|
||||
snippet: step.snippet,
|
||||
steps: step.steps.map(s => this._createTestStep(s)),
|
||||
log: step.log,
|
||||
location: step.location,
|
||||
error: step.error
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { FullProject } from '../types';
|
||||
import { FullConfig, Location, Suite, TestCase, TestResult, TestStatus, TestStep } from '../../types/testReporter';
|
||||
import { assert, calculateSha1 } from 'playwright-core/src/utils/utils';
|
||||
import { sanitizeForFilePath } from '../util';
|
||||
import { formatResultFailure } from './base';
|
||||
import { formatResultFailure, generateCodeFrame } from './base';
|
||||
import { toPosixPath, serializePatterns } from './json';
|
||||
|
||||
export type JsonLocation = Location;
|
||||
@ -93,7 +93,8 @@ export type JsonTestStep = {
|
||||
duration: number;
|
||||
error?: JsonError;
|
||||
steps: JsonTestStep[];
|
||||
log?: string[];
|
||||
location?: Location;
|
||||
snippet?: string;
|
||||
};
|
||||
|
||||
class RawReporter {
|
||||
@ -159,18 +160,18 @@ class RawReporter {
|
||||
fileId,
|
||||
location,
|
||||
suites: suite.suites.map(s => this._serializeSuite(s)),
|
||||
tests: suite.tests.map(t => this._serializeTest(t, fileId, location.file)),
|
||||
tests: suite.tests.map(t => this._serializeTest(t, fileId)),
|
||||
};
|
||||
}
|
||||
|
||||
private _serializeTest(test: TestCase, fileId: string, fileName: string): JsonTestCase {
|
||||
private _serializeTest(test: TestCase, fileId: string): JsonTestCase {
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const testIdExpression = `project:${projectName}|path:${titles.join('>')}`;
|
||||
const testId = fileId + '-' + calculateSha1(testIdExpression);
|
||||
return {
|
||||
testId,
|
||||
title: test.title,
|
||||
location: this._relativeLocation(test.location),
|
||||
location: this._relativeLocation(test.location)!,
|
||||
expectedStatus: test.expectedStatus,
|
||||
timeout: test.timeout,
|
||||
annotations: test.annotations,
|
||||
@ -202,8 +203,9 @@ class RawReporter {
|
||||
startTime: step.startTime.toISOString(),
|
||||
duration: step.duration,
|
||||
error: step.error?.message,
|
||||
location: this._relativeLocation(step.location),
|
||||
steps: this._serializeSteps(test, step.steps),
|
||||
log: step.data.log || undefined,
|
||||
snippet: step.location ? generateCodeFrame({ highlightCode: true, linesBelow: 1, linesAbove: 1 }, step.location.file, step.location) : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -248,9 +250,9 @@ class RawReporter {
|
||||
};
|
||||
}
|
||||
|
||||
private _relativeLocation(location: Location | undefined): Location {
|
||||
private _relativeLocation(location: Location | undefined): Location | undefined {
|
||||
if (!location)
|
||||
return { file: '', line: 0, column: 0 };
|
||||
return undefined;
|
||||
const file = toPosixPath(path.relative(this.config.rootDir, location.file));
|
||||
return {
|
||||
file,
|
||||
|
@ -306,8 +306,9 @@ export class WorkerRunner extends EventEmitter {
|
||||
this.emit('stepEnd', payload);
|
||||
}
|
||||
};
|
||||
// Sanitize location that comes from userland.
|
||||
const location = data.location ? { file: data.location.file, line: data.location.line, column: data.location.column } : undefined;
|
||||
const hasLocation = data.location && !data.location.file.includes('@playwright');
|
||||
// Sanitize location that comes from user land, it might have extra properties.
|
||||
const location = data.location && hasLocation ? { file: data.location.file, line: data.location.line, column: data.location.column } : undefined;
|
||||
const payload: StepBeginPayload = {
|
||||
testId,
|
||||
stepId,
|
||||
|
Loading…
x
Reference in New Issue
Block a user