Skip to content
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

Merged
merged 21 commits into from
Dec 16, 2024

Conversation

Skn0tt
Copy link
Member

@Skn0tt Skn0tt commented Oct 24, 2024

Similar to #33265. Adds a link from the attachment step to the attachment view.

@Skn0tt Skn0tt requested a review from dgozman October 24, 2024 12:12
@Skn0tt Skn0tt self-assigned this Oct 24, 2024

This comment has been minimized.

@Skn0tt
Copy link
Member Author

Skn0tt commented Oct 24, 2024

demo:

Screen.Recording.2024-10-24.at.14.40.25.mov

This comment has been minimized.

Copy link
Contributor

@dgozman dgozman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A screenshot of some sorts would be nice to see.

<span style={{ float: 'right' }}>{msToString(step.duration)}</span>
{attachmentName && <a style={{ float: 'right' }} title='link to attachment' href={'#' + componentID(params => params.set('attachment', attachmentName))} onClick={(evt) => { evt.stopPropagation(); }}>{icons.attachment()}</a>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this rely on the browser to automatically scroll to the anchor? How does that work together with the router? I am confused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's how it works. Didn't even know we had a router until now - i'll see if I can switch this to our Link component.

I figured using the anchor to target an element is the most browser-standards way of making this work. But I can also change it to use scrollIntoView if you'd prefer that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#33295 is a refactoring that allows us to use one scrollIntoView implementation for all attachments. It makes this implementation a lot easier.

packages/html-reporter/src/testResultView.tsx Outdated Show resolved Hide resolved

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

@Skn0tt
Copy link
Member Author

Skn0tt commented Nov 19, 2024

I've updated this PR to use the Anchor primitive we added in #33537.

@Skn0tt Skn0tt requested a review from dgozman November 19, 2024 16:20

This comment has been minimized.

@Skn0tt
Copy link
Member Author

Skn0tt commented Nov 19, 2024

sorry, i'll need to look more into this to make links to trace etc work

@Skn0tt Skn0tt marked this pull request as draft November 19, 2024 17:21
@Skn0tt Skn0tt marked this pull request as ready for review November 20, 2024 09:15
@Skn0tt
Copy link
Member Author

Skn0tt commented Nov 20, 2024

Here's a demo of it working:

Screen.Recording.2024-11-20.at.10.15.00.mov

This comment has been minimized.

This comment has been minimized.

}, [result]);

const screenshotAnchor = React.useMemo(() => screenshots.map(a => `attachment-${a.name}`), [screenshots]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • screenshotAnchors?
  • why not return this from the previous useMemo()?

Same for otherAttachmentsAnchor.

Copy link
Member Author

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.

<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>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I wonder why do we use <Link> in some places, and plain <a> in others?
  • Can we simplify link generation to avoid passing test and result around somehow? Perhaps take existing ones from the current url? Or put them into a context? Something for a followup.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • no idea 🤷 I guess I can also rewrite this to <Link>, but I don't think there'll be a big difference since it's not a text link but a button
  • agree, that'd be nice. i'll look at it in a followup.

Copy link
Member Author

@Skn0tt Skn0tt Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've tried rewriting, but that doesn't work as well because <Link> doesn't allow passing in the float: 'right' style. Let's stick with <a>.

This comment has been minimized.

This comment has been minimized.

@Skn0tt Skn0tt requested a review from dgozman December 11, 2024 13:21
onReveal();
};
window.addEventListener('popstate', listener);

// check if we're already on the anchor. required to make it work on page load.
listener();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this scroll away on some re-renders when effect is re-registered for whatever reason?

Copy link
Member Author

@Skn0tt Skn0tt Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. The effect re-registers when id or onReveal change. We can work against the onReveal change using useCallback, but id might change for example when some UI element is added dynamically and now an enclosing Anchor needs to list more IDs. I'll need to rethink this.

How hard can it be to design a Reveal API in React?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I ended up reverting to the useEffect-based version in 6b3bad4. That makes the code easier to reason about and also ensures that the page-load works as expected.

In a previous iteration, this approach had the problem that it wouldn't re-reveal an element if the current anchor is navigated to again. This iteration fixes that by making the searchParams an effect dependency. So everytime there's a new searchParams object, even if it contains the same parameter strings, the effect will re-fire.

