diff --git a/packages/html-reporter/src/copyToClipboard.tsx b/packages/html-reporter/src/copyToClipboard.tsx index 17b1dfbf95..f02e26c87d 100644 --- a/packages/html-reporter/src/copyToClipboard.tsx +++ b/packages/html-reporter/src/copyToClipboard.tsx @@ -20,12 +20,14 @@ import './copyToClipboard.css'; type CopyToClipboardProps = { value: string; + icon?: JSX.Element; + title?: string; }; /** * A copy to clipboard button. */ -export const CopyToClipboard: React.FunctionComponent = ({ value }) => { +export const CopyToClipboard: React.FunctionComponent = ({ value, icon: copyIcon = icons.copy(), title }) => { type IconType = 'copy' | 'check' | 'cross'; const [icon, setIcon] = React.useState('copy'); const handleCopy = React.useCallback(() => { @@ -38,8 +40,8 @@ export const CopyToClipboard: React.FunctionComponent = ({ setIcon('cross'); }); }, [value]); - const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : icons.copy(); - return ; + const iconElement = icon === 'check' ? icons.check() : icon === 'cross' ? icons.cross() : copyIcon; + return ; }; type CopyToClipboardContainerProps = CopyToClipboardProps & { diff --git a/packages/html-reporter/src/icons.tsx b/packages/html-reporter/src/icons.tsx index ffe6a08fcf..f876748fe5 100644 --- a/packages/html-reporter/src/icons.tsx +++ b/packages/html-reporter/src/icons.tsx @@ -97,3 +97,9 @@ export const copy = () => { ; }; + +export const copilot = () => { + return ; +}; diff --git a/packages/html-reporter/src/testErrorView.css b/packages/html-reporter/src/testErrorView.css index 7dd89ca558..ba51f5d4aa 100644 --- a/packages/html-reporter/src/testErrorView.css +++ b/packages/html-reporter/src/testErrorView.css @@ -16,18 +16,21 @@ @import '@web/third_party/vscode/colors.css'; -.test-error-view { +.test-error-container { white-space: pre; overflow: auto; flex: none; padding: 0; background-color: var(--color-canvas-subtle); border-radius: 6px; - padding: 16px; line-height: initial; margin-bottom: 6px; } +.test-error-view { + padding: 16px; +} + .test-error-text { font-family: monospace; } diff --git a/packages/html-reporter/src/testErrorView.tsx b/packages/html-reporter/src/testErrorView.tsx index 79e2c8c853..f48479cfed 100644 --- a/packages/html-reporter/src/testErrorView.tsx +++ b/packages/html-reporter/src/testErrorView.tsx @@ -17,85 +17,58 @@ import { ansi2html } from '@web/ansi2html'; import * as React from 'react'; import './testErrorView.css'; +import * as icons from './icons'; import type { ImageDiff } from '@web/shared/imageDiffView'; import { ImageDiffView } from '@web/shared/imageDiffView'; import { GitCommitInfoContext } from './reportView'; +import { TestResult } from './types'; +import { CopyToClipboard } from './copyToClipboard'; -export const TestErrorView: React.FC<{ - error: string; - testId?: string; - hidePrompt?: boolean; -}> = ({ error, testId, hidePrompt }) => { - const html = React.useMemo(() => ansiErrorToHtml(error), [error]); +export const TestErrorView: React.FC<{ error: string; testId?: string; result?: TestResult }> = ({ error, testId, result }) => { return ( - <> -
-
+ +
+
- - +
+ ); +}; + +export const CodeSnippet = ({ code, children, testId }: React.PropsWithChildren<{ code: string; testId?: string; }>) => { + const html = React.useMemo(() => ansiErrorToHtml(code), [code]); + return ( +
+ {children} +
+
); }; const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g'); -export function stripAnsiEscapes(str: string): string { +function stripAnsiEscapes(str: string): string { return str.replace(ansiRegex, ''); } const PromptButton: React.FC<{ error: string; -}> = ({ error }) => { - const [copied, setCopied] = React.useState(false); + result?: TestResult; +}> = ({ error, result }) => { const gitCommitInfo = React.useContext(GitCommitInfoContext); - if (!gitCommitInfo) - return undefined; + const prompt = React.useMemo(() => { + const diff = gitCommitInfo?.['pull.diff'] ?? gitCommitInfo?.['revision.diff']; + const pageSnapshot = result?.attachments.find(a => a.name === 'pageSnapshot')?.body; - const diff = gitCommitInfo['pull.diff'] ?? gitCommitInfo['revision.diff']; - if (!diff) - return undefined; + return [ + 'You are a helpful assistant. Help me understand the error cause. Here is the error:', + stripAnsiEscapes(error), + 'And this is the code diff:', + diff, + 'And this is how the page looked:', + pageSnapshot, + ].join('\n\n'); + }, [gitCommitInfo, result]) - const showFireworks = () => { - const fireworksContainer = document.createElement('div'); - fireworksContainer.className = 'fireworks-container'; - document.body.appendChild(fireworksContainer); - - setTimeout(() => { - document.body.removeChild(fireworksContainer); - }, 2000); - }; - - return ( - - ); + return } title="Copy prompt to clipboard" />; }; export const TestScreenshotErrorView: React.FC<{ diff --git a/packages/html-reporter/src/testResultView.tsx b/packages/html-reporter/src/testResultView.tsx index 502edbd23e..41054a9a41 100644 --- a/packages/html-reporter/src/testResultView.tsx +++ b/packages/html-reporter/src/testResultView.tsx @@ -24,7 +24,7 @@ import { Anchor, AttachmentLink, generateTraceUrl, testResultHref } from './link import { statusIcon } from './statusIcon'; import type { ImageDiff } from '@web/shared/imageDiffView'; import { ImageDiffView } from '@web/shared/imageDiffView'; -import { TestErrorView, TestScreenshotErrorView } from './testErrorView'; +import { CodeSnippet, PromptButton, TestErrorView, TestScreenshotErrorView } from './testErrorView'; import * as icons from './icons'; import './testResultView.css'; @@ -90,7 +90,7 @@ export const TestResultView: React.FC<{ {errors.map((error, index) => { if (error.type === 'screenshot') return ; - return ; + return ; })} } {!!result.steps.length && @@ -182,7 +182,7 @@ const StepTreeItem: React.FC<{ {step.count > 1 && <> ✕ {step.count}} {step.location && — {step.location.file}:{step.location.line}} } loadChildren={step.steps.length || step.snippet ? () => { - const snippet = step.snippet ? [] : []; + const snippet = step.snippet ? [] : []; const steps = step.steps.map((s, i) => ); return snippet.concat(steps); } : undefined} depth={depth}/>; diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index e258f22798..1cfc0c6040 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -446,6 +446,17 @@ class HtmlBuilder { return a; } + if (a.name === 'pageSnapshot') { + try { + const body = fs.readFileSync(a.path!, { encoding: 'utf-8' }); + return { + name: 'pageSnapshot', + contentType: a.contentType, + body, + } + } catch {} + } + if (a.path) { let fileName = a.path; try {