use Anchor
This commit is contained in:
parent
579fbd25ba
commit
aa2bedae26
|
|
@ -20,7 +20,7 @@ import './colors.css';
|
|||
import './common.css';
|
||||
import * as icons from './icons';
|
||||
import { clsx } from '@web/uiUtils';
|
||||
import { useAnchor } from './links';
|
||||
import { type AnchorID, useAnchor } from './links';
|
||||
|
||||
export const Chip: React.FC<{
|
||||
header: JSX.Element | string,
|
||||
|
|
@ -53,7 +53,7 @@ export const AutoChip: React.FC<{
|
|||
noInsets?: boolean,
|
||||
children?: any,
|
||||
dataTestId?: string,
|
||||
revealOnAnchorId?: string,
|
||||
revealOnAnchorId?: AnchorID,
|
||||
}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealOnAnchorId }) => {
|
||||
const [expanded, setExpanded] = React.useState(initialExpanded ?? true);
|
||||
const onReveal = React.useCallback(() => setExpanded(true), []);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export function generateTraceUrl(traces: TestAttachment[]) {
|
|||
|
||||
const kMissingContentType = 'x-playwright/missing';
|
||||
|
||||
type AnchorID = string | ((id: string | null) => boolean) | undefined;
|
||||
export type AnchorID = string | ((id: string) => boolean) | undefined;
|
||||
|
||||
export function useAnchor(id: AnchorID, onReveal: () => void) {
|
||||
React.useEffect(() => {
|
||||
|
|
@ -123,8 +123,10 @@ export function useAnchor(id: AnchorID, onReveal: () => void) {
|
|||
|
||||
const listener = () => {
|
||||
const params = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (!params.has('anchor'))
|
||||
return;
|
||||
const anchor = params.get('anchor');
|
||||
const isRevealed = typeof id === 'function' ? id(anchor) : anchor === id;
|
||||
const isRevealed = typeof id === 'function' ? id(anchor!) : anchor === id;
|
||||
if (isRevealed)
|
||||
onReveal();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
|
|||
export const TestResultView: React.FC<{
|
||||
test: TestCase,
|
||||
result: TestResult,
|
||||
}> = ({ result }) => {
|
||||
}> = ({ test, result }) => {
|
||||
const { screenshots, videos, traces, otherAttachments, diffs, errors, htmls } = React.useMemo(() => {
|
||||
const attachments = result?.attachments || [];
|
||||
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
|
||||
|
|
@ -88,7 +88,7 @@ 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) =>
|
||||
|
|
@ -128,11 +128,17 @@ export const TestResultView: React.FC<{
|
|||
</div>)}
|
||||
</AutoChip></Anchor>}
|
||||
|
||||
{!!(otherAttachments.size + htmls.length) && <AutoChip header='Attachments'>
|
||||
{!!(otherAttachments.size + htmls.length) && <AutoChip header='Attachments' revealOnAnchorId={id => id.startsWith('attachment-')}>
|
||||
{[...htmls].map((a, i) => (
|
||||
<AttachmentLink key={`html-link-${i}`} attachment={a} openInNewTab />)
|
||||
<Anchor key={`html-link-${i}`} id={`attachment-${a.name}`}>
|
||||
<AttachmentLink attachment={a} openInNewTab />
|
||||
</Anchor>)
|
||||
)}
|
||||
{[...otherAttachments].map((a, i) =>
|
||||
<Anchor key={`attachment-link-${i}`} id={`attachment-${a.name}`}>
|
||||
<AttachmentLink attachment={a}/>
|
||||
</Anchor>
|
||||
)}
|
||||
{[...otherAttachments].map((a, i) => <AttachmentLink key={`attachment-link-${i}`} attachment={a}></AttachmentLink>)}
|
||||
</AutoChip>}
|
||||
</div>;
|
||||
};
|
||||
|
|
@ -162,21 +168,23 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[]) {
|
|||
}
|
||||
|
||||
const StepTreeItem: React.FC<{
|
||||
test: TestCase;
|
||||
result: TestResult;
|
||||
step: TestStep;
|
||||
depth: number,
|
||||
}> = ({ step, depth }) => {
|
||||
}> = ({ 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='TODO' onClick={evt => { evt.stopPropagation(); }}>{icons.attachment()}</a>}
|
||||
{attachmentName && <a style={{ float: 'right' }} title='link to attachment' href={`#?testId=${test.testId}&anchor=attachment-${attachmentName}&run=${test.results.indexOf(result)}`} onClick={evt => { evt.stopPropagation(); }}>{icons.attachment()}</a>}
|
||||
{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}/>;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue