diff --git a/adminfront/playwright-report/index.html b/adminfront/playwright-report/index.html index 8371ce48..b2747991 100644 --- a/adminfront/playwright-report/index.html +++ b/adminfront/playwright-report/index.html @@ -1,85 +1,24255 @@ - - - - + + - - - + + + 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 + diff --git a/adminfront/src/app/routes.tsx b/adminfront/src/app/routes.tsx index 913491e2..e45b966f 100644 --- a/adminfront/src/app/routes.tsx +++ b/adminfront/src/app/routes.tsx @@ -3,23 +3,23 @@ import AppLayout from "../components/layout/AppLayout"; import ApiKeyCreatePage from "../features/api-keys/ApiKeyCreatePage"; import ApiKeyListPage from "../features/api-keys/ApiKeyListPage"; import AuditLogsPage from "../features/audit/AuditLogsPage"; +import AuthCallbackPage from "../features/auth/AuthCallbackPage"; import AuthPage from "../features/auth/AuthPage"; +import LoginPage from "../features/auth/LoginPage"; import DashboardPage from "../features/dashboard/DashboardPage"; import GlobalOverviewPage from "../features/overview/GlobalOverviewPage"; -import LoginPage from "../features/auth/LoginPage"; -import AuthCallbackPage from "../features/auth/AuthCallbackPage"; +import TenantGroupAdminsTab from "../features/tenant-groups/routes/TenantGroupAdminsTab"; import TenantGroupCreatePage from "../features/tenant-groups/routes/TenantGroupCreatePage"; import TenantGroupDetailPage from "../features/tenant-groups/routes/TenantGroupDetailPage"; import TenantGroupListPage from "../features/tenant-groups/routes/TenantGroupListPage"; import TenantGroupProfileTab from "../features/tenant-groups/routes/TenantGroupProfileTab"; import TenantGroupTenantsTab from "../features/tenant-groups/routes/TenantGroupTenantsTab"; -import TenantGroupAdminsTab from "../features/tenant-groups/routes/TenantGroupAdminsTab"; +import TenantAdminsTab from "../features/tenants/routes/TenantAdminsTab"; import TenantCreatePage from "../features/tenants/routes/TenantCreatePage"; import TenantDetailPage from "../features/tenants/routes/TenantDetailPage"; import TenantListPage from "../features/tenants/routes/TenantListPage"; import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage"; import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage"; -import TenantAdminsTab from "../features/tenants/routes/TenantAdminsTab"; import UserCreatePage from "../features/users/UserCreatePage"; import UserDetailPage from "../features/users/UserDetailPage"; import UserListPage from "../features/users/UserListPage"; diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index f0488612..1894847b 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -4,134 +4,143 @@ import { Key, KeyRound, LayoutDashboard, - LayoutGrid, - LogOut, - Moon, - NotebookTabs, - Rocket, - ShieldHalf, - Sun, - Users, - } from "lucide-react"; - import { useEffect, useState } from "react"; - import { NavLink, Outlet, useNavigate } from "react-router-dom"; - import { t } from "../../lib/i18n"; - import RoleSwitcher from "./RoleSwitcher"; - - const navItems = [ - { label: "ui.admin.nav.overview", to: "/", icon: LayoutDashboard }, - { - label: "ui.admin.nav.tenant_dashboard", - to: "/dashboard", - icon: ShieldHalf, - }, - { label: "ui.admin.nav.tenant_groups", to: "/tenant-groups", icon: LayoutGrid }, - { label: "ui.admin.nav.tenants", to: "/tenants", icon: Building2 }, - { label: "ui.admin.nav.users", to: "/users", icon: Users }, { label: "ui.admin.nav.api_keys", to: "/api-keys", icon: Key }, - { label: "ui.admin.nav.audit_logs", to: "/audit-logs", icon: NotebookTabs }, - { label: "ui.admin.nav.auth_guard", to: "/auth", icon: KeyRound }, - ]; - function AppLayout() { - const navigate = useNavigate(); - const [theme, setTheme] = useState<"light" | "dark">(() => { - const stored = window.localStorage.getItem("admin_theme"); - return stored === "dark" ? "dark" : "light"; - }); - - const handleLogout = () => { - if (window.confirm(t("msg.admin.logout_confirm", "로그아웃 하시겠습니까?"))) { - window.localStorage.removeItem("admin_session"); - navigate("/login"); - } - }; - - useEffect(() => { - const session = window.localStorage.getItem("admin_session"); - if (!session) { - navigate("/login"); - } - }, [navigate]); - - useEffect(() => { - const root = document.documentElement; - root.classList.remove("light", "dark"); - if (theme === "light") { - root.classList.add("light"); - } else { - root.classList.add("dark"); - } - window.localStorage.setItem("admin_theme", theme); - }, [theme]); - - const toggleTheme = () => { - setTheme((prev) => (prev === "light" ? "dark" : "light")); - }; - - return ( -
-