feat(html): open show html report when there are failures (#9526)
This commit is contained in:
parent
7a68f2f661
commit
5ea4ec2f7a
|
|
@ -222,7 +222,6 @@ program
|
||||||
}).addHelpText('afterAll', `
|
}).addHelpText('afterAll', `
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
$ show-trace trace/directory
|
|
||||||
$ show-trace https://example.com/trace.zip`);
|
$ show-trace https://example.com/trace.zip`);
|
||||||
|
|
||||||
if (!process.env.PW_CLI_TARGET_LANG) {
|
if (!process.env.PW_CLI_TARGET_LANG) {
|
||||||
|
|
@ -235,6 +234,7 @@ if (!process.env.PW_CLI_TARGET_LANG) {
|
||||||
|
|
||||||
if (playwrightTestPackagePath) {
|
if (playwrightTestPackagePath) {
|
||||||
require(playwrightTestPackagePath).addTestCommand(program);
|
require(playwrightTestPackagePath).addTestCommand(program);
|
||||||
|
require(playwrightTestPackagePath).addShowReportCommand(program);
|
||||||
} else {
|
} else {
|
||||||
const command = program.command('test').allowUnknownOption(true);
|
const command = program.command('test').allowUnknownOption(true);
|
||||||
command.description('Run tests with Playwright Test. Available in @playwright/test package.');
|
command.description('Run tests with Playwright Test. Available in @playwright/test package.');
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,22 @@ export const Report: React.FC = () => {
|
||||||
const [report, setReport] = React.useState<ProjectTreeItem[]>([]);
|
const [report, setReport] = React.useState<ProjectTreeItem[]>([]);
|
||||||
const [fetchError, setFetchError] = React.useState<string | undefined>();
|
const [fetchError, setFetchError] = React.useState<string | undefined>();
|
||||||
const [testId, setTestId] = React.useState<TestId | undefined>();
|
const [testId, setTestId] = React.useState<TestId | undefined>();
|
||||||
|
const [filter, setFilter] = React.useState<Filter>('failing');
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const result = await fetch('data/projects.json', { cache: 'no-cache' });
|
const result = await fetch('data/projects.json', { cache: 'no-cache' });
|
||||||
const json = (await result.json()) as ProjectTreeItem[];
|
const json = (await result.json()) as ProjectTreeItem[];
|
||||||
|
const hasFailures = !!json.find(p => !p.stats.ok);
|
||||||
|
if (!hasFailures)
|
||||||
|
setFilter('all');
|
||||||
setReport(json);
|
setReport(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setFetchError(e.message);
|
setFetchError(e.message);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
const [filter, setFilter] = React.useState<Filter>('failing');
|
|
||||||
|
|
||||||
return <div className='hbox columns'>
|
return <div className='hbox columns'>
|
||||||
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
|
||||||
|
|
@ -57,7 +60,7 @@ export const Report: React.FC = () => {
|
||||||
<div key='failing' title='Failing tests' className={'tab-element' + ('failing' === filter ? ' selected' : '')} onClick={() => setFilter('failing')}>Failing</div>
|
<div key='failing' title='Failing tests' className={'tab-element' + ('failing' === filter ? ' selected' : '')} onClick={() => setFilter('failing')}>Failing</div>
|
||||||
</div>
|
</div>
|
||||||
{!fetchError && filter === 'all' && report?.map((project, i) => <ProjectTreeItemView key={i} project={project} setTestId={setTestId} testId={testId} failingOnly={false}></ProjectTreeItemView>)}
|
{!fetchError && filter === 'all' && report?.map((project, i) => <ProjectTreeItemView key={i} project={project} setTestId={setTestId} testId={testId} failingOnly={false}></ProjectTreeItemView>)}
|
||||||
{!fetchError && filter === 'failing' && report?.map((project, i) => <ProjectTreeItemView key={i} project={project} setTestId={setTestId} testId={testId} failingOnly={true}></ProjectTreeItemView>)}
|
{!fetchError && filter === 'failing' && report?.filter(p => !p.stats.ok).map((project, i) => <ProjectTreeItemView key={i} project={project} setTestId={setTestId} testId={testId} failingOnly={true}></ProjectTreeItemView>)}
|
||||||
</div>
|
</div>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { Runner, builtInReporters, BuiltInReporter } from './runner';
|
||||||
import { stopProfiling, startProfiling } from './profiler';
|
import { stopProfiling, startProfiling } from './profiler';
|
||||||
import { FilePatternFilter } from './util';
|
import { FilePatternFilter } from './util';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
|
import { showHTMLReport } from './reporters/html';
|
||||||
|
|
||||||
const defaultTimeout = 30000;
|
const defaultTimeout = 30000;
|
||||||
const defaultReporter: BuiltInReporter = process.env.CI ? 'dot' : 'list';
|
const defaultReporter: BuiltInReporter = process.env.CI ? 'dot' : 'list';
|
||||||
|
|
@ -77,9 +78,22 @@ Arguments [test-filter...]:
|
||||||
Pass arguments to filter test files. Each argument is treated as a regular expression.
|
Pass arguments to filter test files. Each argument is treated as a regular expression.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$ test my.spec.ts
|
$ npx playwright test my.spec.ts
|
||||||
$ test --headed
|
$ npx playwright test --headed
|
||||||
$ test --browser=webkit`);
|
$ npx playwright test --browser=webkit`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addShowReportCommand(program: Command) {
|
||||||
|
const command = program.command('show-report [report]');
|
||||||
|
command.description('show HTML report');
|
||||||
|
command.action(report => showHTMLReport(report));
|
||||||
|
command.addHelpText('afterAll', `
|
||||||
|
Arguments [report]:
|
||||||
|
When specified, opens given report, otherwise opens last generated report.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$ npx playwright show-report
|
||||||
|
$ npx playwright show-report playwright-report`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createLoader(opts: { [key: string]: any }): Promise<Loader> {
|
async function createLoader(opts: { [key: string]: any }): Promise<Loader> {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { HttpServer } from 'playwright-core/src/utils/httpServer';
|
||||||
import { calculateSha1, removeFolders } from 'playwright-core/src/utils/utils';
|
import { calculateSha1, removeFolders } from 'playwright-core/src/utils/utils';
|
||||||
import { toPosixPath } from './json';
|
import { toPosixPath } from './json';
|
||||||
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
export type Stats = {
|
export type Stats = {
|
||||||
total: number;
|
total: number;
|
||||||
|
|
@ -114,40 +115,68 @@ class HtmlReporter {
|
||||||
const report = rawReporter.generateProjectReport(this.config, suite);
|
const report = rawReporter.generateProjectReport(this.config, suite);
|
||||||
return report;
|
return report;
|
||||||
});
|
});
|
||||||
const reportFolder = path.resolve(process.cwd(), process.env[`PLAYWRIGHT_HTML_REPORT`] || 'playwright-report');
|
const reportFolder = htmlReportFolder();
|
||||||
await removeFolders([reportFolder]);
|
await removeFolders([reportFolder]);
|
||||||
new HtmlBuilder(reports, reportFolder, this.config.rootDir);
|
const builder = new HtmlBuilder(reportFolder, this.config.rootDir);
|
||||||
|
const stats = builder.build(reports);
|
||||||
|
|
||||||
if (!process.env.CI && !process.env.PWTEST_SKIP_TEST_OUTPUT) {
|
if (!stats.ok && !process.env.CI && !process.env.PWTEST_SKIP_TEST_OUTPUT) {
|
||||||
const server = new HttpServer();
|
showHTMLReport(reportFolder);
|
||||||
server.routePrefix('/', (request, response) => {
|
} else {
|
||||||
let relativePath = new URL('http://localhost' + request.url).pathname;
|
|
||||||
if (relativePath === '/')
|
|
||||||
relativePath = '/index.html';
|
|
||||||
const absolutePath = path.join(reportFolder, ...relativePath.split('/'));
|
|
||||||
return server.serveFile(response, absolutePath);
|
|
||||||
});
|
|
||||||
const url = await server.start(9323);
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
|
||||||
console.log('');
|
console.log('');
|
||||||
open(url);
|
console.log('All tests passed. To open last HTML report run:');
|
||||||
process.on('SIGINT', () => process.exit(0));
|
console.log(colors.cyan(`
|
||||||
await new Promise(() => {});
|
npx playwright show-report
|
||||||
|
`));
|
||||||
|
console.log('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function htmlReportFolder(): string {
|
||||||
|
return path.resolve(process.cwd(), process.env[`PLAYWRIGHT_HTML_REPORT`] || 'playwright-report');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showHTMLReport(reportFolder: string | undefined) {
|
||||||
|
const folder = reportFolder || htmlReportFolder();
|
||||||
|
try {
|
||||||
|
assert(fs.statSync(folder).isDirectory());
|
||||||
|
} catch (e) {
|
||||||
|
console.log(colors.red(`No report found at "${folder}"`));
|
||||||
|
process.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const server = new HttpServer();
|
||||||
|
server.routePrefix('/', (request, response) => {
|
||||||
|
let relativePath = new URL('http://localhost' + request.url).pathname;
|
||||||
|
if (relativePath === '/')
|
||||||
|
relativePath = '/index.html';
|
||||||
|
const absolutePath = path.join(folder, ...relativePath.split('/'));
|
||||||
|
return server.serveFile(response, absolutePath);
|
||||||
|
});
|
||||||
|
const url = await server.start(9323);
|
||||||
|
console.log('');
|
||||||
|
console.log(colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||||
|
console.log('');
|
||||||
|
open(url);
|
||||||
|
process.on('SIGINT', () => process.exit(0));
|
||||||
|
await new Promise(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
class HtmlBuilder {
|
class HtmlBuilder {
|
||||||
private _reportFolder: string;
|
private _reportFolder: string;
|
||||||
private _tests = new Map<string, JsonTestCase>();
|
private _tests = new Map<string, JsonTestCase>();
|
||||||
private _rootDir: string;
|
private _rootDir: string;
|
||||||
private _dataFolder: string;
|
private _dataFolder: string;
|
||||||
|
|
||||||
constructor(rawReports: JsonReport[], outputDir: string, rootDir: string) {
|
constructor(outputDir: string, rootDir: string) {
|
||||||
this._rootDir = rootDir;
|
this._rootDir = rootDir;
|
||||||
this._reportFolder = path.resolve(process.cwd(), outputDir);
|
this._reportFolder = path.resolve(process.cwd(), outputDir);
|
||||||
this._dataFolder = path.join(this._reportFolder, 'data');
|
this._dataFolder = path.join(this._reportFolder, 'data');
|
||||||
|
}
|
||||||
|
|
||||||
|
build(rawReports: JsonReport[]): Stats {
|
||||||
fs.mkdirSync(this._dataFolder, { recursive: true });
|
fs.mkdirSync(this._dataFolder, { recursive: true });
|
||||||
|
|
||||||
// Copy app.
|
// Copy app.
|
||||||
|
|
@ -187,6 +216,7 @@ class HtmlBuilder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fs.writeFileSync(path.join(this._dataFolder, 'projects.json'), JSON.stringify(projects, undefined, 2));
|
fs.writeFileSync(path.join(this._dataFolder, 'projects.json'), JSON.stringify(projects, undefined, 2));
|
||||||
|
return projects.reduce((a, p) => addStats(a, p.stats), emptyStats());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createTestCase(test: JsonTestCase): TestCase {
|
private _createTestCase(test: JsonTestCase): TestCase {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue