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[] {