chore: few html report tweaks (#8631)
This commit is contained in:
parent
e0e4b48df6
commit
6b371f83f2
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue