diff --git a/packages/html-reporter/src/chip.tsx b/packages/html-reporter/src/chip.tsx index 0965a40888..bbddcf7a05 100644 --- a/packages/html-reporter/src/chip.tsx +++ b/packages/html-reporter/src/chip.tsx @@ -20,6 +20,7 @@ import './colors.css'; import './common.css'; import * as icons from './icons'; import { clsx } from '@web/uiUtils'; +import { Reveal } from './links'; export const Chip: React.FC<{ header: JSX.Element | string, @@ -28,10 +29,9 @@ export const Chip: React.FC<{ setExpanded?: (expanded: boolean) => void, children?: any, dataTestId?: string, - targetRef?: React.RefObject, -}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId, targetRef }) => { +}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId }) => { const id = React.useId(); - return
+ return
, -}> = ({ header, initialExpanded, noInsets, children, dataTestId, targetRef }) => { - const [expanded, setExpanded] = React.useState(initialExpanded || initialExpanded === undefined); - return - {children} - ; + revealId?: string, +}> = ({ header, initialExpanded, noInsets, children, dataTestId, revealId }) => { + const [expanded, setExpanded] = React.useState(initialExpanded ?? true); + const onChangeReveal = React.useCallback((isRevealed: boolean) => { + if (isRevealed) + setExpanded(true); + }, [setExpanded]); + return + + {children} + + ; }; diff --git a/packages/html-reporter/src/links.tsx b/packages/html-reporter/src/links.tsx index 4b48090e0a..1db8bd9b2d 100644 --- a/packages/html-reporter/src/links.tsx +++ b/packages/html-reporter/src/links.tsx @@ -113,3 +113,30 @@ export function generateTraceUrl(traces: TestAttachment[]) { } const kMissingContentType = 'x-playwright/missing'; + + +export function useRevealed(revealId?: string) { + const searchParams = React.useContext(SearchParamsContext); + if (revealId === undefined) + return false; + return searchParams.get('reveal') === revealId; +} + +export function Reveal({ revealId, onChange, children }: React.PropsWithChildren<{ revealId?: string, onChange?(isRevealed: boolean, ref: HTMLDivElement): void }>) { + const isRevealed = useRevealed(revealId); + + const ref = React.useRef(null); + React.useEffect(() => { + if (!ref.current) + return; + + const preventDefault = onChange?.(isRevealed, ref.current); + if (preventDefault) + return; + + if (isRevealed) + ref.current?.scrollIntoView({ block: 'start', inline: 'start' }); + }, [isRevealed, onChange]); + + return
{children}
; +} diff --git a/packages/html-reporter/src/reportView.tsx b/packages/html-reporter/src/reportView.tsx index e8a5c3b250..cf0f5e5e56 100644 --- a/packages/html-reporter/src/reportView.tsx +++ b/packages/html-reporter/src/reportView.tsx @@ -101,7 +101,6 @@ const TestCaseViewLoader: React.FC<{ const searchParams = React.useContext(SearchParamsContext); const [test, setTest] = React.useState(); const testId = searchParams.get('testId'); - const anchor = (searchParams.get('anchor') || '') as 'video' | 'diff' | ''; const run = +(searchParams.get('run') || '0'); const { prev, next } = React.useMemo(() => { @@ -133,7 +132,6 @@ const TestCaseViewLoader: React.FC<{ next={next} prev={prev} test={test} - anchor={anchor} run={run} />; }; diff --git a/packages/html-reporter/src/testCaseView.spec.tsx b/packages/html-reporter/src/testCaseView.spec.tsx index 892ad51b7f..b7a9f9405b 100644 --- a/packages/html-reporter/src/testCaseView.spec.tsx +++ b/packages/html-reporter/src/testCaseView.spec.tsx @@ -63,7 +63,7 @@ const testCase: TestCase = { }; test('should render test case', async ({ mount }) => { - const component = await mount(); + const component = await mount(); await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible(); await expect(component.getByText('Hidden annotation')).toBeHidden(); await component.getByText('Annotations').click(); @@ -79,7 +79,7 @@ test('should render test case', async ({ mount }) => { test('should render copy buttons for annotations', async ({ mount, page, context }) => { await context.grantPermissions(['clipboard-read', 'clipboard-write']); - const component = await mount(); + const component = await mount(); await expect(component.getByText('Annotation text', { exact: false }).first()).toBeVisible(); await component.getByText('Annotation text', { exact: false }).first().hover(); await expect(component.locator('.test-case-annotation').getByLabel('Copy to clipboard').first()).toBeVisible(); @@ -108,7 +108,7 @@ const annotationLinkRenderingTestCase: TestCase = { }; test('should correctly render links in annotations', async ({ mount }) => { - const component = await mount(); + const component = await mount(); const firstLink = await component.getByText('https://playwright.dev/docs/intro').first(); await expect(firstLink).toBeVisible(); @@ -181,7 +181,7 @@ const testCaseSummary: TestCaseSummary = { test('should correctly render links in attachments', async ({ mount }) => { - const component = await mount(); + const component = await mount(); await component.getByText('first attachment').click(); const body = await component.getByText('The body with https://playwright.dev/docs/intro link'); await expect(body).toBeVisible(); @@ -194,7 +194,7 @@ test('should correctly render links in attachments', async ({ mount }) => { }); test('should correctly render links in attachment name', async ({ mount }) => { - const component = await mount(); + const component = await mount(); const link = component.getByText('attachment with inline link').locator('a'); await expect(link).toHaveAttribute('href', 'https://github.com/microsoft/playwright/issues/31284'); await expect(link).toHaveText('https://github.com/microsoft/playwright/issues/31284'); @@ -204,7 +204,7 @@ test('should correctly render links in attachment name', async ({ mount }) => { }); test('should correctly render prev and next', async ({ mount }) => { - const component = await mount(); + const component = await mount(); await expect(component).toMatchAriaSnapshot(` - text: group - link "« previous" diff --git a/packages/html-reporter/src/testCaseView.tsx b/packages/html-reporter/src/testCaseView.tsx index 320722fa9b..4e9785ad8a 100644 --- a/packages/html-reporter/src/testCaseView.tsx +++ b/packages/html-reporter/src/testCaseView.tsx @@ -33,9 +33,8 @@ export const TestCaseView: React.FC<{ test: TestCase | undefined, next: TestCaseSummary | undefined, prev: TestCaseSummary | undefined, - anchor: 'video' | 'diff' | '', run: number, -}> = ({ projectNames, test, run, anchor, next, prev }) => { +}> = ({ projectNames, test, run, next, prev }) => { const [selectedResultIndex, setSelectedResultIndex] = React.useState(run); const searchParams = React.useContext(SearchParamsContext); const filterParam = searchParams.has('q') ? '&q=' + searchParams.get('q') : ''; @@ -79,7 +78,7 @@ export const TestCaseView: React.FC<{ test.results.map((result, index) => ({ id: String(index), title:
{statusIcon(result.status)} {retryLabel(index)}
, - render: () => + render: () => })) || []} selectedTab={String(selectedResultIndex)} setSelectedTab={id => setSelectedResultIndex(+id)} />}
; }; diff --git a/packages/html-reporter/src/testFileView.tsx b/packages/html-reporter/src/testFileView.tsx index 4d6890ad33..52a79ab7b4 100644 --- a/packages/html-reporter/src/testFileView.tsx +++ b/packages/html-reporter/src/testFileView.tsx @@ -75,12 +75,12 @@ function imageDiffBadge(test: TestCaseSummary): JSX.Element | undefined { const resultWithImageDiff = test.results.find(result => result.attachments.some(attachment => { return attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/); })); - return resultWithImageDiff ? {image()} : undefined; + return resultWithImageDiff ? {image()} : undefined; } function videoBadge(test: TestCaseSummary): JSX.Element | undefined { const resultWithVideo = test.results.find(result => result.attachments.some(attachment => attachment.name === 'video')); - return resultWithVideo ? {video()} : undefined; + return resultWithVideo ? {video()} : undefined; } function traceBadge(test: TestCaseSummary): JSX.Element | undefined { diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index 3a562f3fcf..79a4331ac7 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -64,9 +64,7 @@ function groupImageDiffs(screenshots: Set): ImageDiff[] { export const TestResultView: React.FC<{ test: TestCase, result: TestResult, - anchor: 'video' | 'diff' | '', -}> = ({ result, anchor }) => { - +}> = ({ 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/'))); @@ -80,20 +78,6 @@ export const TestResultView: React.FC<{ return { screenshots: [...screenshots], videos, traces, otherAttachments, diffs, errors, htmls }; }, [result]); - const videoRef = React.useRef(null); - const imageDiffRef = React.useRef(null); - - const [scrolled, setScrolled] = React.useState(false); - React.useEffect(() => { - if (scrolled) - return; - setScrolled(true); - if (anchor === 'video') - videoRef.current?.scrollIntoView({ block: 'start', inline: 'start' }); - if (anchor === 'diff') - imageDiffRef.current?.scrollIntoView({ block: 'start', inline: 'start' }); - }, [scrolled, anchor, setScrolled, videoRef]); - return
{!!errors.length && {errors.map((error, index) => { @@ -107,8 +91,8 @@ export const TestResultView: React.FC<{ } {diffs.map((diff, index) => - - + + )} @@ -132,7 +116,7 @@ export const TestResultView: React.FC<{
} } - {!!videos.length && + {!!videos.length && {videos.map((a, i) =>