mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat: show warning when some report shards are missing (#20731)
When some of the report shards are missing still show the report but display an error in the status line:  #10437
This commit is contained in:
parent
bc74383480
commit
f10b29fd5e
@ -30,3 +30,7 @@
|
|||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-view-status-line {
|
||||||
|
padding-right: '10px'
|
||||||
|
}
|
||||||
|
@ -29,7 +29,8 @@ export const HeaderView: React.FC<React.PropsWithChildren<{
|
|||||||
filterText: string,
|
filterText: string,
|
||||||
setFilterText: (filterText: string) => void,
|
setFilterText: (filterText: string) => void,
|
||||||
projectNames: string[],
|
projectNames: string[],
|
||||||
}>> = ({ stats, filterText, setFilterText, projectNames }) => {
|
reportLoaderError?: string,
|
||||||
|
}>> = ({ stats, filterText, setFilterText, projectNames, reportLoaderError }) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
window.addEventListener('popstate', () => {
|
window.addEventListener('popstate', () => {
|
||||||
@ -57,9 +58,10 @@ export const HeaderView: React.FC<React.PropsWithChildren<{
|
|||||||
}}></input>
|
}}></input>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className='pt-2'>
|
{reportLoaderError && <div className='header-view-status-line pt-2' data-testid='loader-error' style={{ color: 'var(--color-danger-emphasis)', textAlign: 'right' }}>{reportLoaderError}</div>}
|
||||||
|
<div className='header-view-status-line pt-2'>
|
||||||
{projectNames.length === 1 && <span data-testid="project-name" style={{ color: 'var(--color-fg-subtle)', float: 'left' }}>Project: {projectNames[0]}</span>}
|
{projectNames.length === 1 && <span data-testid="project-name" style={{ color: 'var(--color-fg-subtle)', float: 'left' }}>Project: {projectNames[0]}</span>}
|
||||||
<span data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)', paddingRight: '10px', float: 'right' }}>Total time: {msToString(stats.duration)}</span>
|
<span data-testid="overall-duration" style={{ color: 'var(--color-fg-subtle)', float: 'right' }}>Total time: {msToString(stats.duration)}</span>
|
||||||
</div>
|
</div>
|
||||||
</>);
|
</>);
|
||||||
};
|
};
|
||||||
|
@ -49,6 +49,7 @@ window.onload = () => {
|
|||||||
class ZipReport implements LoadedReport {
|
class ZipReport implements LoadedReport {
|
||||||
private _entries = new Map<string, zip.Entry>();
|
private _entries = new Map<string, zip.Entry>();
|
||||||
private _json!: HTMLReport;
|
private _json!: HTMLReport;
|
||||||
|
private _loaderError: string | undefined;
|
||||||
|
|
||||||
async loadFromBase64(reportBase64: string) {
|
async loadFromBase64(reportBase64: string) {
|
||||||
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(reportBase64), { useWebWorkers: false }) as zip.ZipReader;
|
const zipReader = new zipjs.ZipReader(new zipjs.Data64URIReader(reportBase64), { useWebWorkers: false }) as zip.ZipReader;
|
||||||
@ -62,9 +63,17 @@ class ZipReport implements LoadedReport {
|
|||||||
const paddedNumber = String(i + 1).padStart(paddedLen, '0');
|
const paddedNumber = String(i + 1).padStart(paddedLen, '0');
|
||||||
const fileName = `report-${paddedNumber}-of-${shardTotal}.zip`;
|
const fileName = `report-${paddedNumber}-of-${shardTotal}.zip`;
|
||||||
const zipReader = new zipjs.ZipReader(new zipjs.HttpReader(fileName), { useWebWorkers: false }) as zip.ZipReader;
|
const zipReader = new zipjs.ZipReader(new zipjs.HttpReader(fileName), { useWebWorkers: false }) as zip.ZipReader;
|
||||||
readers.push(this._readReportAndTestEntries(zipReader));
|
readers.push(this._readReportAndTestEntries(zipReader).catch(e => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(e);
|
||||||
|
return undefined;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
this._json = mergeReports(await Promise.all(readers));
|
const reportsOrErrors = await Promise.all(readers);
|
||||||
|
const reports = reportsOrErrors.filter(Boolean) as HTMLReport[];
|
||||||
|
if (reports.length < readers.length)
|
||||||
|
this._loaderError = `Only ${reports.length} of ${shardTotal} report shards loaded`;
|
||||||
|
this._json = mergeReports(reports);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _readReportAndTestEntries(zipReader: zip.ZipReader): Promise<HTMLReport> {
|
private async _readReportAndTestEntries(zipReader: zip.ZipReader): Promise<HTMLReport> {
|
||||||
@ -83,5 +92,9 @@ class ZipReport implements LoadedReport {
|
|||||||
await reportEntry!.getData!(writer);
|
await reportEntry!.getData!(writer);
|
||||||
return JSON.parse(await writer.getData());
|
return JSON.parse(await writer.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loaderError(): string | undefined {
|
||||||
|
return this._loaderError;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,4 +19,5 @@ import type { HTMLReport } from './types';
|
|||||||
export interface LoadedReport {
|
export interface LoadedReport {
|
||||||
json(): HTMLReport;
|
json(): HTMLReport;
|
||||||
entry(name: string): Promise<Object | undefined>;
|
entry(name: string): Promise<Object | undefined>;
|
||||||
|
loaderError(): string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export const ReportView: React.FC<{
|
|||||||
|
|
||||||
return <div className='htmlreport vbox px-4 pb-4'>
|
return <div className='htmlreport vbox px-4 pb-4'>
|
||||||
<main>
|
<main>
|
||||||
{report?.json() && <HeaderView stats={report.json().stats} filterText={filterText} setFilterText={setFilterText} projectNames={report.json().projectNames}></HeaderView>}
|
{report?.json() && <HeaderView stats={report.json().stats} filterText={filterText} setFilterText={setFilterText} projectNames={report.json().projectNames} reportLoaderError={report.loaderError()}></HeaderView>}
|
||||||
{report?.json().metadata && <MetadataView {...report?.json().metadata as Metainfo} />}
|
{report?.json().metadata && <MetadataView {...report?.json().metadata as Metainfo} />}
|
||||||
<Route predicate={testFilesRoutePredicate}>
|
<Route predicate={testFilesRoutePredicate}>
|
||||||
<TestFilesView report={report?.json()} filter={filter} expandedFiles={expandedFiles} setExpandedFiles={setExpandedFiles}></TestFilesView>
|
<TestFilesView report={report?.json()} filter={filter} expandedFiles={expandedFiles} setExpandedFiles={setExpandedFiles}></TestFilesView>
|
||||||
|
@ -1039,3 +1039,54 @@ test('should pad report numbers with zeros', async ({ runInlineTest, showReport,
|
|||||||
`report-003-of-100.zip`
|
`report-003-of-100.zip`
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show report with missing shards', async ({ runInlineTest, showReport, page }, testInfo) => {
|
||||||
|
const totalShards = 15;
|
||||||
|
|
||||||
|
const testFiles = {};
|
||||||
|
for (let i = 0; i < totalShards; i++) {
|
||||||
|
testFiles[`a-${String(i).padStart(2, '0')}.spec.ts`] = `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('passes', async ({}) => { expect(2).toBe(2); });
|
||||||
|
test('fails', async ({}) => { expect(1).toBe(2); });
|
||||||
|
test('skipped', async ({}) => { test.skip('Does not work') });
|
||||||
|
test('flaky', async ({}, testInfo) => { expect(testInfo.retry).toBe(1); });
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allReports = testInfo.outputPath(`aggregated-report`);
|
||||||
|
await fs.promises.mkdir(allReports, { recursive: true });
|
||||||
|
|
||||||
|
// Run tests in 2 out of 15 shards.
|
||||||
|
for (const i of [10, 13]) {
|
||||||
|
const result = await runInlineTest(testFiles,
|
||||||
|
{ 'reporter': 'dot,html', 'retries': 1, 'shard': `${i}/${totalShards}` },
|
||||||
|
{ PW_TEST_HTML_REPORT_OPEN: 'never' },
|
||||||
|
{ usesCustomReporters: true });
|
||||||
|
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
const files = await fs.promises.readdir(testInfo.outputPath(`playwright-report`));
|
||||||
|
expect(new Set(files)).toEqual(new Set([
|
||||||
|
'index.html',
|
||||||
|
`report-${i}-of-${totalShards}.zip`
|
||||||
|
]));
|
||||||
|
await Promise.all(files.map(name => fs.promises.rename(testInfo.outputPath(`playwright-report/${name}`), `${allReports}/${name}`)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show aggregated report
|
||||||
|
await showReport(allReports);
|
||||||
|
|
||||||
|
await expect(page.getByText('Only 2 of 15 report shards loaded')).toBeVisible();
|
||||||
|
|
||||||
|
await expect(page.locator('.subnav-item:has-text("All") .counter')).toHaveText('8');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('2');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('2');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Flaky") .counter')).toHaveText('2');
|
||||||
|
await expect(page.locator('.subnav-item:has-text("Skipped") .counter')).toHaveText('2');
|
||||||
|
|
||||||
|
await expect(page.locator('.test-file-test-outcome-unexpected >> text=fails')).toHaveCount(2);
|
||||||
|
await expect(page.locator('.test-file-test-outcome-flaky >> text=flaky')).toHaveCount(2);
|
||||||
|
await expect(page.locator('.test-file-test-outcome-expected >> text=passes')).toHaveCount(2);
|
||||||
|
await expect(page.locator('.test-file-test-outcome-skipped >> text=skipped')).toHaveCount(2);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user