diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 7dbf1b3a12..922d3b6083 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -25,6 +25,7 @@ "./src/grid/dockerGridFactory": "./lib/grid/dockerGridFactory.js", "./src/utils/async": "./lib/utils/async.js", "./src/utils/httpServer": "./lib/utils/httpServer.js", + "./src/utils/multimap": "./lib/utils/multimap.js", "./src/utils/processLauncher": "./lib/utils/processLauncher.js", "./src/utils/registry": "./lib/utils/registry.js", "./src/utils/utils": "./lib/utils/utils.js" diff --git a/packages/playwright-core/src/web/htmlReport/htmlReport.css b/packages/playwright-core/src/web/htmlReport/htmlReport.css index ca747495e0..9e7631a0fc 100644 --- a/packages/playwright-core/src/web/htmlReport/htmlReport.css +++ b/packages/playwright-core/src/web/htmlReport/htmlReport.css @@ -368,10 +368,6 @@ a.no-decorations { color: var(--color-fg-muted) !important; } -.color-fg-white { - color: white !important; -} - .octicon { margin-right: 7px; flex: none; diff --git a/packages/playwright-core/src/web/htmlReport/htmlReport.tsx b/packages/playwright-core/src/web/htmlReport/htmlReport.tsx index 6b8917e063..4cfabf5c81 100644 --- a/packages/playwright-core/src/web/htmlReport/htmlReport.tsx +++ b/packages/playwright-core/src/web/htmlReport/htmlReport.tsx @@ -303,9 +303,7 @@ function statusIcon(status: 'failed' | 'timedOut' | 'skipped' | 'passed' | 'expe ; case 'skipped': - return ; + return ; } } diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts index c82f78b921..7337fc89ca 100644 --- a/packages/playwright-test/src/reporters/html.ts +++ b/packages/playwright-test/src/reporters/html.ts @@ -179,12 +179,10 @@ class HtmlBuilder { private _reportFolder: string; private _tests = new Map(); private _testPath = new Map(); - private _rootDir: string; private _dataFolder: string; private _hasTraces = false; constructor(outputDir: string, rootDir: string) { - this._rootDir = rootDir; this._reportFolder = path.resolve(process.cwd(), outputDir); this._dataFolder = path.join(this._reportFolder, 'data'); } diff --git a/packages/playwright-test/src/reporters/raw.ts b/packages/playwright-test/src/reporters/raw.ts index bc1d9369de..0842b126e8 100644 --- a/packages/playwright-test/src/reporters/raw.ts +++ b/packages/playwright-test/src/reporters/raw.ts @@ -19,8 +19,10 @@ import path from 'path'; 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, generateCodeFrame } from './base'; +import { formatResultFailure } from './base'; import { toPosixPath, serializePatterns } from './json'; +import { MultiMap } from 'playwright-core/src/utils/multimap'; +import { codeFrameColumns } from '@babel/code-frame'; export type JsonLocation = Location; export type JsonError = string; @@ -99,6 +101,7 @@ export type JsonTestStep = { class RawReporter { private config!: FullConfig; private suite!: Suite; + private stepsInFile = new MultiMap(); onBegin(config: FullConfig, suite: Suite) { this.config = config; @@ -148,6 +151,31 @@ class RawReporter { }, suites: suite.suites.map(s => this._serializeSuite(s)) }; + for (const file of this.stepsInFile.keys()) { + let source: string; + try { + source = fs.readFileSync(file, 'utf-8') + '\n//'; + } catch (e) { + continue; + } + const lines = source.split('\n').length; + const highlighted = codeFrameColumns(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 }); + const highlightedLines = highlighted.split('\n'); + const lineWithArrow = highlightedLines[highlightedLines.length - 1]; + for (const step of this.stepsInFile.get(file)) { + // Don't bother with snippets that have less than 3 lines. + if (step.location!.line < 2 || step.location!.line >= lines) + continue; + // Cut out snippet. + const snippetLines = highlightedLines.slice(step.location!.line - 2, step.location!.line + 1); + // Relocate arrow. + const index = lineWithArrow.indexOf('^'); + const shiftedArrow = lineWithArrow.slice(0, index) + ' '.repeat(step.location!.column - 1) + lineWithArrow.slice(index); + // Insert arrow line. + snippetLines.splice(2, 0, shiftedArrow); + step.snippet = snippetLines.join('\n'); + } + } return report; } @@ -190,23 +218,24 @@ class RawReporter { status: result.status, error: formatResultFailure(test, result, '', true).tokens.join('').trim(), attachments: this._createAttachments(result), - steps: this._serializeSteps(test, result.steps) + steps: result.steps.map(step => this._serializeStep(test, step)) }; } - private _serializeSteps(test: TestCase, steps: TestStep[]): JsonTestStep[] { - return steps.map(step => { - return { - title: step.title, - category: step.category, - startTime: step.startTime.toISOString(), - duration: step.duration, - error: step.error?.message, - location: this._relativeLocation(step.location), - steps: this._serializeSteps(test, step.steps), - snippet: step.location ? generateCodeFrame({ highlightCode: true, linesBelow: 1, linesAbove: 1 }, step.location.file, step.location) : undefined - }; - }); + private _serializeStep(test: TestCase, step: TestStep): JsonTestStep { + const result: JsonTestStep = { + title: step.title, + category: step.category, + startTime: step.startTime.toISOString(), + duration: step.duration, + error: step.error?.message, + location: this._relativeLocation(step.location), + steps: step.steps.map(step => this._serializeStep(test, step)), + }; + + if (step.location) + this.stepsInFile.set(step.location.file, result); + return result; } private _createAttachments(result: TestResult): JsonAttachment[] {