feat(html): show number of filtered tests, update total time (#23743)
This commit is contained in:
parent
0358f6c434
commit
11770156eb
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TestCase, TestFile } from './types';
|
import type { FilteredStats, TestCase, TestFile, TestFileSummary } from './types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import './colors.css';
|
import './colors.css';
|
||||||
import './common.css';
|
import './common.css';
|
||||||
|
|
@ -47,6 +47,7 @@ export const ReportView: React.FC<{
|
||||||
const [filterText, setFilterText] = React.useState(searchParams.get('q') || '');
|
const [filterText, setFilterText] = React.useState(searchParams.get('q') || '');
|
||||||
|
|
||||||
const filter = React.useMemo(() => Filter.parse(filterText), [filterText]);
|
const filter = React.useMemo(() => Filter.parse(filterText), [filterText]);
|
||||||
|
const filteredStats = React.useMemo(() => computeStats(report?.json().files || [], filter), [report, filter]);
|
||||||
|
|
||||||
return <div className='htmlreport vbox px-4 pb-4'>
|
return <div className='htmlreport vbox px-4 pb-4'>
|
||||||
<main>
|
<main>
|
||||||
|
|
@ -59,7 +60,7 @@ export const ReportView: React.FC<{
|
||||||
expandedFiles={expandedFiles}
|
expandedFiles={expandedFiles}
|
||||||
setExpandedFiles={setExpandedFiles}
|
setExpandedFiles={setExpandedFiles}
|
||||||
projectNames={report?.json().projectNames || []}
|
projectNames={report?.json().projectNames || []}
|
||||||
stats={report?.json().stats || { duration: 0 }}
|
filteredStats={filteredStats}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route predicate={testCaseRoutePredicate}>
|
<Route predicate={testCaseRoutePredicate}>
|
||||||
|
|
@ -95,3 +96,17 @@ const TestCaseViewLoader: React.FC<{
|
||||||
}, [test, report, testId]);
|
}, [test, report, testId]);
|
||||||
return <TestCaseView projectNames={report.json().projectNames} test={test} anchor={anchor} run={run}></TestCaseView>;
|
return <TestCaseView projectNames={report.json().projectNames} test={test} anchor={anchor} run={run}></TestCaseView>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function computeStats(files: TestFileSummary[], filter: Filter): FilteredStats {
|
||||||
|
const stats: FilteredStats = {
|
||||||
|
total: 0,
|
||||||
|
duration: 0,
|
||||||
|
};
|
||||||
|
for (const file of files) {
|
||||||
|
const tests = file.tests.filter(t => filter.matches(t));
|
||||||
|
stats.total += tests.length;
|
||||||
|
for (const test of tests)
|
||||||
|
stats.duration += test.duration;
|
||||||
|
}
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||||
<LabelsClickView labels={labels(test)} />
|
<LabelsClickView labels={labels(test)} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
|
<span data-testid='test-duration' style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='test-file-details-row'>
|
<div className='test-file-details-row'>
|
||||||
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')} className='test-file-path-link'>
|
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')} className='test-file-path-link'>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { HTMLReport, TestFileSummary } from './types';
|
import type { FilteredStats, HTMLReport, TestFileSummary } from './types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import type { Filter } from './filter';
|
import type { Filter } from './filter';
|
||||||
import { TestFileView } from './testFileView';
|
import { TestFileView } from './testFileView';
|
||||||
|
|
@ -26,9 +26,9 @@ export const TestFilesView: React.FC<{
|
||||||
expandedFiles: Map<string, boolean>,
|
expandedFiles: Map<string, boolean>,
|
||||||
setExpandedFiles: (value: Map<string, boolean>) => void,
|
setExpandedFiles: (value: Map<string, boolean>) => void,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
stats: { duration: number },
|
filteredStats: FilteredStats,
|
||||||
projectNames: string[],
|
projectNames: string[],
|
||||||
}> = ({ report, filter, expandedFiles, setExpandedFiles, projectNames, stats }) => {
|
}> = ({ report, filter, expandedFiles, setExpandedFiles, projectNames, filteredStats }) => {
|
||||||
const filteredFiles = React.useMemo(() => {
|
const filteredFiles = React.useMemo(() => {
|
||||||
const result: { file: TestFileSummary, defaultExpanded: boolean }[] = [];
|
const result: { file: TestFileSummary, defaultExpanded: boolean }[] = [];
|
||||||
let visibleTests = 0;
|
let visibleTests = 0;
|
||||||
|
|
@ -43,8 +43,9 @@ export const TestFilesView: React.FC<{
|
||||||
return <>
|
return <>
|
||||||
<div className='p-2' style={{ display: 'flex' }}>
|
<div className='p-2' style={{ display: 'flex' }}>
|
||||||
{projectNames.length === 1 && !!projectNames[0] && <div data-testid="project-name" style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>}
|
{projectNames.length === 1 && !!projectNames[0] && <div data-testid="project-name" style={{ color: 'var(--color-fg-subtle)' }}>Project: {projectNames[0]}</div>}
|
||||||
|
{!filter.empty() && <div data-testid="filtered-tests-count" style={{ color: 'var(--color-fg-subtle)' }}>Filtered: {filteredStats.total}</div>}
|
||||||
<div style={{ flex: 'auto' }}></div>
|
<div style={{ flex: 'auto' }}></div>
|
||||||
<div data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(stats.duration)}</div>
|
<div data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)' }}>Total time: {msToString(filteredStats.duration)}</div>
|
||||||
</div>
|
</div>
|
||||||
{report && filteredFiles.map(({ file, defaultExpanded }) => {
|
{report && filteredFiles.map(({ file, defaultExpanded }) => {
|
||||||
return <TestFileView
|
return <TestFileView
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ export type Stats = {
|
||||||
duration: number;
|
duration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FilteredStats = {
|
||||||
|
total: number
|
||||||
|
duration: number,
|
||||||
|
};
|
||||||
|
|
||||||
export type Location = {
|
export type Location = {
|
||||||
file: string;
|
file: string;
|
||||||
line: number;
|
line: number;
|
||||||
|
|
|
||||||
|
|
@ -1316,6 +1316,12 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
||||||
|
|
||||||
await expect(regressionLabelButton).not.toBeVisible();
|
await expect(regressionLabelButton).not.toBeVisible();
|
||||||
|
|
||||||
|
{
|
||||||
|
const testDuration = await page.getByTestId('test-duration').textContent();
|
||||||
|
const totalDuration = await page.getByTestId('overall-duration').textContent();
|
||||||
|
expect(totalDuration).toBe('Total time: ' + testDuration);
|
||||||
|
}
|
||||||
|
|
||||||
await searchInput.clear();
|
await searchInput.clear();
|
||||||
|
|
||||||
await expect(regressionLabelButton).toBeVisible();
|
await expect(regressionLabelButton).toBeVisible();
|
||||||
|
|
@ -1330,6 +1336,12 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
||||||
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(0);
|
await expect(page.locator('.chip', { hasText: 'b.test.js' })).toHaveCount(0);
|
||||||
await expect(page.locator('.test-file-test .test-file-title')).toHaveText('Error Pages › @regression passes');
|
await expect(page.locator('.test-file-test .test-file-title')).toHaveText('Error Pages › @regression passes');
|
||||||
|
|
||||||
|
{
|
||||||
|
const testDuration = await page.getByTestId('test-duration').textContent();
|
||||||
|
const totalDuration = await page.getByTestId('overall-duration').textContent();
|
||||||
|
expect(totalDuration).toBe('Total time: ' + testDuration);
|
||||||
|
}
|
||||||
|
|
||||||
await searchInput.clear();
|
await searchInput.clear();
|
||||||
|
|
||||||
const tagWithDash = page.locator('.test-file-test', { has: page.getByText('Error Pages › @GCC-1508 passes', { exact: true }) }).locator('.label', { hasText: 'GCC-1508' });
|
const tagWithDash = page.locator('.test-file-test', { has: page.getByText('Error Pages › @GCC-1508 passes', { exact: true }) }).locator('.label', { hasText: 'GCC-1508' });
|
||||||
|
|
@ -1437,6 +1449,70 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
||||||
await expect(page).not.toHaveURL(/@regression/);
|
await expect(page).not.toHaveURL(/@regression/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('filter should update stats', async ({ runInlineTest, showReport, page }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.js': `
|
||||||
|
const { expect, test } = require('@playwright/test');
|
||||||
|
const names = ['one foo', 'two foo', 'three bar', 'four bar', 'five baz'];
|
||||||
|
names.forEach(name => {
|
||||||
|
test(name, async ({}) => {
|
||||||
|
expect(name).not.toContain('foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'b.test.js': `
|
||||||
|
const { expect, test } = require('@playwright/test');
|
||||||
|
const names = ['one foo', 'two foo', 'three bar', 'four bar', 'five baz'];
|
||||||
|
names.forEach(name => {
|
||||||
|
test(name, async ({}) => {
|
||||||
|
expect(name).not.toContain('one');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: 'dot,html' }, { PW_TEST_HTML_REPORT_OPEN: 'never' });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.passed).toBe(7);
|
||||||
|
expect(result.failed).toBe(3);
|
||||||
|
|
||||||
|
await showReport();
|
||||||
|
|
||||||
|
async function checkTotalDuration() {
|
||||||
|
let total = 0;
|
||||||
|
for (const text of await page.getByTestId('test-duration').allTextContents()) {
|
||||||
|
expect(text).toMatch(/\d+ms$/);
|
||||||
|
total += parseInt(text.substring(0, text.length - 2), 10);
|
||||||
|
}
|
||||||
|
const totalDuration = await page.getByTestId('overall-duration').textContent();
|
||||||
|
expect(totalDuration).toBe(`Total time: ${total}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchInput = page.locator('.subnav-search-input');
|
||||||
|
await expect(page.getByTestId('filtered-tests-count')).not.toBeVisible();
|
||||||
|
|
||||||
|
await searchInput.fill('s:failed');
|
||||||
|
|
||||||
|
await expect(page.getByTestId('filtered-tests-count')).toHaveText('Filtered: 3');
|
||||||
|
await checkTotalDuration();
|
||||||
|
await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('10');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('7');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('3');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('0');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('0');
|
||||||
|
|
||||||
|
await searchInput.clear();
|
||||||
|
await expect(page.getByTestId('filtered-tests-count')).not.toBeVisible();
|
||||||
|
|
||||||
|
await searchInput.fill('foo');
|
||||||
|
await expect(page.getByTestId('filtered-tests-count')).toHaveText('Filtered: 4');
|
||||||
|
await checkTotalDuration();
|
||||||
|
await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('10');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('7');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('3');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('0');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('0');
|
||||||
|
});
|
||||||
|
|
||||||
test('labels whould be applied together with status filter', async ({ runInlineTest, showReport, page }) => {
|
test('labels whould be applied together with status filter', async ({ runInlineTest, showReport, page }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.js': `
|
'a.test.js': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue