From e3c379d069ffa9661561d25cdbf2f5894a2f8ee8 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sat, 14 Feb 2026 08:58:38 -0800 Subject: Refactor: project structure, implement dependency injection, and align v2 UI with v1 --- frontend/playwright-report/index.html | 21860 +++++++++++++++++++++++++++++++- 1 file changed, 21791 insertions(+), 69 deletions(-) (limited to 'frontend/playwright-report/index.html') diff --git a/frontend/playwright-report/index.html b/frontend/playwright-report/index.html index 4095719..1d408fd 100644 --- a/frontend/playwright-report/index.html +++ b/frontend/playwright-report/index.html @@ -1,85 +1,21807 @@ - - - - + + - - - + + + Playwright Test Report - - +`.trimStart(); + async function zv({ + testInfo: l, + metadata: u, + errorContext: c, + errors: f, + buildCodeFrame: r, + stdout: o, + stderr: h, + }) { + var S; + const v = new Set( + f + .filter( + (O) => + O.message && + !O.message.includes(` +`) + ) + .map((O) => O.message) + ); + for (const O of f) + for (const X of v.keys()) (S = O.message) != null && S.includes(X) && v.delete(X); + const y = f.filter( + (O) => + !( + !O.message || + (!O.message.includes(` +`) && + !v.has(O.message)) + ) + ); + if (!y.length) return; + const A = [Qv, '# Test info', '', l]; + (o && A.push('', '# Stdout', '', '```', Jf(o), '```'), + h && A.push('', '# Stderr', '', '```', Jf(h), '```'), + A.push('', '# Error details')); + for (const O of y) A.push('', '```', Jf(O.message || ''), '```'); + c && A.push(c); + const E = await r(y[y.length - 1]); + return ( + E && A.push('', '# Test source', '', '```ts', E, '```'), + u != null && u.gitDiff && A.push('', '# Local changes', '', '```diff', u.gitDiff, '```'), + A.join(` +`) + ); + } + const Yv = new RegExp( + '([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', + 'g' + ); + function Jf(l) { + return l.replace(Yv, ''); + } + function Lv(l, u) { + var f; + const c = new Map(); + for (const r of l) { + const o = r.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); + if (!o) continue; + const [, h, v, y = ''] = o, + A = h + y; + let E = c.get(A); + (E || ((E = { name: A, anchors: [`attachment-${h}`] }), c.set(A, E)), + E.anchors.push(`attachment-${u.attachments.indexOf(r)}`), + v === 'actual' && (E.actual = { attachment: r }), + v === 'expected' && (E.expected = { attachment: r, title: 'Expected' }), + v === 'previous' && (E.expected = { attachment: r, title: 'Previous' }), + v === 'diff' && (E.diff = { attachment: r })); + } + for (const [r, o] of c) + !o.actual || !o.expected + ? c.delete(r) + : (l.delete(o.actual.attachment), + l.delete(o.expected.attachment), + l.delete((f = o.diff) == null ? void 0 : f.attachment)); + return [...c.values()]; + } + const Gv = ({ test: l, result: u, testRunMetadata: c, options: f }) => { + const { + screenshots: r, + videos: o, + traces: h, + otherAttachments: v, + diffs: y, + errors: A, + otherAttachmentAnchors: E, + screenshotAnchors: S, + errorContext: O, + } = ct.useMemo(() => { + const B = u.attachments.filter((N) => !N.name.startsWith('_')), + b = new Set(B.filter((N) => N.contentType.startsWith('image/'))), + p = [...b].map((N) => `attachment-${B.indexOf(N)}`), + x = B.filter((N) => N.contentType.startsWith('video/')), + R = B.filter((N) => N.name === 'trace'), + U = B.find((N) => N.name === 'error-context'), + Z = new Set(B); + [...b, ...x, ...R].forEach((N) => Z.delete(N)); + const F = [...Z].map((N) => `attachment-${B.indexOf(N)}`), + j = Lv(b, u), + D = u.errors.map((N) => N.message); + return { + screenshots: [...b], + videos: x, + traces: R, + otherAttachments: Z, + diffs: j, + errors: D, + otherAttachmentAnchors: F, + screenshotAnchors: p, + errorContext: U, + }; + }, [u]), + X = P5( + async () => { + if (f != null && f.noCopyPrompt) return; + const B = u.attachments.find((R) => R.name === 'stdout'), + b = u.attachments.find((R) => R.name === 'stderr'), + p = B != null && B.body && B.contentType === 'text/plain' ? B.body : void 0, + x = b != null && b.body && b.contentType === 'text/plain' ? b.body : void 0; + return await zv({ + testInfo: [ + `- Name: ${l.path.join(' >> ')} >> ${l.title}`, + `- Location: ${l.location.file}:${l.location.line}:${l.location.column}`, + ].join(` +`), + metadata: c, + errorContext: + O != null && O.path + ? await fetch(O.path).then((R) => R.text()) + : O == null + ? void 0 + : O.body, + errors: u.errors, + buildCodeFrame: async (R) => R.codeframe, + stdout: p, + stderr: x, + }); + }, + [l, O, c, u], + void 0 + ); + return m.jsxs('div', { + className: 'test-result', + children: [ + !!A.length && + m.jsxs(ke, { + header: 'Errors', + children: [ + X && + m.jsx('div', { + style: { position: 'absolute', right: '16px', padding: '10px', zIndex: 1 }, + children: m.jsx(Nv, { prompt: X }), + }), + A.map((B, b) => { + const p = Xv(B, y); + return m.jsxs(m.Fragment, { + children: [ + m.jsx(wr, { code: B }, 'test-result-error-message-' + b), + p && m.jsx(Bv, { diff: p }), + ], + }); + }), + ], + }), + !!u.steps.length && + m.jsx(ke, { + header: 'Test Steps', + children: u.steps.map((B, b) => + m.jsx(cm, { step: B, result: u, test: l, depth: 0 }, `step-${b}`) + ), + }), + y.map((B, b) => + m.jsx( + Si, + { + id: B.anchors, + children: m.jsx(ke, { + dataTestId: 'test-results-image-diff', + header: `Image mismatch: ${B.name}`, + revealOnAnchorId: B.anchors, + children: m.jsx(um, { diff: B }), + }), + }, + `diff-${b}` + ) + ), + !!r.length && + m.jsx(ke, { + header: 'Screenshots', + revealOnAnchorId: S, + children: r.map((B, b) => + m.jsxs( + Si, + { + id: `attachment-${u.attachments.indexOf(B)}`, + children: [ + m.jsx('a', { + href: Ve(B.path), + children: m.jsx('img', { className: 'screenshot', src: Ve(B.path) }), + }), + m.jsx(nc, { attachment: B, result: u }), + ], + }, + `screenshot-${b}` + ) + ), + }), + !!h.length && + m.jsx(Si, { + id: 'attachment-trace', + children: m.jsx(ke, { + header: 'Traces', + revealOnAnchorId: 'attachment-trace', + children: m.jsxs('div', { + children: [ + m.jsx('a', { + href: Ve(nm(h)), + children: m.jsx('img', { + className: 'screenshot', + src: Cv, + style: { width: 192, height: 117, marginLeft: 20 }, + }), + }), + h.map((B, b) => + m.jsx( + nc, + { + attachment: B, + result: u, + linkName: h.length === 1 ? 'trace' : `trace-${b + 1}`, + }, + `trace-${b}` + ) + ), + ], + }), + }), + }), + !!o.length && + m.jsx(Si, { + id: 'attachment-video', + children: m.jsx(ke, { + header: 'Videos', + revealOnAnchorId: 'attachment-video', + children: o.map((B) => + m.jsxs( + 'div', + { + children: [ + m.jsx('video', { + controls: !0, + children: m.jsx('source', { src: Ve(B.path), type: B.contentType }), + }), + m.jsx(nc, { attachment: B, result: u }), + ], + }, + B.path + ) + ), + }), + }), + !!v.size && + m.jsx(ke, { + header: 'Attachments', + revealOnAnchorId: E, + dataTestId: 'attachments', + children: [...v].map((B, b) => + m.jsx( + Si, + { + id: `attachment-${u.attachments.indexOf(B)}`, + children: m.jsx(nc, { + attachment: B, + result: u, + openInNewTab: B.contentType.startsWith('text/html'), + }), + }, + `attachment-link-${b}` + ) + ), + }), + ], + }); + }; + function Xv(l, u) { + const c = l.split(` +`)[0]; + if (!(!c.includes('toHaveScreenshot') && !c.includes('toMatchSnapshot'))) + return u.find((f) => l.includes(f.name)); + } + const cm = ({ test: l, step: u, result: c, depth: f }) => { + const r = se(); + return m.jsx(Tv, { + title: m.jsxs('div', { + 'aria-label': u.title, + className: 'step-title-container', + children: [ + hc(u.error || u.duration === -1 ? 'failed' : u.skipped ? 'skipped' : 'passed'), + m.jsxs('span', { + className: 'step-title-text', + children: [ + m.jsx('span', { children: u.title }), + u.count > 1 && + m.jsxs(m.Fragment, { + children: [ + ' ✕ ', + m.jsx('span', { className: 'test-result-counter', children: u.count }), + ], + }), + u.location && + m.jsxs('span', { + className: 'test-result-path', + children: ['— ', u.location.file, ':', u.location.line], + }), + ], + }), + m.jsx('span', { className: 'step-spacer' }), + u.attachments.length > 0 && + m.jsx('a', { + className: 'step-attachment-link', + title: 'reveal attachment', + href: Ve( + Cn({ test: l, result: c, anchor: `attachment-${u.attachments[0]}` }, r) + ), + onClick: (o) => { + o.stopPropagation(); + }, + children: Ih(), + }), + m.jsx('span', { className: 'step-duration', children: Ol(u.duration) }), + ], + }), + loadChildren: + u.steps.length || u.snippet + ? () => { + const o = u.snippet + ? [m.jsx(wr, { testId: 'test-snippet', code: u.snippet }, 'line')] + : [], + h = u.steps.map((v, y) => + m.jsx(cm, { step: v, depth: f + 1, result: c, test: l }, y) + ); + return o.concat(h); + } + : void 0, + depth: f, + }); + }, + Vv = ({ + projectNames: l, + test: u, + testRunMetadata: c, + run: f, + next: r, + prev: o, + options: h, + }) => { + const [v, y] = ct.useState(f), + A = se(), + E = u.annotations.filter((S) => !S.type.startsWith('_')) ?? []; + return m.jsxs(m.Fragment, { + children: [ + m.jsx(Or, { + title: u.title, + leftSuperHeader: m.jsx('div', { + className: 'test-case-path', + children: u.path.join(' › '), + }), + rightSuperHeader: m.jsxs(m.Fragment, { + children: [ + m.jsx('div', { + className: Ze(!o && 'hidden'), + children: m.jsx(Tn, { href: Cn({ test: o }, A), children: '« previous' }), + }), + m.jsx('div', { style: { width: 10 } }), + m.jsx('div', { + className: Ze(!r && 'hidden'), + children: m.jsx(Tn, { href: Cn({ test: r }, A), children: 'next »' }), + }), + ], + }), + }), + m.jsxs('div', { + className: 'hbox', + style: { lineHeight: '24px' }, + children: [ + m.jsx('div', { + className: 'test-case-location', + children: m.jsxs(Sr, { + value: `${u.location.file}:${u.location.line}`, + children: [u.location.file, ':', u.location.line], + }), + }), + m.jsx('div', { style: { flex: 'auto' } }), + m.jsx(tm, { test: u, trailingSeparator: !0 }), + m.jsx('div', { className: 'test-case-duration', children: Ol(u.duration) }), + ], + }), + m.jsx($h, { + style: { marginLeft: '6px' }, + projectNames: l, + activeProjectName: u.projectName, + otherLabels: u.tags, + }), + u.results.length === 0 && + E.length !== 0 && + m.jsx(ke, { + header: 'Annotations', + dataTestId: 'test-case-annotations', + children: E.map((S, O) => m.jsx(z2, { annotation: S }, O)), + }), + m.jsx(Sv, { + tabs: + u.results.map((S, O) => ({ + id: String(O), + title: m.jsxs('div', { + style: { display: 'flex', alignItems: 'center' }, + children: [ + hc(S.status), + ' ', + Zv(O), + u.results.length > 1 && + m.jsx('span', { + className: 'test-case-run-duration', + children: Ol(S.duration), + }), + ], + }), + render: () => { + const X = S.annotations.filter((B) => !B.type.startsWith('_')); + return m.jsxs(m.Fragment, { + children: [ + !!X.length && + m.jsx(ke, { + header: 'Annotations', + dataTestId: 'test-case-annotations', + children: X.map((B, b) => m.jsx(z2, { annotation: B }, b)), + }), + m.jsx(Gv, { test: u, result: S, testRunMetadata: c, options: h }), + ], + }); + }, + })) || [], + selectedTab: String(v), + setSelectedTab: (S) => y(+S), + }), + ], + }); + }; + function z2({ annotation: { type: l, description: u } }) { + return m.jsxs('div', { + className: 'test-case-annotation', + children: [ + m.jsx('span', { style: { fontWeight: 'bold' }, children: l }), + u && m.jsxs(Sr, { value: u, children: [': ', Di(u)] }), + ], + }); + } + function Zv(l) { + return l ? `Retry #${l}` : 'Run'; + } + const sm = ({ + file: l, + projectNames: u, + isFileExpanded: c, + setFileExpanded: f, + footer: r, + }) => { + const o = se(); + return m.jsx(im, { + expanded: c ? c(l.fileId) : void 0, + noInsets: !0, + setExpanded: f ? (h) => f(l.fileId, h) : void 0, + header: m.jsx('span', { className: 'chip-header-allow-selection', children: l.fileName }), + footer: r, + children: l.tests.map((h) => + m.jsxs( + 'div', + { + className: Ze('test-file-test', 'test-file-test-outcome-' + h.outcome), + children: [ + m.jsxs('div', { + className: 'hbox', + style: { alignItems: 'flex-start' }, + children: [ + m.jsxs('div', { + className: 'hbox', + children: [ + m.jsx('span', { + className: 'test-file-test-status-icon', + children: hc(h.outcome), + }), + m.jsxs('span', { + children: [ + m.jsx(Tn, { + href: Cn({ test: h }, o), + title: [...h.path, h.title].join(' › '), + children: m.jsx('span', { + className: 'test-file-title', + children: [...h.path, h.title].join(' › '), + }), + }), + m.jsx($h, { + style: { marginLeft: '6px' }, + projectNames: u, + activeProjectName: h.projectName, + otherLabels: h.tags, + }), + ], + }), + ], + }), + m.jsx('span', { + 'data-testid': 'test-duration', + style: { minWidth: '50px', textAlign: 'right' }, + children: Ol(h.duration), + }), + ], + }), + m.jsx('div', { + className: 'test-file-details-row', + children: m.jsxs('div', { + className: 'test-file-details-row-items', + children: [ + m.jsx(Tn, { + href: Cn({ test: h }, o), + title: [...h.path, h.title].join(' › '), + className: 'test-file-path-link', + children: m.jsxs('span', { + className: 'test-file-path', + children: [h.location.file, ':', h.location.line], + }), + }), + m.jsx(qv, { test: h }), + m.jsx(Iv, { test: h }), + m.jsx(tm, { test: h, dim: !0 }), + ], + }), + }), + ], + }, + `test-${h.testId}` + ) + ), + }); + }; + function qv({ test: l }) { + const u = se(); + for (const c of l.results) + for (const f of c.attachments) + if (f.contentType.startsWith('image/') && f.name.match(/-(expected|actual|diff)/)) + return m.jsx(Tr, { + href: Cn( + { test: l, result: c, anchor: `attachment-${c.attachments.indexOf(f)}` }, + u + ), + title: 'View images', + dim: !0, + children: k5(), + }); + } + function Iv({ test: l }) { + const u = se(), + c = l.results.find((f) => f.attachments.some((r) => r.name === 'video')); + return c + ? m.jsx(Tr, { + href: Cn({ test: l, result: c, anchor: 'attachment-video' }, u), + title: 'View video', + dim: !0, + children: J5(), + }) + : void 0; + } + class Kv extends ct.Component { + constructor() { + super(...arguments); + yn(this, 'state', { error: null, errorInfo: null }); + } + componentDidCatch(c, f) { + this.setState({ error: c, errorInfo: f }); + } + render() { + var c, f, r; + return this.state.error || this.state.errorInfo + ? m.jsxs('div', { + className: 'metadata-view p-3', + children: [ + m.jsx('p', { + children: 'An error was encountered when trying to render metadata.', + }), + m.jsx('p', { + children: m.jsxs('pre', { + style: { overflow: 'scroll' }, + children: [ + (c = this.state.error) == null ? void 0 : c.message, + m.jsx('br', {}), + (f = this.state.error) == null ? void 0 : f.stack, + m.jsx('br', {}), + (r = this.state.errorInfo) == null ? void 0 : r.componentStack, + ], + }), + }), + ], + }) + : this.props.children; + } + } + const kv = (l) => m.jsx(Kv, { children: m.jsx(Jv, { metadata: l.metadata }) }), + Jv = (l) => { + const u = l.metadata, + c = se().has('show-metadata-other') + ? Object.entries(l.metadata).filter(([r]) => !fm.has(r)) + : []; + if (u.ci || u.gitCommit || c.length > 0) + return m.jsxs('div', { + className: 'metadata-view', + children: [ + u.ci && !u.gitCommit && m.jsx(Fv, { info: u.ci }), + u.gitCommit && m.jsx(Wv, { ci: u.ci, commit: u.gitCommit }), + c.length > 0 && + m.jsxs(m.Fragment, { + children: [ + (u.gitCommit || u.ci) && m.jsx('div', { className: 'metadata-separator' }), + m.jsx('div', { + className: 'metadata-section metadata-properties', + role: 'list', + children: c.map(([r, o]) => { + const h = + typeof o != 'object' || o === null || o === void 0 + ? String(o) + : JSON.stringify(o), + v = h.length > 1e3 ? h.slice(0, 1e3) + '…' : h; + return m.jsx( + 'div', + { + className: 'copyable-property', + role: 'listitem', + children: m.jsxs(Sr, { + value: h, + children: [ + m.jsx('span', { + style: { fontWeight: 'bold' }, + title: r, + children: r, + }), + ': ', + m.jsx('span', { title: v, children: Di(v) }), + ], + }), + }, + r + ); + }), + }), + ], + }), + ], + }); + }, + Fv = ({ info: l }) => { + const u = l.prTitle || `Commit ${l.commitHash}`, + c = l.prHref || l.commitHref; + return m.jsx('div', { + className: 'metadata-section', + role: 'list', + children: m.jsx('div', { + role: 'listitem', + children: m.jsx('a', { + href: Ve(c), + target: '_blank', + rel: 'noopener noreferrer', + title: u, + children: u, + }), + }), + }); + }, + Wv = ({ ci: l, commit: u }) => { + const c = (l == null ? void 0 : l.prTitle) || u.subject, + f = (l == null ? void 0 : l.prHref) || (l == null ? void 0 : l.commitHref), + r = ` <${u.author.email}>`, + o = `${u.author.name}${r}`, + h = Intl.DateTimeFormat(void 0, { dateStyle: 'medium' }).format(u.committer.time), + v = Intl.DateTimeFormat(void 0, { dateStyle: 'full', timeStyle: 'long' }).format( + u.committer.time + ); + return m.jsxs('div', { + className: 'metadata-section', + role: 'list', + children: [ + m.jsxs('div', { + role: 'listitem', + children: [ + f && + m.jsx('a', { + href: Ve(f), + target: '_blank', + rel: 'noopener noreferrer', + title: c, + children: c, + }), + !f && m.jsx('span', { title: c, children: c }), + ], + }), + m.jsxs('div', { + role: 'listitem', + className: 'hbox', + children: [ + m.jsx('span', { className: 'mr-1', children: o }), + m.jsxs('span', { title: v, children: [' on ', h] }), + ], + }), + ], + }); + }, + fm = new Set(['ci', 'gitCommit', 'gitDiff', 'actualWorkers']), + _v = (l) => { + const u = Object.entries(l).filter(([c]) => !fm.has(c)); + return !l.ci && !l.gitCommit && !u.length; + }, + Pv = ({ files: l, expandedFiles: u, setExpandedFiles: c, projectNames: f }) => { + const r = ct.useMemo(() => { + const o = []; + let h = 0; + for (const v of l) + ((h += v.tests.length), o.push({ file: v, defaultExpanded: h < 200 })); + return o; + }, [l]); + return m.jsx(m.Fragment, { + children: + r.length > 0 + ? r.map(({ file: o, defaultExpanded: h }) => + m.jsx( + sm, + { + file: o, + projectNames: f, + isFileExpanded: (v) => { + const y = u.get(v); + return y === void 0 ? h : !!y; + }, + setFileExpanded: (v, y) => { + const A = new Map(u); + (A.set(v, y), c(A)); + }, + }, + `file-${o.fileId}` + ) + ) + : m.jsx('div', { + className: 'chip-header test-file-no-files', + children: 'No tests found', + }), + }); + }, + Y2 = ({ report: l, filteredStats: u, metadataVisible: c, toggleMetadataVisible: f }) => { + if (!l) return null; + const r = l.projectNames.length === 1 && !!l.projectNames[0], + o = !r && !u, + h = + !_v(l.metadata) && + m.jsxs('div', { + className: Ze('metadata-toggle', !o && 'metadata-toggle-second-line'), + role: 'button', + onClick: f, + title: c ? 'Hide metadata' : 'Show metadata', + children: [c ? Ni() : Cl(), 'Metadata'], + }), + v = m.jsxs('div', { + className: 'test-file-header-info', + children: [ + r && + m.jsxs('div', { + 'data-testid': 'project-name', + children: ['Project: ', l.projectNames[0]], + }), + u && + m.jsxs('div', { + 'data-testid': 'filtered-tests-count', + children: ['Filtered: ', u.total, ' ', !!u.total && '(' + Ol(u.duration) + ')'], + }), + o && h, + ], + }), + y = m.jsxs(m.Fragment, { + children: [ + m.jsx('div', { + 'data-testid': 'overall-time', + style: { marginRight: '10px' }, + children: l ? new Date(l.startTime).toLocaleString() : '', + }), + m.jsxs('div', { + 'data-testid': 'overall-duration', + children: ['Total time: ', Ol(l.duration ?? 0)], + }), + ], + }); + return m.jsxs(m.Fragment, { + children: [ + m.jsx(Or, { title: l.options.title, leftSuperHeader: v, rightSuperHeader: y }), + !o && h, + c && m.jsx(kv, { metadata: l.metadata }), + !!l.errors.length && + m.jsx(ke, { + header: 'Errors', + dataTestId: 'report-errors', + children: l.errors.map((A, E) => + m.jsx(wr, { code: A }, 'test-report-error-message-' + E) + ), + }), + ], + }); + }, + rm = (l) => { + const u = Math.round(l / 1e3), + c = Math.floor(u / 60), + f = u % 60; + return c === 0 ? `${f}s` : `${c}m ${f}s`; + }, + $v = ({ entries: l }) => { + const f = Math.max(...l.map((D) => D.label.length)) * 10, + o = { top: 20, right: 20, bottom: 40, left: Math.min(800 * 0.5, Math.max(50, f)) }, + h = 800 - o.left - o.right, + v = Math.min(...l.map((D) => D.startTime)), + y = Math.max(...l.map((D) => D.startTime + D.duration)); + let A, E; + const S = y - v; + S < 60 * 1e3 + ? ((A = 10 * 1e3), (E = !0)) + : S < 300 * 1e3 + ? ((A = 30 * 1e3), (E = !0)) + : S < 1800 * 1e3 + ? ((A = 300 * 1e3), (E = !1)) + : ((A = 600 * 1e3), (E = !1)); + const O = Math.ceil(v / A) * A, + X = (D, N) => { + const K = new Date(D).toLocaleTimeString(void 0, { + hour: '2-digit', + minute: '2-digit', + second: E ? '2-digit' : void 0, + }); + if (N) return K; + if (K.endsWith(' AM') || K.endsWith(' PM')) return K.slice(0, -3); + }, + b = (y - v) * 1.1, + p = Math.ceil(b / A) * A, + x = h / p, + R = 20, + U = 8, + Z = l.length * (R + U), + F = []; + for (let D = O; D <= v + p; D += A) { + const N = D - v; + F.push({ x: N * x, label: X(D, D === O) }); + } + const j = Z + o.top + o.bottom; + return m.jsx('svg', { + viewBox: `0 0 800 ${j}`, + preserveAspectRatio: 'xMidYMid meet', + style: { width: '100%', height: 'auto' }, + role: 'img', + children: m.jsxs('g', { + transform: `translate(${o.left}, ${o.top})`, + role: 'presentation', + children: [ + F.map(({ x: D, label: N }, K) => + m.jsxs( + 'g', + { + 'aria-hidden': 'true', + children: [ + m.jsx('line', { + x1: D, + y1: 0, + x2: D, + y2: Z, + stroke: 'var(--color-border-muted)', + strokeWidth: '1', + }), + m.jsx('text', { + x: D, + y: Z + 20, + textAnchor: 'middle', + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + children: N, + }), + ], + }, + K + ) + ), + l.map((D, N) => { + const K = D.startTime - v, + J = D.duration * x, + k = K * x, + nt = N * (R + U), + P = [ + 'var(--color-scale-blue-2)', + 'var(--color-scale-blue-3)', + 'var(--color-scale-blue-4)', + ], + st = P[N % P.length]; + return m.jsxs( + 'g', + { + role: 'listitem', + 'aria-label': D.tooltip, + children: [ + m.jsx('rect', { + className: 'gantt-bar', + x: k, + y: nt, + width: J, + height: R, + fill: st, + rx: '2', + tabIndex: 0, + children: m.jsx('title', { children: D.tooltip }), + }), + m.jsx('text', { + x: k + J + 6, + y: nt + R / 2, + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + 'aria-hidden': 'true', + children: rm(D.duration), + }), + m.jsx('text', { + x: -10, + y: nt + R / 2, + textAnchor: 'end', + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + 'aria-hidden': 'true', + children: D.label, + }), + ], + }, + N + ); + }), + m.jsx('line', { + x1: 0, + y1: 0, + x2: 0, + y2: Z, + stroke: 'var(--color-fg-muted)', + strokeWidth: '1', + 'aria-hidden': 'true', + }), + m.jsx('line', { + x1: 0, + y1: Z, + x2: h, + y2: Z, + stroke: 'var(--color-fg-muted)', + strokeWidth: '1', + 'aria-hidden': 'true', + }), + ], + }), + }); + }; + function ty({ report: l, tests: u }) { + return m.jsxs(m.Fragment, { + children: [m.jsx(ny, { report: l }), m.jsx(ey, { report: l, tests: u })], + }); + } + function ey({ report: l, tests: u }) { + const [c, f] = ue.useState(50); + return m.jsx(sm, { + file: { fileId: 'slowest', fileName: 'Slowest Tests', tests: u.slice(0, c), stats: null }, + projectNames: l.json().projectNames, + footer: + c < u.length + ? m.jsxs('button', { + className: 'link-badge fullwidth-link', + style: { padding: '8px 5px' }, + onClick: () => f((r) => r + 50), + children: [Ni(), 'Show 50 more'], + }) + : void 0, + }); + } + function ny({ report: l }) { + const u = l.json().machines; + if (u.length === 0) return null; + const c = u + .map((f) => { + const r = f.tag.join(' '), + o = new Date(f.startTime).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + }); + let h = `${r} started at ${o}, runs ${rm(f.duration)}`; + return ( + f.shardIndex && (h += ` (shard ${f.shardIndex})`), + { + label: r, + tooltip: h, + startTime: f.startTime, + duration: f.duration, + shardIndex: f.shardIndex ?? 1, + } + ); + }) + .sort((f, r) => f.label.localeCompare(r.label) || f.shardIndex - r.shardIndex); + return m.jsx(ke, { header: 'Timeline', children: m.jsx($v, { entries: c }) }); + } + const ay = (l) => !l.has('testId') && !l.has('speedboard'), + ly = (l) => l.has('testId'), + iy = (l) => l.has('speedboard') && !l.has('testId'), + uy = ({ report: l }) => { + var Z, F; + const u = se(), + [c, f] = ct.useState(new Map()), + [r, o] = ct.useState(u.get('q') || ''), + [h, v] = ct.useState(!1), + y = u.has('speedboard'), + [A] = _h('mergeFiles', !1), + E = u.get('testId'), + S = ((Z = u.get('q')) == null ? void 0 : Z.toString()) || '', + O = S ? '&q=' + S : '', + X = (F = l == null ? void 0 : l.json()) == null ? void 0 : F.options.title, + B = ct.useMemo(() => { + const j = new Map(); + for (const D of (l == null ? void 0 : l.json().files) || []) + for (const N of D.tests) j.set(N.testId, D.fileId); + return j; + }, [l]), + b = ct.useMemo(() => rc.parse(r), [r]), + p = ct.useMemo( + () => (b.empty() ? void 0 : sy((l == null ? void 0 : l.json().files) || [], b)), + [l, b] + ), + x = ct.useMemo(() => (y ? oy(l, b) : A ? ry(l, b) : fy(l, b)), [l, b, A, y]), + { prev: R, next: U } = ct.useMemo(() => { + const j = x.tests.findIndex((K) => K.testId === E), + D = j > 0 ? x.tests[j - 1] : void 0, + N = j < x.tests.length - 1 ? x.tests[j + 1] : void 0; + return { prev: D, next: N }; + }, [E, x]); + return ( + ct.useEffect(() => { + const j = (D) => { + if ( + D.target instanceof HTMLInputElement || + D.target instanceof HTMLTextAreaElement || + D.shiftKey || + D.ctrlKey || + D.metaKey || + D.altKey + ) + return; + const N = new URLSearchParams(u); + switch (D.key) { + case 'a': + (D.preventDefault(), ca('#?')); + break; + case 'p': + (D.preventDefault(), + N.delete('testId'), + N.delete('speedboard'), + ca(Na(N, 's:passed', !1))); + break; + case 'f': + (D.preventDefault(), + N.delete('testId'), + N.delete('speedboard'), + ca(Na(N, 's:failed', !1))); + break; + case 'ArrowLeft': + R && (D.preventDefault(), N.delete('testId'), ca(Cn({ test: R }, N) + O)); + break; + case 'ArrowRight': + U && (D.preventDefault(), N.delete('testId'), ca(Cn({ test: U }, N) + O)); + break; + } + }; + return ( + document.addEventListener('keydown', j), + () => document.removeEventListener('keydown', j) + ); + }, [R, U, O, S, u]), + ct.useEffect(() => { + X ? (document.title = X) : (document.title = 'Playwright Test Report'); + }, [X]), + m.jsx('div', { + className: 'htmlreport vbox px-4 pb-4', + children: m.jsxs('main', { + children: [ + l && m.jsx(Ev, { stats: l.json().stats, filterText: r, setFilterText: o }), + m.jsxs(Kf, { + predicate: ay, + children: [ + m.jsx(Y2, { + report: l == null ? void 0 : l.json(), + filteredStats: p, + metadataVisible: h, + toggleMetadataVisible: () => v((j) => !j), + }), + m.jsx(Pv, { + files: x.files, + expandedFiles: c, + setExpandedFiles: f, + projectNames: (l == null ? void 0 : l.json().projectNames) || [], + }), + ], + }), + m.jsxs(Kf, { + predicate: iy, + children: [ + m.jsx(Y2, { + report: l == null ? void 0 : l.json(), + filteredStats: p, + metadataVisible: h, + toggleMetadataVisible: () => v((j) => !j), + }), + l && m.jsx(ty, { report: l, tests: x.tests }), + ], + }), + m.jsx(Kf, { + predicate: ly, + children: + l && + m.jsx(cy, { report: l, next: U, prev: R, testId: E, testIdToFileIdMap: B }), + }), + ], + }), + }) + ); + }, + cy = ({ report: l, testIdToFileIdMap: u, next: c, prev: f, testId: r }) => { + const [o, h] = ct.useState('loading'), + v = +(se().get('run') || '0'); + if ( + (ct.useEffect(() => { + (async () => { + if (!r || (typeof o == 'object' && r === o.testId)) return; + const S = u.get(r); + if (!S) { + h('not-found'); + return; + } + const O = await l.entry(`${S}.json`); + h((O == null ? void 0 : O.tests.find((X) => X.testId === r)) || 'not-found'); + })(); + }, [o, l, r, u]), + o === 'loading') + ) + return m.jsx('div', { className: 'test-case-column' }); + if (o === 'not-found') + return m.jsxs('div', { + className: 'test-case-column', + children: [ + m.jsx(Or, { title: 'Test not found' }), + m.jsxs('div', { className: 'test-case-location', children: ['Test ID: ', r] }), + ], + }); + const { projectNames: y, metadata: A, options: E } = l.json(); + return m.jsx('div', { + className: 'test-case-column', + children: m.jsx(Vv, { + projectNames: y, + testRunMetadata: A, + options: E, + next: c, + prev: f, + test: o, + run: v, + }), + }); + }; + function sy(l, u) { + const c = { total: 0, duration: 0 }; + for (const f of l) { + const r = f.tests.filter((o) => u.matches(o)); + c.total += r.length; + for (const o of r) c.duration += o.duration; + } + return c; + } + function fy(l, u) { + const c = { files: [], tests: [] }; + for (const f of (l == null ? void 0 : l.json().files) || []) { + const r = f.tests.filter((o) => u.matches(o)); + (r.length && c.files.push({ ...f, tests: r }), c.tests.push(...r)); + } + return c; + } + function ry(l, u) { + const c = [], + f = new Map(); + for (const o of (l == null ? void 0 : l.json().files) || []) { + const h = o.tests.filter((v) => u.matches(v)); + for (const v of h) { + const y = v.path[0] ?? ''; + let A = f.get(y); + A || + ((A = { + fileId: y, + fileName: y, + tests: [], + stats: { total: 0, expected: 0, unexpected: 0, flaky: 0, skipped: 0, ok: !0 }, + }), + f.set(y, A), + c.push(A)); + const E = { ...v, path: v.path.slice(1) }; + A.tests.push(E); + } + } + c.sort((o, h) => o.fileName.localeCompare(h.fileName)); + const r = { files: c, tests: [] }; + for (const o of c) r.tests.push(...o.tests); + return r; + } + function oy(l, u) { + const f = ((l == null ? void 0 : l.json().files) || []) + .flatMap((r) => r.tests) + .filter((r) => u.matches(r)); + return (f.sort((r, o) => o.duration - r.duration), { files: [], tests: f }); + } + const dy = + "data:image/svg+xml,%3csvg%20width='400'%20height='400'%20viewBox='0%200%20400%20400'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M136.444%20221.556C123.558%20225.213%20115.104%20231.625%20109.535%20238.032C114.869%20233.364%20122.014%20229.08%20131.652%20226.348C141.51%20223.554%20149.92%20223.574%20156.869%20224.915V219.481C150.941%20218.939%20144.145%20219.371%20136.444%20221.556ZM108.946%20175.876L61.0895%20188.484C61.0895%20188.484%2061.9617%20189.716%2063.5767%20191.36L104.153%20180.668C104.153%20180.668%20103.578%20188.077%2098.5847%20194.705C108.03%20187.559%20108.946%20175.876%20108.946%20175.876ZM149.005%20288.347C81.6582%20306.486%2046.0272%20228.438%2035.2396%20187.928C30.2556%20169.229%2028.0799%20155.067%2027.5%20145.928C27.4377%20144.979%2027.4665%20144.179%2027.5336%20143.446C24.04%20143.657%2022.3674%20145.473%2022.7077%20150.721C23.2876%20159.855%2025.4633%20174.016%2030.4473%20192.721C41.2301%20233.225%2076.8659%20311.273%20144.213%20293.134C158.872%20289.185%20169.885%20281.992%20178.152%20272.81C170.532%20279.692%20160.995%20285.112%20149.005%20288.347ZM161.661%20128.11V132.903H188.077C187.535%20131.206%20186.989%20129.677%20186.447%20128.11H161.661Z'%20fill='%232D4552'/%3e%3cpath%20d='M193.981%20167.584C205.861%20170.958%20212.144%20179.287%20215.465%20186.658L228.711%20190.42C228.711%20190.42%20226.904%20164.623%20203.57%20157.995C181.741%20151.793%20168.308%20170.124%20166.674%20172.496C173.024%20167.972%20182.297%20164.268%20193.981%20167.584ZM299.422%20186.777C277.573%20180.547%20264.145%20198.916%20262.535%20201.255C268.89%20196.736%20278.158%20193.031%20289.837%20196.362C301.698%20199.741%20307.976%20208.06%20311.307%20215.436L324.572%20219.212C324.572%20219.212%20322.736%20193.41%20299.422%20186.777ZM286.262%20254.795L176.072%20223.99C176.072%20223.99%20177.265%20230.038%20181.842%20237.869L274.617%20263.805C282.255%20259.386%20286.262%20254.795%20286.262%20254.795ZM209.867%20321.102C122.618%20297.71%20133.166%20186.543%20147.284%20133.865C153.097%20112.156%20159.073%2096.0203%20164.029%2085.204C161.072%2084.5953%20158.623%2086.1529%20156.203%2091.0746C150.941%20101.747%20144.212%20119.124%20137.7%20143.45C123.586%20196.127%20113.038%20307.29%20200.283%20330.682C241.406%20341.699%20273.442%20324.955%20297.323%20298.659C274.655%20319.19%20245.714%20330.701%20209.867%20321.102Z'%20fill='%232D4552'/%3e%3cpath%20d='M161.661%20262.296V239.863L99.3324%20257.537C99.3324%20257.537%20103.938%20230.777%20136.444%20221.556C146.302%20218.762%20154.713%20218.781%20161.661%20220.123V128.11H192.869C189.471%20117.61%20186.184%20109.526%20183.423%20103.909C178.856%2094.612%20174.174%20100.775%20163.545%20109.665C156.059%20115.919%20137.139%20129.261%20108.668%20136.933C80.1966%20144.61%2057.179%20142.574%2047.5752%20140.911C33.9601%20138.562%2026.8387%20135.572%2027.5049%20145.928C28.0847%20155.062%2030.2605%20169.224%2035.2445%20187.928C46.0272%20228.433%2081.663%20306.481%20149.01%20288.342C166.602%20283.602%20179.019%20274.233%20187.626%20262.291H161.661V262.296ZM61.0848%20188.484L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6614%20203.743%2061.0848%20188.484%2061.0848%20188.484Z'%20fill='%23E2574C'/%3e%3cpath%20d='M341.786%20129.174C329.345%20131.355%20299.498%20134.072%20262.612%20124.185C225.716%20114.304%20201.236%2097.0224%20191.537%2088.8994C177.788%2077.3834%20171.74%2069.3802%20165.788%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.098C297.093%20344.47%20343.53%20242.92%20357.644%20190.238C364.157%20165.917%20367.013%20147.5%20367.799%20135.625C368.695%20122.173%20359.455%20126.078%20341.786%20129.174ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756ZM223.42%20268.713C182.403%20256.698%20176.077%20223.99%20176.077%20223.99L286.262%20254.796C286.262%20254.791%20264.021%20280.578%20223.42%20268.713ZM262.377%20201.495C262.377%20201.495%20276.107%20180.126%20299.422%20186.773C322.736%20193.411%20324.572%20219.208%20324.572%20219.208L262.377%20201.495Z'%20fill='%232EAD33'/%3e%3cpath%20d='M139.88%20246.04L99.3324%20257.532C99.3324%20257.532%20103.737%20232.44%20133.607%20222.496L110.647%20136.33L108.663%20136.933C80.1918%20144.611%2057.1742%20142.574%2047.5704%20140.911C33.9554%20138.563%2026.834%20135.572%2027.5001%20145.929C28.08%20155.063%2030.2557%20169.224%2035.2397%20187.929C46.0225%20228.433%2081.6583%20306.481%20149.005%20288.342L150.989%20287.719L139.88%20246.04ZM61.0848%20188.485L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6615%20203.743%2061.0848%20188.485%2061.0848%20188.485Z'%20fill='%23D65348'/%3e%3cpath%20d='M225.27%20269.163L223.415%20268.712C182.398%20256.698%20176.072%20223.99%20176.072%20223.99L232.89%20239.872L262.971%20124.281L262.607%20124.185C225.711%20114.304%20201.232%2097.0224%20191.532%2088.8994C177.783%2077.3834%20171.735%2069.3802%20165.783%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.097L211.655%20321.5L225.27%20269.163ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756Z'%20fill='%231D8D22'/%3e%3cpath%20d='M141.946%20245.451L131.072%20248.537C133.641%20263.019%20138.169%20276.917%20145.276%20289.195C146.513%20288.922%20147.74%20288.687%20149%20288.342C152.302%20287.451%20155.364%20286.348%20158.312%20285.145C150.371%20273.361%20145.118%20259.789%20141.946%20245.451ZM137.7%20143.451C132.112%20164.307%20127.113%20194.326%20128.489%20224.436C130.952%20223.367%20133.554%20222.371%20136.444%20221.551L138.457%20221.101C136.003%20188.939%20141.308%20156.165%20147.284%20133.866C148.799%20128.225%20150.318%20122.978%20151.832%20118.085C149.393%20119.637%20146.767%20121.228%20143.776%20122.867C141.759%20129.093%20139.722%20135.898%20137.7%20143.451Z'%20fill='%23C04B41'/%3e%3c/svg%3e", + Ff = N5, + Rr = document.createElement('link'); + Rr.rel = 'shortcut icon'; + Rr.href = dy; + document.head.appendChild(Rr); + const hy = () => { + const [l, u] = ct.useState(); + return ( + ct.useEffect(() => { + const c = new my(); + c.load().then(() => { + var f; + ((f = document.getElementById('playwrightReportBase64')) == null || f.remove(), u(c)); + }); + }, []), + m.jsx(cv, { children: m.jsx(uy, { report: l }) }) + ); + }; + window.onload = () => { + (gv(), X5.createRoot(document.querySelector('#root')).render(m.jsx(hy, {}))); + }; + class my { + constructor() { + yn(this, '_entries', new Map()); + yn(this, '_json'); + } + async load() { + const u = document.getElementById('playwrightReportBase64').textContent, + c = new Ff.ZipReader(new Ff.Data64URIReader(u), { useWebWorkers: !1 }); + for (const f of await c.getEntries()) this._entries.set(f.filename, f); + this._json = await this.entry('report.json'); + } + json() { + return this._json; + } + async entry(u) { + const c = this._entries.get(u), + f = new Ff.TextWriter(); + return (await c.getData(f), JSON.parse(await f.getData())); + } + } + + -
+
- \ No newline at end of file + -- cgit v1.2.3