use Anchor
This commit is contained in:
parent
579fbd25ba
commit
aa2bedae26
|
|
@ -20,7 +20,7 @@ import './colors.css';
|
||||||
import './common.css';
|
import './common.css';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
import { clsx } from '@web/uiUtils';
|
import { clsx } from '@web/uiUtils';
|
||||||
import { useAnchor } from './links';
|
import { type AnchorID, useAnchor } from './links';
|
||||||
|
|
||||||
export const Chip: React.FC<{
|
export const Chip: React.FC<{
|
||||||
header: JSX.Element | string,
|
header: JSX.Element | string,
|
||||||
|
|
@ -53,7 +53,7 @@ export const AutoChip: React.FC<{
|
||||||
noInsets?: boolean,
|
noInsets?: boolean,
|
||||||
children?: any,
|
children?: any,
|
||||||
dataTestId?: string,
|
dataTestId?: string,
|
||||||
revealOnAnchorId?: string,
|
revealOnAnchorId?: AnchorID,
|
||||||
}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealOnAnchorId }) => {
|
}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealOnAnchorId }) => {
|
||||||
const [expanded, setExpanded] = React.useState(initialExpanded ?? true);
|
const [expanded, setExpanded] = React.useState(initialExpanded ?? true);
|
||||||
const onReveal = React.useCallback(() => setExpanded(true), []);
|
const onReveal = React.useCallback(() => setExpanded(true), []);
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ export function generateTraceUrl(traces: TestAttachment[]) {
|
||||||
|
|
||||||
const kMissingContentType = 'x-playwright/missing';
|
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) {
|
export function useAnchor(id: AnchorID, onReveal: () => void) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -123,8 +123,10 @@ export function useAnchor(id: AnchorID, onReveal: () => void) {
|
||||||
|
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
const params = new URLSearchParams(window.location.hash.slice(1));
|
const params = new URLSearchParams(window.location.hash.slice(1));
|
||||||
|
if (!params.has('anchor'))
|
||||||
|
return;
|
||||||
const anchor = params.get('anchor');
|
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)
|
if (isRevealed)
|
||||||
onReveal();
|
onReveal();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function groupImageDiffs(screenshots: Set<TestAttachment>): ImageDiff[] {
|
||||||
export const TestResultView: React.FC<{
|
export const TestResultView: React.FC<{
|
||||||
test: TestCase,
|
test: TestCase,
|
||||||
result: TestResult,
|
result: TestResult,
|
||||||
}> = ({ result }) => {
|
}> = ({ test, result }) => {
|
||||||
const { screenshots, videos, traces, otherAttachments, diffs, errors, htmls } = React.useMemo(() => {
|
const { screenshots, videos, traces, otherAttachments, diffs, errors, htmls } = React.useMemo(() => {
|
||||||
const attachments = result?.attachments || [];
|
const attachments = result?.attachments || [];
|
||||||
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
|
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/')));
|
||||||
|
|
@ -88,7 +88,7 @@ export const TestResultView: React.FC<{
|
||||||
})}
|
})}
|
||||||
</AutoChip>}
|
</AutoChip>}
|
||||||
{!!result.steps.length && <AutoChip header='Test Steps'>
|
{!!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>}
|
</AutoChip>}
|
||||||
|
|
||||||
{diffs.map((diff, index) =>
|
{diffs.map((diff, index) =>
|
||||||
|
|
@ -128,11 +128,17 @@ export const TestResultView: React.FC<{
|
||||||
</div>)}
|
</div>)}
|
||||||
</AutoChip></Anchor>}
|
</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) => (
|
{[...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>}
|
</AutoChip>}
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
@ -162,21 +168,23 @@ function classifyErrors(testErrors: string[], diffs: ImageDiff[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const StepTreeItem: React.FC<{
|
const StepTreeItem: React.FC<{
|
||||||
|
test: TestCase;
|
||||||
|
result: TestResult;
|
||||||
step: TestStep;
|
step: TestStep;
|
||||||
depth: number,
|
depth: number,
|
||||||
}> = ({ step, depth }) => {
|
}> = ({ test, step, result, depth }) => {
|
||||||
const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1];
|
const attachmentName = step.title.match(/^attach "(.*)"$/)?.[1];
|
||||||
return <TreeItem title={<span aria-label={step.title}>
|
return <TreeItem title={<span aria-label={step.title}>
|
||||||
<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
|
<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')}
|
{statusIcon(step.error || step.duration === -1 ? 'failed' : 'passed')}
|
||||||
<span>{step.title}</span>
|
<span>{step.title}</span>
|
||||||
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</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>}
|
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>}
|
||||||
</span>} loadChildren={step.steps.length + (step.snippet ? 1 : 0) ? () => {
|
</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)
|
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;
|
return children;
|
||||||
} : undefined} depth={depth}></TreeItem>;
|
} : undefined} depth={depth}/>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue