Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/playwright/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function addTestServerCommand(program: Command) {
function addShowReportCommand(program: Command) {
const command = program.command('show-report [report]');
command.description('show HTML report');
command.action((report, options) => showHTMLReport(report, options.host, +options.port));
command.action((report, options) => showHTMLReport(terminalScreen, report, options.host, +options.port));
command.option('--host <host>', 'Host to serve report on', 'localhost');
command.option('--port <port>', 'Port to serve report on', '9323');
command.addHelpText('afterAll', `
Expand Down
75 changes: 40 additions & 35 deletions packages/playwright/src/reporters/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ import path from 'path';
import { Transform } from 'stream';

import { HttpServer, MultiMap, assert, calculateSha1, getPackageManagerExecCommand, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils';
import { colors } from 'playwright-core/lib/utils';
import { open } from 'playwright-core/lib/utilsBundle';
import { mime } from 'playwright-core/lib/utilsBundle';
import { yazl } from 'playwright-core/lib/zipBundle';

import { CommonReporterOptions, formatError, formatResultFailure, internalScreen } from './base';
import { CommonReporterOptions, formatError, formatResultFailure, internalScreen, terminalScreen } from './base';
import { codeFrameColumns } from '../transform/babelBundle';
import { resolveReporterOutputPath, stripAnsiEscapes } from '../util';

Expand All @@ -34,6 +33,7 @@ import type * as api from '../../types/testReporter';
import type { HTMLReport, HTMLReportOptions, Location, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types';
import type { ZipFile } from 'playwright-core/lib/zipBundle';
import type { TransformCallback } from 'stream';
import type { TerminalScreen } from './base';

type TestEntry = {
testCase: TestCase;
Expand All @@ -57,6 +57,7 @@ class HtmlReporter implements ReporterV2 {
private config!: api.FullConfig;
private suite!: api.Suite;
private _options: HtmlReporterConfigOptions & CommonReporterOptions;
private _screen: TerminalScreen;
private _outputFolder!: string;
private _attachmentsBaseURL!: string;
private _open: string | undefined;
Expand All @@ -67,8 +68,9 @@ class HtmlReporter implements ReporterV2 {
private _reportConfigs = new Map<string, api.FullConfig>();
private _machines: MachineData[] = [];

constructor(options: HtmlReporterConfigOptions & CommonReporterOptions) {
constructor(options: HtmlReporterConfigOptions & CommonReporterOptions & { screen?: TerminalScreen }) {
this._options = options;
this._screen = options.screen ?? terminalScreen;
}

version(): 'v2' {
Expand Down Expand Up @@ -97,12 +99,12 @@ class HtmlReporter implements ReporterV2 {
if (reportedWarnings.has(key))
continue;
reportedWarnings.add(key);
writeLine(colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`));
writeLine(`
html reporter folder: ${colors.bold(outputFolder)}
test results folder: ${colors.bold(project.outputDir)}`);
writeLine('');
writeLine(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss.
this._writeLine(this._screen.colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`));
this._writeLine(`
html reporter folder: ${this._screen.colors.bold(outputFolder)}
test results folder: ${this._screen.colors.bold(project.outputDir)}`);
this._writeLine('');
this._writeLine(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss.
`);
}
}
Expand All @@ -113,7 +115,7 @@ class HtmlReporter implements ReporterV2 {
const outputFolder = reportFolderFromEnv() ?? resolveReporterOutputPath('playwright-report', this._options.configDir, this._options.outputFolder);
return {
outputFolder,
open: getHtmlReportOptionProcessEnv() || this._options.open || 'on-failure',
open: this._getHtmlReportOptionProcessEnv() || this._options.open || 'on-failure',
attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || 'data/',
host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host,
port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port,
Expand Down Expand Up @@ -169,21 +171,37 @@ class HtmlReporter implements ReporterV2 {
return;
const { ok, singleTestId } = this._buildResult;
const isCodingAgent = !!process.env.CLAUDECODE || !!process.env.COPILOT_CLI;
const shouldOpen = !isCodingAgent && !!process.stdin.isTTY && (this._open === 'always' || (!ok && this._open === 'on-failure'));
const shouldOpen = !isCodingAgent && this._screen.isTTY && (this._open === 'always' || (!ok && this._open === 'on-failure'));
if (shouldOpen) {
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
} else if (this._options._mode === 'test' && !!process.stdin.isTTY) {
await showHTMLReport(this._screen, this._outputFolder, this._host, this._port, singleTestId);
} else if (this._options._mode === 'test' && this._screen.isTTY) {
const packageManagerCommand = getPackageManagerExecCommand();
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder);
const hostArg = this._host ? ` --host ${this._host}` : '';
const portArg = this._port ? ` --port ${this._port}` : '';
writeLine('');
writeLine('To open last HTML report run:');
writeLine(colors.cyan(`
this._writeLine('');
this._writeLine('To open last HTML report run:');
this._writeLine(this._screen.colors.cyan(`
${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg}
`));
}
}

private _getHtmlReportOptionProcessEnv(): HtmlReportOpenOption | undefined {
// Note: PW_TEST_HTML_REPORT_OPEN is for backwards compatibility.
const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
if (!htmlOpenEnv)
return undefined;
if (!isHtmlReportOption(htmlOpenEnv)) {
this._writeLine(this._screen.colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(', ')}`));
return undefined;
}
return htmlOpenEnv;
}

private _writeLine(line: string) {
writeLine(this._screen, line);
}
}

function reportFolderFromEnv(): string | undefined {
Expand All @@ -192,36 +210,24 @@ function reportFolderFromEnv(): string | undefined {
return envValue ? path.resolve(envValue) : undefined;
}

function getHtmlReportOptionProcessEnv(): HtmlReportOpenOption | undefined {
// Note: PW_TEST_HTML_REPORT_OPEN is for backwards compatibility.
const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
if (!htmlOpenEnv)
return undefined;
if (!isHtmlReportOption(htmlOpenEnv)) {
writeLine(colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(', ')}`));
return undefined;
}
return htmlOpenEnv;
}

function standaloneDefaultFolder(): string {
return reportFolderFromEnv() ?? resolveReporterOutputPath('playwright-report', process.cwd(), undefined);
}

export async function showHTMLReport(reportFolder: string | undefined, host: string = 'localhost', port?: number, testId?: string) {
export async function showHTMLReport(screen: TerminalScreen, reportFolder: string | undefined, host: string = 'localhost', port?: number, testId?: string) {
const folder = reportFolder ?? standaloneDefaultFolder();
try {
assert(fs.statSync(folder).isDirectory());
} catch (e) {
writeLine(colors.red(`No report found at "${folder}"`));
writeLine(screen, screen.colors.red(`No report found at "${folder}"`));
gracefullyProcessExitDoNotHang(1);
return;
}
const server = startHtmlReportServer(folder);
await server.start({ port, host, preferredPort: port ? undefined : 9323 });
let url = server.urlPrefix('human-readable');
writeLine('');
writeLine(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
writeLine(screen, '');
writeLine(screen, screen.colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
if (testId)
url += `#?testId=${testId}`;
url = url.replace('0.0.0.0', 'localhost');
Expand Down Expand Up @@ -740,9 +746,8 @@ function createErrorCodeframe(message: string, location: Location) {
);
}

function writeLine(line: string) {
// eslint-disable-next-line no-restricted-properties
process.stdout.write(line + '\n');
function writeLine(screen: TerminalScreen, line: string) {
screen.stdout.write(line + '\n');
}

export default HtmlReporter;
Loading