-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(html): link from attachment step to attachment #33267
base: main
Are you sure you want to change the base?
Changes from 11 commits
8afe8f6
c89488a
0afc545
3730900
dcf885f
579fbd2
aa2beda
a30c651
1fbb66d
4276d45
00d3b41
78120bb
fc90c1f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,10 +25,15 @@ import { statusIcon } from './statusIcon'; | |
import type { ImageDiff } from '@web/shared/imageDiffView'; | ||
import { ImageDiffView } from '@web/shared/imageDiffView'; | ||
import { TestErrorView, TestScreenshotErrorView } from './testErrorView'; | ||
import * as icons from './icons'; | ||
import './testResultView.css'; | ||
|
||
function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] { | ||
const snapshotNameToImageDiff = new Map<string, ImageDiff>(); | ||
interface ImageDiffWithAnchors extends ImageDiff { | ||
anchors: string[]; | ||
} | ||
|
||
function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiffWithAnchors[] { | ||
const snapshotNameToImageDiff = new Map<string, ImageDiffWithAnchors>(); | ||
for (const attachment of screenshots) { | ||
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); | ||
if (!match) | ||
|
@@ -37,9 +42,10 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] { | |
const snapshotName = name + extension; | ||
let imageDiff = snapshotNameToImageDiff.get(snapshotName); | ||
if (!imageDiff) { | ||
imageDiff = { name: snapshotName }; | ||
imageDiff = { name: snapshotName, anchors: [`attachment-${name}`] }; | ||
snapshotNameToImageDiff.set(snapshotName, imageDiff); | ||
} | ||
imageDiff.anchors.push(`attachment-${attachment.name}`); | ||
if (category === 'actual') | ||
imageDiff.actual = { attachment }; | ||
if (category === 'expected') | ||
|
@@ -64,20 +70,22 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] { | |
export const TestResultView: React.FC<{ | ||
test: TestCase, | ||
result: TestResult, | ||
}> = ({ result }) => { | ||
const { screenshots, videos, traces, otherAttachments, diffs, errors, htmls } = React.useMemo(() => { | ||
}> = ({ test, result }) => { | ||
const { screenshots, videos, traces, otherAttachments, diffs, errors } = React.useMemo(() => { | ||
const attachments = result?.attachments || []; | ||
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); | ||
const videos = attachments.filter(a => a.contentType.startsWith('video/')); | ||
const traces = attachments.filter(a => a.name === 'trace'); | ||
const htmls = attachments.filter(a => a.contentType.startsWith('text/html')); | ||
const otherAttachments = new Set<TestAttachment>(attachments); | ||
[...screenshots, ...videos, ...traces, ...htmls].forEach(a => otherAttachments.delete(a)); | ||
[...screenshots, ...videos, ...traces].forEach(a => otherAttachments.delete(a)); | ||
const diffs = groupImageDiffs(screenshots); | ||
const errors = classifyErrors(result.errors, diffs); | ||
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, htmls }; | ||
return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors }; | ||
}, [result]); | ||
|
||
const screenshotAnchor = React.useMemo(() => screenshots.map(a => `attachment-${a.name}`), [screenshots]); | ||
const otherAttachmentsAnchor = React.useMemo(() => [...otherAttachments].map(a => `attachment-${a.name}`), [otherAttachments]); | ||
|
||
return <div className='test-result'> | ||
{!!errors.length && <AutoChip header='Errors'> | ||
{errors.map((error, index) => { | ||
|
@@ -87,18 +95,18 @@ export const TestResultView: React.FC<{ | |
})} | ||
</AutoChip>} | ||
{!!result.steps.length && <AutoChip header='Test Steps'> | ||
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} depth={0}></StepTreeItem>)} | ||
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} result={result} test={test} depth={0}/>)} | ||
</AutoChip>} | ||
|
||
{diffs.map((diff, index) => | ||
<Anchor key={`diff-${index}`} id={`diff-${index}`}> | ||
<AutoChip dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} revealOnAnchorId={`diff-${index}`}> | ||
<Anchor key={`diff-${index}`} id={diff.anchors}> | ||
<AutoChip dataTestId='test-results-image-diff' header={`Image mismatch: ${diff.name}`} revealOnAnchorId={diff.anchors}> | ||
<ImageDiffView diff={diff}/> | ||
</AutoChip> | ||
</Anchor> | ||
)} | ||
|
||
{!!screenshots.length && <AutoChip header='Screenshots'> | ||
{!!screenshots.length && <Anchor id={screenshotAnchor}><AutoChip header='Screenshots' revealOnAnchorId={screenshotAnchor}> | ||
{screenshots.map((a, i) => { | ||
return <div key={`screenshot-${i}`}> | ||
<a href={a.path}> | ||
|
@@ -107,9 +115,9 @@ export const TestResultView: React.FC<{ | |
<AttachmentLink attachment={a}></AttachmentLink> | ||
</div>; | ||
})} | ||
</AutoChip>} | ||
</AutoChip></Anchor>} | ||
|
||
{!!traces.length && <Anchor id='traces'><AutoChip header='Traces' revealOnAnchorId='traces'> | ||
{!!traces.length && <Anchor id='attachment-trace'><AutoChip header='Traces' revealOnAnchorId='attachment-trace'> | ||
{<div> | ||
<a href={generateTraceUrl(traces)}> | ||
<img className='screenshot' src={traceImage} style={{ width: 192, height: 117, marginLeft: 20 }} /> | ||
|
@@ -118,7 +126,7 @@ export const TestResultView: React.FC<{ | |
</div>} | ||
</AutoChip></Anchor>} | ||
|
||
{!!videos.length && <Anchor id='videos'><AutoChip header='Videos' revealOnAnchorId='videos'> | ||
{!!videos.length && <Anchor id='attachment-video'><AutoChip header='Videos' revealOnAnchorId='attachment-video'> | ||
{videos.map((a, i) => <div key={`video-${i}`}> | ||
<video controls> | ||
<source src={a.path} type={a.contentType}/> | ||
|
@@ -127,11 +135,12 @@ export const TestResultView: React.FC<{ | |
</div>)} | ||
</AutoChip></Anchor>} | ||
|
||
{!!(otherAttachments.size + htmls.length) && <AutoChip header='Attachments'> | ||
{[...htmls].map((a, i) => ( | ||
<AttachmentLink key={`html-link-${i}`} attachment={a} openInNewTab />) | ||
{!!otherAttachments.size && <AutoChip header='Attachments' revealOnAnchorId={otherAttachmentsAnchor}> | ||
{[...otherAttachments].map((a, i) => | ||
<Anchor key={`attachment-link-${i}`} id={`attachment-${a.name}`}> | ||
<AttachmentLink attachment={a} openInNewTab={a.contentType.startsWith('text/html')} /> | ||
</Anchor> | ||
)} | ||
{[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)} | ||
</AutoChip>} | ||
</div>; | ||
}; | ||
|
@@ -161,19 +170,23 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[]) { | |
} | ||
|
||
const StepTreeItem: React.FC<{ | ||
test: TestCase; | ||
result: TestResult; | ||
step: TestStep; | ||
depth: number, | ||
}> = ({ step, depth }) => { | ||
return <TreeItem title={<span> | ||
}> = ({ test, step, result, depth }) => { | ||
const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1]; | ||
return <TreeItem title={<span aria-label={step.title}> | ||
<span style={{ float: 'right' }}>{msToString(step.duration)}</span> | ||
{attachmentName && <a style={{ float: 'right' }} title='link to attachment' href={`#?testId=${test.testId}&anchor=attachment-${encodeURIComponent(attachmentName)}&run=${test.results.indexOf(result)}`} onClick={evt => { evt.stopPropagation(); }}>{icons.attachment()}</a>} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i've tried rewriting, but that doesn't work as well because |
||
{statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')} | ||
<span>{step.title}</span> | ||
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>} | ||
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>} | ||
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => { | ||
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1}></StepTreeItem>); | ||
const children = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />); | ||
if (step.snippet) | ||
children.unshift(<TestErrorView testId='test-snippet' key='line' error={step.snippet}></TestErrorView>); | ||
children.unshift(<TestErrorView testId='test-snippet' key='line' error={step.snippet}/>); | ||
return children; | ||
} : undefined} depth={depth}></TreeItem>; | ||
} : undefined} depth={depth}/>; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
screenshotAnchors
?useMemo()
?Same for
otherAttachmentsAnchor
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'd also work, i'll make that change.