mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-11 12:33:45 +03:00
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.
|
||||
*/
|
||||
|
||||
import type { TestCase, TestFile } from './types';
|
||||
import type { FilteredStats, TestCase, TestFile, TestFileSummary } from './types';
|
||||
import * as React from 'react';
|
||||
import './colors.css';
|
||||
import './common.css';
|
||||
@ -47,6 +47,7 @@ export const ReportView: React.FC<{
|
||||
const [filterText, setFilterText] = React.useState(searchParams.get('q') || '');
|
||||
|
||||
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'>
|
||||
<main>
|
||||
@ -59,7 +60,7 @@ export const ReportView: React.FC<{
|
||||
expandedFiles={expandedFiles}
|
||||
setExpandedFiles={setExpandedFiles}
|
||||
projectNames={report?.json().projectNames || []}
|
||||
stats={report?.json().stats || { duration: 0 }}
|
||||
filteredStats={filteredStats}
|
||||
/>
|
||||
</Route>
|
||||
<Route predicate={testCaseRoutePredicate}>
|
||||
@ -95,3 +96,17 @@ const TestCaseViewLoader: React.FC<{
|
||||
}, [test, report, testId]);
|
||||
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)} />
|
||||
</span>
|
||||
</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 className='test-file-details-row'>
|
||||
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')} className='test-file-path-link'>
|
||||
|
@ -14,7 +14,7 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { HTMLReport, TestFileSummary } from './types';
|
||||
import type { FilteredStats, HTMLReport, TestFileSummary } from './types';
|
||||
import * as React from 'react';
|
||||
import type { Filter } from './filter';
|
||||
import { TestFileView } from './testFileView';
|
||||
@ -26,9 +26,9 @@ export const TestFilesView: React.FC<{
|
||||
expandedFiles: Map<string, boolean>,
|
||||
setExpandedFiles: (value: Map<string, boolean>) => void,
|
||||
filter: Filter,
|
||||
stats: { duration: number },
|
||||
filteredStats: FilteredStats,
|
||||
projectNames: string[],
|
||||
}> = ({ report, filter, expandedFiles, setExpandedFiles, projectNames, stats }) => {
|
||||
}> = ({ report, filter, expandedFiles, setExpandedFiles, projectNames, filteredStats }) => {
|
||||
const filteredFiles = React.useMemo(() => {
|
||||
const result: { file: TestFileSummary, defaultExpanded: boolean }[] = [];
|
||||
let visibleTests = 0;
|
||||
@ -43,8 +43,9 @@ export const TestFilesView: React.FC<{
|
||||
return <>
|
||||
<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>}
|
||||
{!filter.empty() && <div data-testid="filtered-tests-count" style={{ color: 'var(--color-fg-subtle)' }}>Filtered: {filteredStats.total}</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>
|
||||
{report && filteredFiles.map(({ file, defaultExpanded }) => {
|
||||
return <TestFileView
|
||||
|
@ -26,6 +26,11 @@ export type Stats = {
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export type FilteredStats = {
|
||||
total: number
|
||||
duration: number,
|
||||
};
|
||||
|
||||
export type Location = {
|
||||
file: string;
|
||||
line: number;
|
||||
|
@ -1316,6 +1316,12 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
||||
|
||||
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 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('.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();
|
||||
|
||||
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/);
|
||||
});
|
||||
|
||||
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 }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.js': `
|
||||
|
Loading…
Reference in New Issue
Block a user