chore: few html report tweaks (#8631)

This commit is contained in:
Pavel Feldman 2021-09-01 14:35:11 -07:00 committed by GitHub
parent e0e4b48df6
commit 6b371f83f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 66 deletions

View file

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
*/ */
.suite-tree { .suite-tree-column {
line-height: 18px; line-height: 18px;
flex: auto; flex: auto;
overflow: auto; overflow: auto;
@ -31,28 +31,19 @@
min-height: 18px; min-height: 18px;
} }
.suite-tree .tree-item-title:not(.selected):hover { .suite-tree-column .tree-item-title:not(.selected):hover {
background-color: #e8e8e8; background-color: #e8e8e8;
} }
.suite-tree .tree-item-title.selected { .suite-tree-column .tree-item-title.selected {
background-color: #0060c0; background-color: #0060c0;
color: white; color: white;
} }
.suite-tree .tree-item-title.selected * { .suite-tree-column .tree-item-title.selected * {
color: white !important; color: white !important;
} }
.test-case {
flex: auto;
}
.test-case .tab-content {
overflow: auto;
}
.error-message { .error-message {
white-space: pre; white-space: pre;
font-family: monospace; font-family: monospace;
@ -88,6 +79,8 @@
.test-result { .test-result {
flex: auto; flex: auto;
display: flex; display: flex;
flex-direction: column;
padding-right: 8px;
} }
.test-overview-title { .test-overview-title {
@ -109,11 +102,6 @@
max-height: 500px; max-height: 500px;
} }
.test-result .tabbed-pane {
margin-top: 50px;
width: 550px;
}
.image-preview { .image-preview {
position: relative; position: relative;
display: flex; display: flex;
@ -137,15 +125,55 @@
margin-left: 24px; margin-left: 24px;
} }
.steps-tree .tree-item-title:not(.selected):hover { .test-result .tree-item-title:not(.selected):hover {
background-color: #e8e8e8; background-color: #e8e8e8;
} }
.steps-tree .tree-item-title.selected { .test-result .tree-item-title.selected {
background-color: #0060c0; background-color: #0060c0;
color: white; color: white;
} }
.steps-tree .tree-item-title.selected * { .test-result .tree-item-title.selected * {
color: white !important; color: white !important;
} }
.suite-tree-column .tab-strip,
.test-case-column .tab-strip {
border: none;
box-shadow: none;
background-color: transparent;
}
.suite-tree-column .tab-element,
.test-case-column .tab-element {
border: none;
text-transform: uppercase;
font-weight: bold;
font-size: 11px;
color: #aaa;
}
.suite-tree-column .tab-element.selected,
.test-case-column .tab-element.selected {
color: #555;
}
.test-case-title {
flex: none;
display: flex;
align-items: center;
padding: 10px;
font-size: 18px;
cursor: pointer;
}
.test-case-location {
flex: none;
display: flex;
align-items: center;
padding: 0 10px 10px;
color: var(--blue);
text-decoration: underline;
cursor: pointer;
}

View file

@ -53,7 +53,7 @@ export const Report: React.FC = () => {
return <div className='hbox'> return <div className='hbox'>
<SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}> <SplitView sidebarSize={300} orientation='horizontal' sidebarIsFirst={true}>
<TestCaseView test={selectedTest}></TestCaseView> <TestCaseView test={selectedTest}></TestCaseView>
<div className='suite-tree'> <div className='suite-tree-column'>
<div className='tab-strip'>{ <div className='tab-strip'>{
(['Failing', 'All'] as Filter[]).map(item => { (['Failing', 'All'] as Filter[]).map(item => {
const selected = item === filter; const selected = item === filter;
@ -145,54 +145,55 @@ const TestTreeItem: React.FC<{
}; };
const TestCaseView: React.FC<{ const TestCaseView: React.FC<{
test?: JsonTestCase, test: JsonTestCase | undefined,
}> = ({ test }) => { }> = ({ test }) => {
const [selectedTab, setSelectedTab] = React.useState<string>('0'); const [selectedResultIndex, setSelectedResultIndex] = React.useState(0);
return <div className="test-case vbox"> const [selectedStep, setSelectedStep] = React.useState<JsonTestStep | undefined>(undefined);
{ !test && <div className='tab-strip' />} const result = test?.results[selectedResultIndex];
{ test && <TabbedPane tabs={ return <SplitView sidebarSize={500} orientation='horizontal' sidebarIsFirst={true}>
test?.results.map((result, index) => ({ <div className='test-details-column vbox'>
id: String(index),
title: <div style={{ display: 'flex', alignItems: 'center' }}>{statusIcon(result.status)} {retryLabel(index)}</div>,
render: () => <TestOverview test={test} result={result}></TestOverview>
})) || []} selectedTab={selectedTab} setSelectedTab={setSelectedTab} /> }
</div>;
};
const TestOverview: React.FC<{
test: JsonTestCase,
result: JsonTestResult,
}> = ({ test, result }) => {
const [selectedStep, setSelectedStep] = React.useState<JsonTestStep | undefined>();
return <div className='test-result'>
<SplitView sidebarSize={500} orientation='horizontal' sidebarIsFirst={true}>
{!selectedStep && <TestResultDetails test={test} result={result} />} {!selectedStep && <TestResultDetails test={test} result={result} />}
{!!selectedStep && <TestStepDetails test={test} result={result} step={selectedStep}/>} {!!selectedStep && <TestStepDetails test={test} result={result} step={selectedStep}/>}
<div className='vbox steps-tree'> </div>
<TreeItem <div className='test-case-column vbox'>
title={<div className='test-overview-title'>{renderLocation(test.location, true)} {test?.title} ({msToString(result.duration)})</div>} { test && <div className='test-case-title' onClick={() => setSelectedStep(undefined)}>{test?.title}</div> }
depth={0} { test && <div className='test-case-location' onClick={() => setSelectedStep(undefined)}>{renderLocation(test.location, true)}</div> }
key='test' { test && <TabbedPane tabs={
onClick={() => setSelectedStep(undefined)}> test?.results.map((result, index) => ({
</TreeItem> id: String(index),
{result.steps.map((step, i) => <StepTreeItem key={i} step={step} depth={0} selectedStep={selectedStep} setSelectedStep={setSelectedStep}></StepTreeItem>)} title: <div style={{ display: 'flex', alignItems: 'center' }}>{statusIcon(result.status)} {retryLabel(index)}</div>,
</div> render: () => <TestResultView test={test} result={result} selectedStep={selectedStep} setSelectedStep={setSelectedStep}></TestResultView>
</SplitView> })) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
</div>
</SplitView>;
};
const TestResultView: React.FC<{
test: JsonTestCase,
result: JsonTestResult,
selectedStep: JsonTestStep | undefined,
setSelectedStep: (step: JsonTestStep | undefined) => void;
}> = ({ test, result, selectedStep, setSelectedStep }) => {
return <div className='test-result'>
{result.steps.map((step, i) => <StepTreeItem key={i} step={step} depth={0} selectedStep={selectedStep} setSelectedStep={setSelectedStep}></StepTreeItem>)}
</div>; </div>;
}; };
const TestResultDetails: React.FC<{ const TestResultDetails: React.FC<{
test: JsonTestCase, test: JsonTestCase | undefined,
result: JsonTestResult, result: JsonTestResult | undefined,
}> = ({ test, result }) => { }> = ({ test, result }) => {
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 screenshots = result.attachments.filter(a => a.name === 'screenshot'); const attachments = result?.attachments || [];
const video = result.attachments.filter(a => a.name === 'video'); const screenshots = attachments.filter(a => a.name === 'screenshot');
for (const a of result.attachments) const video = attachments.filter(a => a.name === 'video');
for (const a of attachments)
attachmentsMap.set(a.name, a); attachmentsMap.set(a.name, a);
return { attachmentsMap, screenshots, video }; return { attachmentsMap, screenshots, video };
}, [ result ]); }, [ result ]);
if (!result)
return <div></div>;
return <div> return <div>
{result.failureSnippet && <div className='test-overview-title'>Test error</div>} {result.failureSnippet && <div className='test-overview-title'>Test error</div>}
{result.failureSnippet && <div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(result.failureSnippet.trim())) }}></div>} {result.failureSnippet && <div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(result.failureSnippet.trim())) }}></div>}
@ -211,14 +212,14 @@ const TestResultDetails: React.FC<{
}; };
const TestStepDetails: React.FC<{ const TestStepDetails: React.FC<{
test: JsonTestCase, test: JsonTestCase | undefined,
result: JsonTestResult, result: JsonTestResult | undefined,
step: JsonTestStep, step: JsonTestStep | undefined,
}> = ({ test, result, step }) => { }> = ({ test, result, step }) => {
const [source, setSource] = React.useState<SourceProps>({ text: '', language: 'javascript' }); const [source, setSource] = React.useState<SourceProps>({ text: '', language: 'javascript' });
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
const frame = step.stack?.[0]; const frame = step?.stack?.[0];
if (!frame || !frame.sha1) if (!frame || !frame.sha1)
return; return;
try { try {
@ -230,10 +231,25 @@ const TestStepDetails: React.FC<{
} }
})(); })();
}, [step]); }, [step]);
const [selectedTab, setSelectedTab] = React.useState('log');
return <div className='vbox'> return <div className='vbox'>
{step.failureSnippet && <div className='test-overview-title'>Step error</div>} <TabbedPane selectedTab={selectedTab} setSelectedTab={setSelectedTab} tabs={[
{step.failureSnippet && <div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(step.failureSnippet.trim())) }}></div>} {
<Source text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></Source> id: 'log',
title: 'Log',
render: () => <div></div>
},
{
id: 'errors',
title: 'Errors',
render: () => <div className='error-message' dangerouslySetInnerHTML={{ __html: new ansi2html({ colors: ansiColors }).toHtml(escapeHTML(step?.failureSnippet?.trim() || '')) }}></div>
},
{
id: 'source',
title: 'Source',
render: () => <Source text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></Source>
}
]}></TabbedPane>
</div>; </div>;
}; };
@ -243,7 +259,7 @@ const StepTreeItem: React.FC<{
selectedStep?: JsonTestStep, selectedStep?: JsonTestStep,
setSelectedStep: (step: JsonTestStep | undefined) => void; setSelectedStep: (step: JsonTestStep | undefined) => void;
}> = ({ step, depth, selectedStep, setSelectedStep }) => { }> = ({ step, depth, selectedStep, setSelectedStep }) => {
return <TreeItem title={<div style={{ display: 'flex', alignItems: 'center', flex: 'auto', maxWidth: 430 }}> return <TreeItem title={<div style={{ display: 'flex', alignItems: 'center', flex: 'auto' }}>
{testStepStatusIcon(step)} {testStepStatusIcon(step)}
<span style={{ whiteSpace: 'pre' }}>{step.preview || step.title}</span> <span style={{ whiteSpace: 'pre' }}>{step.preview || step.title}</span>
<div style={{ flex: 'auto' }}></div> <div style={{ flex: 'auto' }}></div>
@ -286,7 +302,7 @@ export const ImageDiff: React.FunctionComponent<{
export const AttachmentLink: React.FunctionComponent<{ export const AttachmentLink: React.FunctionComponent<{
attachment: JsonAttachment, attachment: JsonAttachment,
}> = ({ attachment }) => { }> = ({ attachment }) => {
return <TreeItem title={<div style={{ display: 'flex', alignItems: 'center', flex: 'auto', maxWidth: 430 }}> return <TreeItem title={<div style={{ display: 'flex', alignItems: 'center', flex: 'auto' }}>
<span className={'codicon codicon-cloud-download'}></span> <span className={'codicon codicon-cloud-download'}></span>
{attachment.sha1 && <a href={'resources/' + attachment.sha1} target='_blank'>{attachment.name}</a>} {attachment.sha1 && <a href={'resources/' + attachment.sha1} target='_blank'>{attachment.name}</a>}
{attachment.body && <span>{attachment.name}</span>} {attachment.body && <span>{attachment.name}</span>}