feat(html reporter): show test source (#8643)
This commit is contained in:
parent
42e44f888b
commit
798f894f47
|
|
@ -22,7 +22,7 @@ import { formatError, formatResultFailure } from './base';
|
||||||
import { serializePatterns, toPosixPath } from './json';
|
import { serializePatterns, toPosixPath } from './json';
|
||||||
|
|
||||||
export type JsonStats = { expected: number, unexpected: number, flaky: number, skipped: number };
|
export type JsonStats = { expected: number, unexpected: number, flaky: number, skipped: number };
|
||||||
export type JsonLocation = Location;
|
export type JsonLocation = Location & { sha1?: string };
|
||||||
export type JsonStackFrame = { file: string, line: number, column: number, sha1?: string };
|
export type JsonStackFrame = { file: string, line: number, column: number, sha1?: string };
|
||||||
|
|
||||||
export type JsonConfig = Omit<FullConfig, 'projects'> & {
|
export type JsonConfig = Omit<FullConfig, 'projects'> & {
|
||||||
|
|
@ -158,13 +158,14 @@ class HtmlReporter {
|
||||||
fs.writeFileSync(path.join(this._reportFolder, 'report.json'), JSON.stringify(output));
|
fs.writeFileSync(path.join(this._reportFolder, 'report.json'), JSON.stringify(output));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _relativeLocation(location: Location | undefined): Location {
|
private _relativeLocation(location: Location | undefined): JsonLocation {
|
||||||
if (!location)
|
if (!location)
|
||||||
return { file: '', line: 0, column: 0 };
|
return { file: '', line: 0, column: 0 };
|
||||||
return {
|
return {
|
||||||
file: toPosixPath(path.relative(this.config.rootDir, location.file)),
|
file: toPosixPath(path.relative(this.config.rootDir, location.file)),
|
||||||
line: location.line,
|
line: location.line,
|
||||||
column: location.column,
|
column: location.column,
|
||||||
|
sha1: this._sourceProcessor.copySourceFile(location.file),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,7 +313,7 @@ class SourceProcessor {
|
||||||
return { stack: frames, preview };
|
return { stack: frames, preview };
|
||||||
}
|
}
|
||||||
|
|
||||||
private copySourceFile(file: string): string | undefined {
|
copySourceFile(file: string): string | undefined {
|
||||||
let sha1: string | undefined;
|
let sha1: string | undefined;
|
||||||
if (this.sha1Cache.has(file)) {
|
if (this.sha1Cache.has(file)) {
|
||||||
sha1 = this.sha1Cache.get(file);
|
sha1 = this.sha1Cache.get(file);
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
flex: none;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icon {
|
.status-icon {
|
||||||
|
|
@ -177,3 +177,7 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.test-details-column {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,21 @@ const TestResultDetails: React.FC<{
|
||||||
test: JsonTestCase | undefined,
|
test: JsonTestCase | undefined,
|
||||||
result: JsonTestResult | undefined,
|
result: JsonTestResult | undefined,
|
||||||
}> = ({ test, result }) => {
|
}> = ({ test, result }) => {
|
||||||
|
const [selectedTab, setSelectedTab] = React.useState('errors');
|
||||||
|
const [source, setSource] = React.useState<SourceProps>({ text: '', language: 'javascript' });
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (!test || !test.location.sha1)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const response = await fetch('resources/' + test.location.sha1);
|
||||||
|
const text = await response.text();
|
||||||
|
setSource({ text, language: 'javascript', highlight: [{ line: test.location.line, type: 'paused' }], revealLine: test.location.line });
|
||||||
|
} catch (e) {
|
||||||
|
setSource({ text: '', language: 'javascript' });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [test]);
|
||||||
const { screenshots, video, attachmentsMap } = React.useMemo(() => {
|
const { screenshots, video, attachmentsMap } = React.useMemo(() => {
|
||||||
const attachmentsMap = new Map<string, JsonAttachment>();
|
const attachmentsMap = new Map<string, JsonAttachment>();
|
||||||
const attachments = result?.attachments || [];
|
const attachments = result?.attachments || [];
|
||||||
|
|
@ -194,20 +209,40 @@ const TestResultDetails: React.FC<{
|
||||||
}, [ result ]);
|
}, [ result ]);
|
||||||
if (!result)
|
if (!result)
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
return <div>
|
return <div className='vbox'>
|
||||||
{result.failureSnippet && <div className='test-overview-title'>Test error</div>}
|
<TabbedPane selectedTab={selectedTab} setSelectedTab={setSelectedTab} tabs={[
|
||||||
{result.failureSnippet && <div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(result.failureSnippet.trim())) }}></div>}
|
{
|
||||||
{attachmentsMap.has('expected') && attachmentsMap.has('actual') && <ImageDiff actual={attachmentsMap.get('actual')!} expected={attachmentsMap.get('expected')!} diff={attachmentsMap.get('diff')}></ImageDiff>}
|
id: 'errors',
|
||||||
{!!screenshots.length && <div className='test-overview-title'>Screenshots</div>}
|
title: 'Errors',
|
||||||
{screenshots.map(a => <div className='image-preview'><img src={'resources/' + a.sha1} /></div>)}
|
render: () => {
|
||||||
{!!video.length && <div className='test-overview-title'>Video</div>}
|
return <div style={{ overflow: 'auto' }}>
|
||||||
{video.map(a => <div className='image-preview'>
|
<div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(result.failureSnippet?.trim() || '')) }}></div>
|
||||||
<video controls>
|
{attachmentsMap.has('expected') && attachmentsMap.has('actual') && <ImageDiff actual={attachmentsMap.get('actual')!} expected={attachmentsMap.get('expected')!} diff={attachmentsMap.get('diff')}></ImageDiff>}
|
||||||
<source src={'resources/' + a.sha1} type={a.contentType}/>
|
</div>;
|
||||||
</video>
|
}
|
||||||
</div>)}
|
},
|
||||||
{!!result.attachments && <div className='test-overview-title'>Attachments</div>}
|
{
|
||||||
{result.attachments.map(a => <AttachmentLink attachment={a}></AttachmentLink>)}
|
id: 'results',
|
||||||
|
title: 'Results',
|
||||||
|
render: () => {
|
||||||
|
return <div style={{ overflow: 'auto' }}>
|
||||||
|
{screenshots.map(a => <div className='image-preview'><img src={'resources/' + a.sha1} /></div>)}
|
||||||
|
{video.map(a => <div className='image-preview'>
|
||||||
|
<video controls>
|
||||||
|
<source src={'resources/' + a.sha1} type={a.contentType}/>
|
||||||
|
</video>
|
||||||
|
</div>)}
|
||||||
|
{!!result.attachments && <div className='test-overview-title'>Attachments</div>}
|
||||||
|
{result.attachments.map(a => <AttachmentLink attachment={a}></AttachmentLink>)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'source',
|
||||||
|
title: 'Source',
|
||||||
|
render: () => <Source text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></Source>
|
||||||
|
}
|
||||||
|
]}></TabbedPane>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue