feat(trace-viewer): display text attachments in ui mode (#31215)

This commit is contained in:
Álvaro Martínez 2024-07-08 20:16:14 +02:00 committed by GitHub
parent 1d930542e1
commit 00131c1e3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 67 additions and 10 deletions

View File

@ -31,7 +31,7 @@ import { getPlaywrightVersion } from '../../utils/userAgent';
import { urlMatches } from '../../utils/network';
import { Frame } from '../frames';
import type { HeadersArray, LifecycleEvent } from '../types';
import { isTextualMimeType } from '../../utils/mimeType';
import { isTextualMimeType } from '../../utils/isomorphic/mimeType';
const FALLBACK_HTTP_VERSION = 'HTTP/1.1';
@ -674,4 +674,4 @@ function safeDateToISOString(value: string | number) {
}
}
const startedDateSymbol = Symbol('startedDate');
const startedDateSymbol = Symbol('startedDate');

View File

@ -26,7 +26,7 @@ export * from './headers';
export * from './hostPlatform';
export * from './httpServer';
export * from './manualPromise';
export * from './mimeType';
export * from './isomorphic/mimeType';
export * from './multimap';
export * from './network';
export * from './processLauncher';

View File

@ -45,3 +45,7 @@
max-width: 80%;
box-shadow: 0 12px 28px 0 rgba(0,0,0,.2), 0 2px 4px 0 rgba(0,0,0,.1);
}
a.codicon-cloud-download:hover{
background-color: var(--vscode-list-inactiveSelectionBackground)
}

View File

@ -20,9 +20,55 @@ import { ImageDiffView } from '@web/shared/imageDiffView';
import type { MultiTraceModel } from './modelUtil';
import { PlaceholderPanel } from './placeholderPanel';
import type { AfterActionTraceEventAttachment } from '@trace/trace';
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
import { isTextualMimeType } from '@isomorphic/mimeType';
import { Expandable } from '@web/components/expandable';
type Attachment = AfterActionTraceEventAttachment & { traceUrl: string };
type ExpandableAttachmentProps = {
attachment: Attachment;
};
const ExpandableAttachment: React.FunctionComponent<ExpandableAttachmentProps> = ({ attachment }) => {
const [expanded, setExpanded] = React.useState(false);
const [loaded, setLoaded] = React.useState(false);
const [attachmentText, setAttachmentText] = React.useState<string | null>(null);
const [emptyContentReason, setEmptyContentReason] = React.useState<string>('');
React.useMemo(() => {
if (!isTextualMimeType(attachment.contentType)) {
setEmptyContentReason('no preview available');
return;
}
if (expanded && !loaded) {
setEmptyContentReason('loading...');
fetch(attachmentURL(attachment)).then(response => response.text()).then(text => {
setAttachmentText(text);
setLoaded(true);
}).catch(err => setEmptyContentReason('failed to load: ' + err.message));
}
}, [attachment, expanded, loaded]);
return <Expandable title={
<>
{attachment.name}
<a href={attachmentURL(attachment) + '&download'}
className={'codicon codicon-cloud-download'}
style={{ cursor: 'pointer', color: 'var(--vscode-foreground)', marginLeft: '0.5rem' }}
onClick={$event => $event.stopPropagation()}>
</a>
</>
} expanded={expanded} expandOnTitleClick={true} setExpanded={exp => setExpanded(exp)}>
<div aria-label={attachment.name}>
{ attachmentText ?
<CodeMirrorWrapper text={attachmentText!} readOnly wrapLines={false}></CodeMirrorWrapper> :
<i>{emptyContentReason}</i>
}
</div>
</Expandable>;
};
export const AttachmentsTab: React.FunctionComponent<{
model: MultiTraceModel | undefined,
}> = ({ model }) => {
@ -82,7 +128,7 @@ export const AttachmentsTab: React.FunctionComponent<{
{attachments.size ? <div className='attachments-section'>Attachments</div> : undefined}
{[...attachments.values()].map((a, i) => {
return <div className='attachment-item' key={`attachment-${i}`}>
<a href={attachmentURL(a) + '&download'}>{a.name}</a>
<ExpandableAttachment attachment={a} />
</div>;
})}
</div>;

View File

@ -25,6 +25,7 @@ test('should contain text attachment', async ({ runUITest }) => {
test('attach test', async () => {
await test.info().attach('note', { path: __filename });
await test.info().attach('🎭', { body: 'hi tester!', contentType: 'text/plain' });
await test.info().attach('escaped', { body: '## Header\\n\\n> TODO: some todo\\n- _Foo_\\n- **Bar**', contentType: 'text/plain' });
});
`,
});
@ -32,13 +33,19 @@ test('should contain text attachment', async ({ runUITest }) => {
await page.getByTitle('Run all').click();
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
await page.getByText('Attachments').click();
for (const { name, content } of [
{ name: 'note', content: 'attach test' },
{ name: '🎭', content: 'hi tester!' }
for (const { name, content, displayedAsText } of [
{ name: 'note', content: 'attach test', displayedAsText: false },
{ name: '🎭', content: 'hi tester!', displayedAsText: true },
{ name: 'escaped', content: '## Header\n\n> TODO: some todo\n- _Foo_\n- **Bar**', displayedAsText: true },
]) {
await page.getByText(`attach "${name}"`, { exact: true }).click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: name }).click();
await page.locator('.expandable-title', { hasText: name }).click();
await expect(page.getByLabel(name)).toContainText(displayedAsText ?
content.split('\n')?.[0] :
'no preview available'
);
await page.locator('.expandable-title', { hasText: name }).getByRole('link').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(name);
expect((await readAllFromStream(await download.createReadStream())).toString()).toContain(content);
@ -60,7 +67,7 @@ test('should contain binary attachment', async ({ runUITest }) => {
await page.getByText('Attachments').click();
await page.getByText('attach "data"', { exact: true }).click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'data' }).click();
await page.locator('.expandable-title', { hasText: 'data' }).getByRole('link').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('data');
expect(await readAllFromStream(await download.createReadStream())).toEqual(Buffer.from([1, 2, 3]));
@ -81,7 +88,7 @@ test('should contain string attachment', async ({ runUITest }) => {
await page.getByText('Attachments').click();
await page.getByText('attach "note"', { exact: true }).click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'note' }).click();
await page.locator('.expandable-title', { hasText: 'note' }).getByRole('link').click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('note');
expect((await readAllFromStream(await download.createReadStream())).toString()).toEqual('text42');