return () => window.removeEventListener('popstate', listener);
}, [id, onReveal]);
}

export function useIsAnchored(id: AnchorID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we merge this with useAnchor() and make it return boolean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that would only make it harder to use I'm afraid. This one is good for making CSS dependent on anchor, while the other one is good for calling imperative code on anchor change.

for (const result of test.results) {
for (const attachment of result.attachments) {
if (attachment.contentType.startsWith('image/') && !!attachment.name.match(/-(expected|actual|diff)/))
return <Link href={`#?testId=${test.testId}&anchor=attachment-${attachment.name}&run=${test.results.indexOf(result)}`} title='View images' className='test-file-badge'>{image()}</Link>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attachment.name for sure needs escaping. Might as well use URLSearchParams here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 2ae3918

<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>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract a helper function for attachmentAnchorURL() and use everywhere? It gets pretty complicated with all the parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in f04234b

const navState = new URLSearchParams(url.hash.slice(1));
return navState.get('anchor') === 'attachment-foo';
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can use expect().toBeInViewport() here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 91ef9c6

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

@Skn0tt Skn0tt requested a review from dgozman December 12, 2024 11:26
@@ -59,7 +59,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
<span data-testid='test-duration' style={{ minWidth: '50px', textAlign: 'right' }}>{msToString(test.duration)}</span>
</div>
<div className='test-file-details-row'>
<Link href={`#?testId=${test.testId}`} title={[...test.path, test.title].join(' › ')} className='test-file-path-link'>
<Link href={testResultHref({ test })} title={[...test.path, test.title].join(' › ')} className='test-file-path-link'>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether testResultHref() should automatically append filterParam? For example, here and below at line 78 we lose it, which looks unintentional.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'd rather fix that in a separate PR though, so it's easier to revert if we don't want it for some reason.

<ImageDiffView diff={diff}/>
</AutoChip>
</Anchor>
)}

{!!screenshots.length && <AutoChip header='Screenshots'>
{!!screenshots.length && <Anchor id={screenshotAnchors}><AutoChip header='Screenshots' revealOnAnchorId={screenshotAnchors}>
{screenshots.map((a, i) => {
return <div key={`screenshot-${i}`}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to wrap each screenshot in Anchor to be able to scroll to a particular one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, added in d098a57

This comment has been minimized.

@Skn0tt Skn0tt requested a review from dgozman December 16, 2024 10:47
<ImageDiffView diff={diff}/>
</AutoChip>
</Anchor>
)}

{!!screenshots.length && <AutoChip header='Screenshots'>
{!!screenshots.length && <Anchor id={screenshotAnchors}><AutoChip header='Screenshots' revealOnAnchorId={screenshotAnchors}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do we need a big Anchor here? I think expanding AutoChip should just work, and each screenshot will be scrolled to by the inner Anchor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

Copy link
Contributor

Test results for "tests 1"

1 failed
❌ [playwright-test] › playwright.artifacts.spec.ts:222:5 › should work with screenshot: only-on-failure & fullPage @macos-latest-node18-1

6 flaky ⚠️ [firefox-library] › library/inspector/cli-codegen-aria.spec.ts:77:7 › should update aria snapshot highlight @firefox-ubuntu-22.04-node18
⚠️ [firefox-page] › page/page-evaluate.spec.ts:403:3 › should throw for too deep reference chain @firefox-ubuntu-22.04-node18
⚠️ [webkit-library] › library/debug-controller.spec.ts:202:1 › should record custom data-testid @webkit-ubuntu-22.04-node18
⚠️ [webkit-library] › library/proxy.spec.ts:63:3 › should work with IP:PORT notion @webkit-ubuntu-22.04-node18
⚠️ [webkit-library] › library/trace-viewer.spec.ts:109:1 › should show tracing.group in the action list with location @webkit-ubuntu-22.04-node18
⚠️ [webkit-page] › page/page-set-input-files.spec.ts:205:3 › should upload multiple large files @webkit-ubuntu-22.04-node18

37305 passed, 650 skipped
✔️✔️✔️

Merge workflow run.

@Skn0tt Skn0tt merged commit 512cb36 into microsoft:main Dec 16, 2024
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants