doc-detective 4.0.2-dev.1 → 4.0.2-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/adapters/claude-code.d.ts +77 -0
- package/dist/agents/adapters/claude-code.d.ts.map +1 -0
- package/dist/agents/adapters/claude-code.js +461 -0
- package/dist/agents/adapters/claude-code.js.map +1 -0
- package/dist/agents/adapters/codex.d.ts +64 -0
- package/dist/agents/adapters/codex.d.ts.map +1 -0
- package/dist/agents/adapters/codex.js +299 -0
- package/dist/agents/adapters/codex.js.map +1 -0
- package/dist/agents/adapters/copilot-cli.d.ts +29 -0
- package/dist/agents/adapters/copilot-cli.d.ts.map +1 -0
- package/dist/agents/adapters/copilot-cli.js +195 -0
- package/dist/agents/adapters/copilot-cli.js.map +1 -0
- package/dist/agents/adapters/gemini-cli.d.ts +29 -0
- package/dist/agents/adapters/gemini-cli.d.ts.map +1 -0
- package/dist/agents/adapters/gemini-cli.js +207 -0
- package/dist/agents/adapters/gemini-cli.js.map +1 -0
- package/dist/agents/adapters/opencode.d.ts +67 -0
- package/dist/agents/adapters/opencode.d.ts.map +1 -0
- package/dist/agents/adapters/opencode.js +341 -0
- package/dist/agents/adapters/opencode.js.map +1 -0
- package/dist/agents/adapters/qwen-code.d.ts +30 -0
- package/dist/agents/adapters/qwen-code.d.ts.map +1 -0
- package/dist/agents/adapters/qwen-code.js +212 -0
- package/dist/agents/adapters/qwen-code.js.map +1 -0
- package/dist/agents/command.d.ts +11 -0
- package/dist/agents/command.d.ts.map +1 -0
- package/dist/agents/command.js +41 -0
- package/dist/agents/command.js.map +1 -0
- package/dist/agents/fetcher.d.ts +30 -0
- package/dist/agents/fetcher.d.ts.map +1 -0
- package/dist/agents/fetcher.js +112 -0
- package/dist/agents/fetcher.js.map +1 -0
- package/dist/agents/prompts.d.ts +24 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +74 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/agents/registry.d.ts +4 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +25 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/runner.d.ts +13 -0
- package/dist/agents/runner.d.ts.map +1 -0
- package/dist/agents/runner.js +155 -0
- package/dist/agents/runner.js.map +1 -0
- package/dist/agents/spawn-helper.d.ts +33 -0
- package/dist/agents/spawn-helper.d.ts.map +1 -0
- package/dist/agents/spawn-helper.js +98 -0
- package/dist/agents/spawn-helper.js.map +1 -0
- package/dist/agents/types.d.ts +41 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/cli.js +42 -10
- package/dist/cli.js.map +1 -1
- package/dist/common/src/detectTests.d.ts.map +1 -1
- package/dist/common/src/detectTests.js +43 -12
- package/dist/common/src/detectTests.js.map +1 -1
- package/dist/common/src/fileTypes.d.ts.map +1 -1
- package/dist/common/src/fileTypes.js +10 -0
- package/dist/common/src/fileTypes.js.map +1 -1
- package/dist/common/src/schemas/schemas.json +594 -0
- package/dist/common/src/types/generated/checkLink_v3.d.ts +15 -0
- package/dist/common/src/types/generated/checkLink_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/config_v3.d.ts +4 -0
- package/dist/common/src/types/generated/config_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts +4 -0
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/step_v3.d.ts +15 -0
- package/dist/common/src/types/generated/step_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/test_v3.d.ts +30 -0
- package/dist/common/src/types/generated/test_v3.d.ts.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +10 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/integrations/heretto.d.ts.map +1 -1
- package/dist/core/integrations/heretto.js +11 -3
- package/dist/core/integrations/heretto.js.map +1 -1
- package/dist/core/tests/checkLink.d.ts.map +1 -1
- package/dist/core/tests/checkLink.js +136 -29
- package/dist/core/tests/checkLink.js.map +1 -1
- package/dist/core/tests/loadCookie.d.ts.map +1 -1
- package/dist/core/tests/loadCookie.js +12 -2
- package/dist/core/tests/loadCookie.js.map +1 -1
- package/dist/core/tests/saveScreenshot.d.ts +15 -1
- package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
- package/dist/core/tests/saveScreenshot.js +41 -17
- package/dist/core/tests/saveScreenshot.js.map +1 -1
- package/dist/index.cjs +816 -59
- package/dist/reporters/htmlReporter.d.ts +2 -0
- package/dist/reporters/htmlReporter.d.ts.map +1 -0
- package/dist/reporters/htmlReporter.js +1589 -0
- package/dist/reporters/htmlReporter.js.map +1 -0
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +43 -10
- package/dist/utils.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function htmlReporter(config = {}, outputPath, results, options = {}) {
|
|
4
|
+
const outputExtensions = [".html", ".htm"];
|
|
5
|
+
outputPath = path.resolve(outputPath);
|
|
6
|
+
let outputFile = "";
|
|
7
|
+
let outputDir = "";
|
|
8
|
+
let reportType = "doc-detective-results";
|
|
9
|
+
if (options.command) {
|
|
10
|
+
if (options.command === "runCoverage") {
|
|
11
|
+
reportType = "coverageResults";
|
|
12
|
+
}
|
|
13
|
+
else if (options.command === "runTests") {
|
|
14
|
+
reportType = "testResults";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (outputExtensions.some((ext) => outputPath.endsWith(ext))) {
|
|
18
|
+
outputDir = path.dirname(outputPath);
|
|
19
|
+
outputFile = outputPath;
|
|
20
|
+
if (fs.existsSync(outputFile)) {
|
|
21
|
+
let counter = 0;
|
|
22
|
+
const ext = path.extname(outputFile);
|
|
23
|
+
const base = outputFile.slice(0, -ext.length);
|
|
24
|
+
while (fs.existsSync(`${base}-${counter}${ext}`)) {
|
|
25
|
+
counter++;
|
|
26
|
+
}
|
|
27
|
+
outputFile = `${base}-${counter}${ext}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
outputDir = outputPath;
|
|
32
|
+
outputFile = path.resolve(outputDir, `${reportType}-${Date.now()}.html`);
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
if (!fs.existsSync(outputDir)) {
|
|
36
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
const html = buildHtml(results);
|
|
39
|
+
fs.writeFileSync(outputFile, html);
|
|
40
|
+
console.log(`See HTML report at ${outputFile}\n`);
|
|
41
|
+
return outputFile;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.error(`Error writing HTML report to ${outputFile}. ${err}`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function buildHtml(results) {
|
|
49
|
+
const reportJson = JSON.stringify(results, null, 2)
|
|
50
|
+
.replace(/</g, "\\u003c")
|
|
51
|
+
.replace(/>/g, "\\u003e")
|
|
52
|
+
.replace(/\u2028/g, "\\u2028")
|
|
53
|
+
.replace(/\u2029/g, "\\u2029");
|
|
54
|
+
return `<!DOCTYPE html>
|
|
55
|
+
<html lang="en">
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="utf-8"/>
|
|
58
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
59
|
+
<title>Doc Detective — Test Report</title>
|
|
60
|
+
<style>
|
|
61
|
+
${CSS_CONTENT}
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<div id="root"></div>
|
|
66
|
+
<script id="dd-report-data" type="application/json">${reportJson}</script>
|
|
67
|
+
<script>
|
|
68
|
+
window.REPORT_DATA = JSON.parse(document.getElementById("dd-report-data").textContent);
|
|
69
|
+
</script>
|
|
70
|
+
<script>
|
|
71
|
+
${JS_CONTENT}
|
|
72
|
+
</script>
|
|
73
|
+
</body>
|
|
74
|
+
</html>`;
|
|
75
|
+
}
|
|
76
|
+
const CSS_CONTENT = `
|
|
77
|
+
|
|
78
|
+
:root {
|
|
79
|
+
--dd-green: #4B9A47;
|
|
80
|
+
--dd-green-deep: #22623D;
|
|
81
|
+
--dd-green-bright: #3EB16E;
|
|
82
|
+
--dd-green-electric: #00C122;
|
|
83
|
+
--dd-green-forest: #2E8555;
|
|
84
|
+
--dd-green-tint: #E8F3E7;
|
|
85
|
+
--dd-green-tint-2: #D2E8CE;
|
|
86
|
+
--dd-ink: #0D0E11;
|
|
87
|
+
--dd-ink-2: #1A1C21;
|
|
88
|
+
--dd-ink-3: #2A2D34;
|
|
89
|
+
--dd-gray-900: #3A3F47;
|
|
90
|
+
--dd-gray-700: #5B616B;
|
|
91
|
+
--dd-gray-500: #8A909B;
|
|
92
|
+
--dd-gray-300: #C7CBD3;
|
|
93
|
+
--dd-gray-200: #E2E5EA;
|
|
94
|
+
--dd-gray-100: #F1F3F6;
|
|
95
|
+
--dd-gray-50: #F7F8FA;
|
|
96
|
+
--dd-paper: #FFFFFF;
|
|
97
|
+
--dd-pass: #22623D;
|
|
98
|
+
--dd-pass-bg: #E8F3E7;
|
|
99
|
+
--dd-fail: #B0261A;
|
|
100
|
+
--dd-fail-bg: #FBEAE7;
|
|
101
|
+
--dd-warn: #8A5A00;
|
|
102
|
+
--dd-warn-bg: #FBF1DB;
|
|
103
|
+
--dd-skip: #4A5058;
|
|
104
|
+
--dd-skip-bg: #F1F3F6;
|
|
105
|
+
--dd-info: #2563A0;
|
|
106
|
+
--dd-info-bg: #E5EEF7;
|
|
107
|
+
--dd-code-bg: #0D0E11;
|
|
108
|
+
--dd-code-fg: #E6E8EC;
|
|
109
|
+
--fg1: var(--dd-ink);
|
|
110
|
+
--fg2: #4A5058;
|
|
111
|
+
--fg3: #606770;
|
|
112
|
+
--fg-inverse: var(--dd-paper);
|
|
113
|
+
--fg-brand: var(--dd-green-deep);
|
|
114
|
+
--bg1: var(--dd-paper);
|
|
115
|
+
--bg2: var(--dd-gray-50);
|
|
116
|
+
--bg3: var(--dd-gray-100);
|
|
117
|
+
--bg-brand: var(--dd-green-tint);
|
|
118
|
+
--bg-ink: var(--dd-ink);
|
|
119
|
+
--border-subtle: var(--dd-gray-200);
|
|
120
|
+
--border-strong: var(--dd-gray-300);
|
|
121
|
+
--border-brand: var(--dd-green-bright);
|
|
122
|
+
--shadow-xs: 0 1px 2px rgba(13,14,17,0.04);
|
|
123
|
+
--shadow-sm: 0 1px 2px rgba(13,14,17,0.06), 0 1px 1px rgba(13,14,17,0.04);
|
|
124
|
+
--shadow-md: 0 4px 10px rgba(13,14,17,0.06), 0 2px 4px rgba(13,14,17,0.04);
|
|
125
|
+
--shadow-lg: 0 16px 32px rgba(13,14,17,0.08), 0 4px 8px rgba(13,14,17,0.04);
|
|
126
|
+
--shadow-focus: 0 0 0 3px rgba(62,177,110,0.35);
|
|
127
|
+
--radius-xs: 4px;
|
|
128
|
+
--radius-sm: 6px;
|
|
129
|
+
--radius-md: 8px;
|
|
130
|
+
--radius-lg: 12px;
|
|
131
|
+
--radius-xl: 16px;
|
|
132
|
+
--radius-pill: 999px;
|
|
133
|
+
--space-1: 4px;
|
|
134
|
+
--space-2: 8px;
|
|
135
|
+
--space-3: 12px;
|
|
136
|
+
--space-4: 16px;
|
|
137
|
+
--space-5: 24px;
|
|
138
|
+
--space-6: 32px;
|
|
139
|
+
--space-7: 48px;
|
|
140
|
+
--space-8: 64px;
|
|
141
|
+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
142
|
+
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
143
|
+
--font-display: 'Inter', sans-serif;
|
|
144
|
+
--fs-display: 56px;
|
|
145
|
+
--fs-h1: 40px;
|
|
146
|
+
--fs-h2: 30px;
|
|
147
|
+
--fs-h3: 22px;
|
|
148
|
+
--fs-h4: 18px;
|
|
149
|
+
--fs-body: 16px;
|
|
150
|
+
--fs-small: 14px;
|
|
151
|
+
--fs-micro: 12px;
|
|
152
|
+
--fs-code: 14.5px;
|
|
153
|
+
--lh-tight: 1.15;
|
|
154
|
+
--lh-snug: 1.3;
|
|
155
|
+
--lh-normal: 1.55;
|
|
156
|
+
--lh-loose: 1.7;
|
|
157
|
+
--tracking-tight: -0.02em;
|
|
158
|
+
--tracking-normal: 0;
|
|
159
|
+
--tracking-wide: 0.04em;
|
|
160
|
+
--tracking-caps: 0.08em;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
* { box-sizing: border-box; }
|
|
164
|
+
html, body { margin: 0; padding: 0; }
|
|
165
|
+
body {
|
|
166
|
+
font-family: var(--font-sans);
|
|
167
|
+
font-size: var(--fs-body);
|
|
168
|
+
line-height: var(--lh-normal);
|
|
169
|
+
color: var(--fg1);
|
|
170
|
+
background: var(--bg2);
|
|
171
|
+
-webkit-font-smoothing: antialiased;
|
|
172
|
+
text-rendering: optimizeLegibility;
|
|
173
|
+
}
|
|
174
|
+
button { font: inherit; }
|
|
175
|
+
|
|
176
|
+
.app {
|
|
177
|
+
min-height: 100vh;
|
|
178
|
+
display: grid;
|
|
179
|
+
grid-template-rows: auto 1fr;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Header */
|
|
183
|
+
.hdr {
|
|
184
|
+
background: var(--dd-ink);
|
|
185
|
+
color: #F1F3F6;
|
|
186
|
+
border-bottom: 1px solid #000;
|
|
187
|
+
position: relative;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
}
|
|
190
|
+
.hdr::after {
|
|
191
|
+
content: "";
|
|
192
|
+
position: absolute; inset: auto 0 0 0;
|
|
193
|
+
height: 3px;
|
|
194
|
+
background: linear-gradient(90deg,
|
|
195
|
+
var(--dd-pass) 0%, var(--dd-pass) var(--pct-pass,0%),
|
|
196
|
+
var(--dd-fail) var(--pct-pass,0%), var(--dd-fail) var(--pct-fail-end,0%),
|
|
197
|
+
var(--dd-warn) var(--pct-fail-end,0%), var(--dd-warn) var(--pct-warn-end,0%),
|
|
198
|
+
var(--dd-skip) var(--pct-warn-end,0%), var(--dd-skip) 100%);
|
|
199
|
+
}
|
|
200
|
+
.hdr-inner {
|
|
201
|
+
max-width: 1280px; margin: 0 auto;
|
|
202
|
+
padding: 18px 28px 20px;
|
|
203
|
+
display: flex; align-items: center; gap: 18px;
|
|
204
|
+
}
|
|
205
|
+
.brand { display: flex; align-items: center; gap: 12px; }
|
|
206
|
+
.brand svg { width: 30px; height: 30px; }
|
|
207
|
+
.brand .wm {
|
|
208
|
+
font-weight: 800; font-size: 15px; letter-spacing: -0.01em;
|
|
209
|
+
}
|
|
210
|
+
.brand .wm .tag { color: var(--dd-green-bright); }
|
|
211
|
+
.brand .divider {
|
|
212
|
+
width: 1px; height: 22px; background: #2A2D34; margin: 0 6px;
|
|
213
|
+
}
|
|
214
|
+
.hdr-title { flex: 1; min-width: 0; }
|
|
215
|
+
.hdr-title .eyebrow {
|
|
216
|
+
font-family: var(--font-mono); font-size: 11px;
|
|
217
|
+
color: var(--dd-gray-500); letter-spacing: 0.08em; text-transform: uppercase;
|
|
218
|
+
margin-bottom: 3px;
|
|
219
|
+
}
|
|
220
|
+
.hdr-title h1 {
|
|
221
|
+
margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -0.01em;
|
|
222
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
223
|
+
}
|
|
224
|
+
.hdr-title h1 .sub { color: var(--dd-gray-500); font-weight: 500; }
|
|
225
|
+
.hdr-actions { display: flex; gap: 8px; align-items: center; }
|
|
226
|
+
.hdr-btn {
|
|
227
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
228
|
+
padding: 8px 12px; border-radius: 8px;
|
|
229
|
+
border: 1px solid #2A2D34; background: #15171B; color: #E6E8EC;
|
|
230
|
+
font-size: 13px; font-weight: 500; cursor: pointer;
|
|
231
|
+
transition: background .15s, border-color .15s;
|
|
232
|
+
}
|
|
233
|
+
.hdr-btn:hover { background: #1F222A; border-color: #3A3F47; }
|
|
234
|
+
.hdr-btn.primary { background: var(--dd-green-bright); color: #07150C; border-color: transparent; font-weight: 600; }
|
|
235
|
+
.hdr-btn.primary:hover { background: #4DC482; }
|
|
236
|
+
|
|
237
|
+
/* Meta strip */
|
|
238
|
+
.metastrip {
|
|
239
|
+
background: #15171B;
|
|
240
|
+
color: var(--dd-gray-300);
|
|
241
|
+
border-top: 1px solid #000;
|
|
242
|
+
font-family: var(--font-mono); font-size: 12px;
|
|
243
|
+
}
|
|
244
|
+
.metastrip-inner {
|
|
245
|
+
max-width: 1280px; margin: 0 auto;
|
|
246
|
+
padding: 10px 28px;
|
|
247
|
+
display: flex; flex-wrap: wrap; gap: 24px;
|
|
248
|
+
}
|
|
249
|
+
.metastrip .m { display: inline-flex; gap: 6px; align-items: baseline; }
|
|
250
|
+
.metastrip .m .k { color: var(--dd-gray-500); }
|
|
251
|
+
.metastrip .m .v { color: #E6E8EC; }
|
|
252
|
+
|
|
253
|
+
/* Main */
|
|
254
|
+
main {
|
|
255
|
+
max-width: 1280px; margin: 0 auto; width: 100%;
|
|
256
|
+
padding: 32px 28px 96px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Verdict */
|
|
260
|
+
.verdict {
|
|
261
|
+
display: grid;
|
|
262
|
+
grid-template-columns: minmax(320px, 420px) 1fr;
|
|
263
|
+
gap: 20px;
|
|
264
|
+
margin-bottom: 32px;
|
|
265
|
+
}
|
|
266
|
+
@media (max-width: 900px) {
|
|
267
|
+
.verdict { grid-template-columns: 1fr; }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.verdict-card {
|
|
271
|
+
background: var(--dd-paper);
|
|
272
|
+
border: 1px solid var(--border-subtle);
|
|
273
|
+
border-radius: var(--radius-xl);
|
|
274
|
+
padding: 22px 24px;
|
|
275
|
+
position: relative;
|
|
276
|
+
overflow: hidden;
|
|
277
|
+
}
|
|
278
|
+
.verdict-card .vk {
|
|
279
|
+
font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
|
|
280
|
+
text-transform: uppercase; color: var(--fg3);
|
|
281
|
+
margin-bottom: 8px;
|
|
282
|
+
}
|
|
283
|
+
.verdict-card .vv {
|
|
284
|
+
display: flex; align-items: baseline; gap: 12px;
|
|
285
|
+
}
|
|
286
|
+
.verdict-card .vv .big {
|
|
287
|
+
font-size: 48px; font-weight: 800; letter-spacing: -0.02em; line-height: 1;
|
|
288
|
+
font-family: var(--font-mono);
|
|
289
|
+
}
|
|
290
|
+
.verdict-card.fail .vv .big { color: var(--dd-fail); }
|
|
291
|
+
.verdict-card.warn .vv .big { color: var(--dd-warn); }
|
|
292
|
+
.verdict-card.pass .vv .big { color: var(--dd-pass); }
|
|
293
|
+
.verdict-card.skip .vv .big { color: var(--dd-skip); }
|
|
294
|
+
.verdict-card .vv .note {
|
|
295
|
+
color: var(--fg2); font-size: 14px; line-height: 1.4;
|
|
296
|
+
}
|
|
297
|
+
.verdict-card .vbar {
|
|
298
|
+
margin-top: 18px; height: 8px; border-radius: 999px; overflow: hidden;
|
|
299
|
+
display: grid;
|
|
300
|
+
background: var(--bg3);
|
|
301
|
+
}
|
|
302
|
+
.verdict-card .vbar span { display: block; }
|
|
303
|
+
.verdict-card .vbar .pass { background: var(--dd-pass); }
|
|
304
|
+
.verdict-card .vbar .fail { background: var(--dd-fail); }
|
|
305
|
+
.verdict-card .vbar .warn { background: var(--dd-warn); }
|
|
306
|
+
.verdict-card .vbar .skip { background: var(--dd-skip); }
|
|
307
|
+
|
|
308
|
+
/* Summary tiles */
|
|
309
|
+
.summary {
|
|
310
|
+
display: grid; grid-template-columns: repeat(4, 1fr);
|
|
311
|
+
gap: 12px;
|
|
312
|
+
}
|
|
313
|
+
@media (max-width: 900px) {
|
|
314
|
+
.summary { grid-template-columns: repeat(2, 1fr); }
|
|
315
|
+
}
|
|
316
|
+
.sum {
|
|
317
|
+
background: var(--dd-paper);
|
|
318
|
+
border: 1px solid var(--border-subtle);
|
|
319
|
+
border-radius: var(--radius-lg);
|
|
320
|
+
padding: 16px 18px 14px;
|
|
321
|
+
position: relative;
|
|
322
|
+
display: flex; flex-direction: column; justify-content: space-between;
|
|
323
|
+
min-height: 132px;
|
|
324
|
+
overflow: hidden;
|
|
325
|
+
}
|
|
326
|
+
.sum .lbl {
|
|
327
|
+
font-family: var(--font-mono);
|
|
328
|
+
font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
|
|
329
|
+
text-transform: uppercase; color: var(--fg3);
|
|
330
|
+
}
|
|
331
|
+
.sum .row { display: flex; align-items: baseline; gap: 10px; margin-top: 6px; }
|
|
332
|
+
.sum .num { font-size: 30px; font-weight: 800; line-height: 1; font-family: var(--font-mono); letter-spacing: -0.01em; }
|
|
333
|
+
.sum .of { font-size: 12px; color: var(--fg3); font-family: var(--font-mono); }
|
|
334
|
+
.sum .miniBar {
|
|
335
|
+
margin-top: 14px; height: 6px; border-radius: 999px; overflow: hidden;
|
|
336
|
+
display: grid; background: var(--bg3);
|
|
337
|
+
}
|
|
338
|
+
.sum .miniBar span { display: block; }
|
|
339
|
+
.sum .legend {
|
|
340
|
+
margin-top: 8px; display: flex; gap: 10px; flex-wrap: wrap;
|
|
341
|
+
font-family: var(--font-mono); font-size: 11px; color: var(--fg3);
|
|
342
|
+
}
|
|
343
|
+
.sum .legend i {
|
|
344
|
+
display: inline-block; width: 8px; height: 8px; border-radius: 2px;
|
|
345
|
+
margin-right: 4px; vertical-align: 1px;
|
|
346
|
+
}
|
|
347
|
+
.sum.pass .num { color: var(--dd-pass); }
|
|
348
|
+
.sum.fail .num { color: var(--dd-fail); }
|
|
349
|
+
.sum.warn .num { color: var(--dd-warn); }
|
|
350
|
+
.sum.skip .num { color: var(--dd-skip); }
|
|
351
|
+
.sum .miniBar .p { background: var(--dd-pass); }
|
|
352
|
+
.sum .miniBar .f { background: var(--dd-fail); }
|
|
353
|
+
.sum .miniBar .w { background: var(--dd-warn); }
|
|
354
|
+
.sum .miniBar .s { background: var(--dd-skip); }
|
|
355
|
+
.sum .corner-stripe {
|
|
356
|
+
position: absolute; top: 0; left: 0; bottom: 0; width: 3px;
|
|
357
|
+
}
|
|
358
|
+
.sum.pass .corner-stripe { background: var(--dd-pass); }
|
|
359
|
+
.sum.fail .corner-stripe { background: var(--dd-fail); }
|
|
360
|
+
.sum.warn .corner-stripe { background: var(--dd-warn); }
|
|
361
|
+
.sum.skip .corner-stripe { background: var(--dd-skip); }
|
|
362
|
+
|
|
363
|
+
/* Toolbar */
|
|
364
|
+
.toolbar {
|
|
365
|
+
display: flex; gap: 8px; align-items: center;
|
|
366
|
+
margin: 28px 0 14px;
|
|
367
|
+
flex-wrap: wrap;
|
|
368
|
+
}
|
|
369
|
+
.toolbar h2 {
|
|
370
|
+
margin: 0 8px 0 0;
|
|
371
|
+
font-size: 15px; font-weight: 700; letter-spacing: -0.005em;
|
|
372
|
+
color: var(--fg1);
|
|
373
|
+
}
|
|
374
|
+
.toolbar .count {
|
|
375
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--fg3);
|
|
376
|
+
margin-right: auto;
|
|
377
|
+
}
|
|
378
|
+
.filter {
|
|
379
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
380
|
+
padding: 6px 10px; border-radius: 999px;
|
|
381
|
+
border: 1px solid var(--border-subtle);
|
|
382
|
+
background: var(--dd-paper);
|
|
383
|
+
font-size: 12px; font-weight: 600; color: var(--fg2);
|
|
384
|
+
font-family: var(--font-mono);
|
|
385
|
+
cursor: pointer; transition: background .12s, border-color .12s, color .12s;
|
|
386
|
+
letter-spacing: 0.04em;
|
|
387
|
+
}
|
|
388
|
+
.filter:hover { border-color: var(--border-strong); color: var(--fg1); }
|
|
389
|
+
.filter .d {
|
|
390
|
+
width: 8px; height: 8px; border-radius: 999px;
|
|
391
|
+
}
|
|
392
|
+
.filter.pass .d { background: var(--dd-pass); }
|
|
393
|
+
.filter.fail .d { background: var(--dd-fail); }
|
|
394
|
+
.filter.warn .d { background: var(--dd-warn); }
|
|
395
|
+
.filter.skip .d { background: var(--dd-skip); }
|
|
396
|
+
.filter.all .d { background: var(--fg2); }
|
|
397
|
+
.filter.active.pass { background: var(--dd-pass-bg); color: var(--dd-pass); border-color: color-mix(in oklab, var(--dd-pass) 35%, transparent); }
|
|
398
|
+
.filter.active.fail { background: var(--dd-fail-bg); color: var(--dd-fail); border-color: color-mix(in oklab, var(--dd-fail) 35%, transparent); }
|
|
399
|
+
.filter.active.warn { background: var(--dd-warn-bg); color: var(--dd-warn); border-color: color-mix(in oklab, var(--dd-warn) 35%, transparent); }
|
|
400
|
+
.filter.active.skip { background: var(--dd-skip-bg); color: var(--dd-skip); border-color: var(--border-strong); }
|
|
401
|
+
.filter.active.all { background: var(--bg3); color: var(--fg1); border-color: var(--border-strong); }
|
|
402
|
+
.toolbar .spacer { width: 1px; height: 22px; background: var(--border-subtle); margin: 0 4px; }
|
|
403
|
+
.toolbar .linkbtn {
|
|
404
|
+
border: 0; background: transparent; color: var(--fg-brand);
|
|
405
|
+
font-size: 13px; font-weight: 600; cursor: pointer; padding: 6px 8px;
|
|
406
|
+
border-radius: 6px;
|
|
407
|
+
}
|
|
408
|
+
.toolbar .linkbtn:hover { background: var(--bg3); }
|
|
409
|
+
|
|
410
|
+
.search-input {
|
|
411
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
412
|
+
padding: 7px 12px; border-radius: 8px;
|
|
413
|
+
border: 1px solid var(--border-subtle); background: var(--dd-paper);
|
|
414
|
+
color: var(--fg2); font-size: 13px; min-width: 240px;
|
|
415
|
+
}
|
|
416
|
+
.search-input svg { color: var(--fg3); flex-shrink: 0; }
|
|
417
|
+
.search-input input {
|
|
418
|
+
border: 0; background: transparent; outline: none;
|
|
419
|
+
font: inherit; color: var(--fg1); flex: 1; min-width: 0;
|
|
420
|
+
}
|
|
421
|
+
.search-input input::placeholder { color: var(--fg3); }
|
|
422
|
+
|
|
423
|
+
/* Badges */
|
|
424
|
+
.badge {
|
|
425
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
426
|
+
padding: 3px 10px; border-radius: 999px;
|
|
427
|
+
font-family: var(--font-mono); font-size: 10.5px; font-weight: 700;
|
|
428
|
+
letter-spacing: 0.08em;
|
|
429
|
+
border: 1px solid transparent;
|
|
430
|
+
white-space: nowrap;
|
|
431
|
+
}
|
|
432
|
+
.badge .dot { width: 6px; height: 6px; border-radius: 999px; }
|
|
433
|
+
.badge.pass { background: var(--dd-pass-bg); color: var(--dd-pass); }
|
|
434
|
+
.badge.pass .dot { background: var(--dd-pass); }
|
|
435
|
+
.badge.fail { background: var(--dd-fail-bg); color: var(--dd-fail); }
|
|
436
|
+
.badge.fail .dot { background: var(--dd-fail); }
|
|
437
|
+
.badge.warn { background: var(--dd-warn-bg); color: var(--dd-warn); }
|
|
438
|
+
.badge.warn .dot { background: var(--dd-warn); }
|
|
439
|
+
.badge.skip { background: var(--dd-skip-bg); color: var(--dd-skip); }
|
|
440
|
+
.badge.skip .dot { background: var(--dd-skip); }
|
|
441
|
+
|
|
442
|
+
.tag {
|
|
443
|
+
display: inline-flex; align-items: center;
|
|
444
|
+
padding: 2px 8px; border-radius: 4px;
|
|
445
|
+
font-family: var(--font-mono); font-size: 11.5px; font-weight: 500;
|
|
446
|
+
background: var(--bg3); color: var(--dd-gray-900);
|
|
447
|
+
border: 1px solid var(--border-subtle);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Spec card */
|
|
451
|
+
.spec {
|
|
452
|
+
background: var(--dd-paper);
|
|
453
|
+
border: 1px solid var(--border-subtle);
|
|
454
|
+
border-radius: var(--radius-lg);
|
|
455
|
+
margin-bottom: 12px;
|
|
456
|
+
position: relative;
|
|
457
|
+
overflow: hidden;
|
|
458
|
+
transition: border-color .12s, box-shadow .12s;
|
|
459
|
+
}
|
|
460
|
+
.spec:hover { border-color: var(--border-strong); }
|
|
461
|
+
.spec.open { box-shadow: var(--shadow-sm); }
|
|
462
|
+
.spec .stripe {
|
|
463
|
+
position: absolute; top: 0; left: 0; bottom: 0; width: 4px;
|
|
464
|
+
}
|
|
465
|
+
.spec.pass .stripe { background: var(--dd-pass); }
|
|
466
|
+
.spec.fail .stripe { background: var(--dd-fail); }
|
|
467
|
+
.spec.warn .stripe { background: var(--dd-warn); }
|
|
468
|
+
.spec.skip .stripe { background: var(--dd-skip); }
|
|
469
|
+
.spec-head {
|
|
470
|
+
display: grid;
|
|
471
|
+
grid-template-columns: 20px auto 1fr auto;
|
|
472
|
+
align-items: center;
|
|
473
|
+
gap: 14px;
|
|
474
|
+
padding: 16px 20px 16px 24px;
|
|
475
|
+
cursor: pointer; user-select: none;
|
|
476
|
+
}
|
|
477
|
+
.spec-head .chev {
|
|
478
|
+
color: var(--fg3); font-size: 12px;
|
|
479
|
+
transition: transform .15s;
|
|
480
|
+
}
|
|
481
|
+
.spec.open > .spec-head .chev { transform: rotate(90deg); }
|
|
482
|
+
.spec-head .title-col { min-width: 0; }
|
|
483
|
+
.spec-head .title {
|
|
484
|
+
font-size: 15px; font-weight: 600; color: var(--fg1); letter-spacing: -0.005em;
|
|
485
|
+
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
|
|
486
|
+
}
|
|
487
|
+
.spec-head .desc {
|
|
488
|
+
margin-top: 3px; font-size: 13px; color: var(--fg2); line-height: 1.4;
|
|
489
|
+
max-width: 78ch;
|
|
490
|
+
}
|
|
491
|
+
.spec-head .path {
|
|
492
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--fg3);
|
|
493
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
494
|
+
}
|
|
495
|
+
.spec-head .metrics {
|
|
496
|
+
display: flex; align-items: center; gap: 14px;
|
|
497
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--fg3);
|
|
498
|
+
}
|
|
499
|
+
.spec-head .metrics .m { display: inline-flex; align-items: center; gap: 5px; }
|
|
500
|
+
.spec-head .metrics .m.pass { color: var(--dd-pass); }
|
|
501
|
+
.spec-head .metrics .m.fail { color: var(--dd-fail); }
|
|
502
|
+
.spec-head .metrics .m.warn { color: var(--dd-warn); }
|
|
503
|
+
.spec-head .metrics .m.skip { color: var(--dd-skip); }
|
|
504
|
+
.spec-head .metrics .sep {
|
|
505
|
+
width: 1px; height: 14px; background: var(--border-subtle);
|
|
506
|
+
}
|
|
507
|
+
.spec-body {
|
|
508
|
+
border-top: 1px solid var(--border-subtle);
|
|
509
|
+
padding: 6px 0 10px 0;
|
|
510
|
+
background: var(--bg2);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Test row */
|
|
514
|
+
.test {
|
|
515
|
+
background: var(--dd-paper);
|
|
516
|
+
border: 1px solid var(--border-subtle);
|
|
517
|
+
border-radius: var(--radius-md);
|
|
518
|
+
margin: 8px 18px 8px 38px;
|
|
519
|
+
overflow: hidden;
|
|
520
|
+
position: relative;
|
|
521
|
+
}
|
|
522
|
+
.test::before {
|
|
523
|
+
content: ""; position: absolute; top: 0; left: 0; bottom: 0; width: 3px;
|
|
524
|
+
}
|
|
525
|
+
.test.pass::before { background: var(--dd-pass); }
|
|
526
|
+
.test.fail::before { background: var(--dd-fail); }
|
|
527
|
+
.test.warn::before { background: var(--dd-warn); }
|
|
528
|
+
.test.skip::before { background: var(--dd-skip); }
|
|
529
|
+
.test-head {
|
|
530
|
+
display: grid;
|
|
531
|
+
grid-template-columns: 16px auto 1fr auto;
|
|
532
|
+
align-items: center;
|
|
533
|
+
gap: 12px;
|
|
534
|
+
padding: 11px 16px 11px 18px;
|
|
535
|
+
cursor: pointer; user-select: none;
|
|
536
|
+
}
|
|
537
|
+
.test-head .chev { color: var(--fg3); font-size: 11px; transition: transform .15s; }
|
|
538
|
+
.test.open > .test-head .chev { transform: rotate(90deg); }
|
|
539
|
+
.test-head .title {
|
|
540
|
+
font-size: 14px; font-weight: 600; color: var(--fg1);
|
|
541
|
+
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
|
|
542
|
+
}
|
|
543
|
+
.test-head .desc {
|
|
544
|
+
margin-top: 2px; font-size: 12.5px; color: var(--fg2); line-height: 1.4;
|
|
545
|
+
}
|
|
546
|
+
.test-head .metrics {
|
|
547
|
+
display: flex; align-items: center; gap: 10px;
|
|
548
|
+
font-family: var(--font-mono); font-size: 11.5px; color: var(--fg3);
|
|
549
|
+
}
|
|
550
|
+
.test-body {
|
|
551
|
+
border-top: 1px solid var(--border-subtle);
|
|
552
|
+
padding: 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* Context block */
|
|
556
|
+
.context {
|
|
557
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
558
|
+
}
|
|
559
|
+
.context:last-child { border-bottom: 0; }
|
|
560
|
+
.context-head {
|
|
561
|
+
display: grid;
|
|
562
|
+
grid-template-columns: auto auto 1fr auto;
|
|
563
|
+
align-items: center;
|
|
564
|
+
gap: 10px;
|
|
565
|
+
padding: 10px 16px;
|
|
566
|
+
background: var(--bg2);
|
|
567
|
+
font-family: var(--font-mono); font-size: 12px; color: var(--fg2);
|
|
568
|
+
}
|
|
569
|
+
.context-head .what { color: var(--fg1); font-weight: 600; }
|
|
570
|
+
.context-head .meta {
|
|
571
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
572
|
+
color: var(--fg1); font-weight: 600;
|
|
573
|
+
}
|
|
574
|
+
.context-head .meta svg { color: var(--fg2); }
|
|
575
|
+
|
|
576
|
+
/* Steps */
|
|
577
|
+
.steps { padding: 0 8px 8px 8px; background: var(--dd-paper); }
|
|
578
|
+
.step {
|
|
579
|
+
display: grid;
|
|
580
|
+
grid-template-columns: 16px 88px 120px 1fr auto;
|
|
581
|
+
align-items: center;
|
|
582
|
+
gap: 12px;
|
|
583
|
+
padding: 10px 10px 10px 12px;
|
|
584
|
+
border-bottom: 1px dashed var(--border-subtle);
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
user-select: none;
|
|
587
|
+
font-size: 13.5px;
|
|
588
|
+
transition: background .08s;
|
|
589
|
+
position: relative;
|
|
590
|
+
}
|
|
591
|
+
.step:last-of-type { border-bottom: 0; }
|
|
592
|
+
.step:hover { background: var(--bg2); }
|
|
593
|
+
.step.pass::before, .step.fail::before, .step.warn::before, .step.skip::before {
|
|
594
|
+
content: ""; position: absolute; left: 0; top: 8px; bottom: 8px;
|
|
595
|
+
width: 2px; border-radius: 2px;
|
|
596
|
+
}
|
|
597
|
+
.step.pass::before { background: var(--dd-pass); opacity: .55; }
|
|
598
|
+
.step.fail::before { background: var(--dd-fail); opacity: .9; }
|
|
599
|
+
.step.warn::before { background: var(--dd-warn); opacity: .9; }
|
|
600
|
+
.step.skip::before { background: var(--dd-skip); opacity: .55; }
|
|
601
|
+
.step .chev { color: var(--fg3); font-size: 10px; transition: transform .15s; }
|
|
602
|
+
.step.open .chev { transform: rotate(90deg); }
|
|
603
|
+
.step .desc {
|
|
604
|
+
color: var(--fg1); min-width: 0;
|
|
605
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
606
|
+
}
|
|
607
|
+
.step.has-fail .desc, .step.has-warn .desc { font-weight: 500; }
|
|
608
|
+
.step .dur {
|
|
609
|
+
font-family: var(--font-mono); font-size: 11.5px; color: var(--fg3);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/* Step detail */
|
|
613
|
+
.step-detail {
|
|
614
|
+
grid-column: 1 / -1;
|
|
615
|
+
margin: 2px 0 12px 12px;
|
|
616
|
+
padding: 12px 14px;
|
|
617
|
+
border: 1px solid var(--border-subtle);
|
|
618
|
+
border-radius: var(--radius-md);
|
|
619
|
+
background: var(--bg2);
|
|
620
|
+
display: grid;
|
|
621
|
+
grid-template-columns: 1fr;
|
|
622
|
+
gap: 12px;
|
|
623
|
+
cursor: default;
|
|
624
|
+
}
|
|
625
|
+
.step-detail .result-note {
|
|
626
|
+
display: grid; grid-template-columns: auto 1fr; gap: 10px;
|
|
627
|
+
align-items: start;
|
|
628
|
+
padding: 10px 12px;
|
|
629
|
+
border-radius: var(--radius-sm);
|
|
630
|
+
background: var(--dd-paper);
|
|
631
|
+
border: 1px solid var(--border-subtle);
|
|
632
|
+
}
|
|
633
|
+
.step-detail .result-note svg { margin-top: 2px; flex-shrink: 0; }
|
|
634
|
+
.step-detail.fail .result-note { border-color: color-mix(in oklab, var(--dd-fail) 35%, var(--border-subtle)); background: var(--dd-fail-bg); }
|
|
635
|
+
.step-detail.fail .result-note svg { color: var(--dd-fail); }
|
|
636
|
+
.step-detail.warn .result-note { border-color: color-mix(in oklab, var(--dd-warn) 35%, var(--border-subtle)); background: var(--dd-warn-bg); }
|
|
637
|
+
.step-detail.warn .result-note svg { color: var(--dd-warn); }
|
|
638
|
+
.step-detail.pass .result-note { background: var(--dd-paper); }
|
|
639
|
+
.step-detail.pass .result-note svg { color: var(--dd-pass); }
|
|
640
|
+
.step-detail.skip .result-note svg { color: var(--dd-skip); }
|
|
641
|
+
.step-detail .result-note .body { font-size: 13px; line-height: 1.55; color: var(--fg1); word-break: break-word; }
|
|
642
|
+
.step-detail .result-note .body code { font-family: var(--font-mono); font-size: 0.92em; background: rgba(0,0,0,0.06); padding: 1px 5px; border-radius: 3px; }
|
|
643
|
+
|
|
644
|
+
/* Detail grid */
|
|
645
|
+
.detail-grid {
|
|
646
|
+
display: grid; gap: 12px;
|
|
647
|
+
grid-template-columns: 1fr 1fr;
|
|
648
|
+
}
|
|
649
|
+
@media (max-width: 900px) { .detail-grid { grid-template-columns: 1fr; } }
|
|
650
|
+
|
|
651
|
+
.detail-panel {
|
|
652
|
+
border: 1px solid var(--border-subtle);
|
|
653
|
+
border-radius: var(--radius-sm);
|
|
654
|
+
background: var(--dd-paper);
|
|
655
|
+
overflow: hidden;
|
|
656
|
+
}
|
|
657
|
+
.detail-panel .dp-head {
|
|
658
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
659
|
+
padding: 8px 12px; font-family: var(--font-mono); font-size: 11px;
|
|
660
|
+
color: var(--fg3); letter-spacing: 0.06em; text-transform: uppercase;
|
|
661
|
+
background: var(--bg2); border-bottom: 1px solid var(--border-subtle);
|
|
662
|
+
}
|
|
663
|
+
.detail-panel .dp-head .copy-btn {
|
|
664
|
+
border: 0; background: transparent; color: var(--fg3);
|
|
665
|
+
font-size: 11px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
|
|
666
|
+
font-family: var(--font-mono); letter-spacing: 0;
|
|
667
|
+
}
|
|
668
|
+
.detail-panel .dp-head .copy-btn:hover { background: var(--bg3); color: var(--fg1); }
|
|
669
|
+
.detail-panel pre {
|
|
670
|
+
margin: 0;
|
|
671
|
+
padding: 12px 14px;
|
|
672
|
+
font-family: var(--font-mono); font-size: 12px; line-height: 1.55;
|
|
673
|
+
color: var(--fg1);
|
|
674
|
+
white-space: pre-wrap; word-break: break-word;
|
|
675
|
+
max-height: 380px; overflow: auto;
|
|
676
|
+
}
|
|
677
|
+
.detail-panel pre .k { color: #2563A0; }
|
|
678
|
+
.detail-panel pre .s { color: #4d7e2c; }
|
|
679
|
+
.detail-panel pre .n { color: #9a5a1a; }
|
|
680
|
+
.detail-panel pre .b { color: #5B616B; }
|
|
681
|
+
|
|
682
|
+
/* Key-value list */
|
|
683
|
+
.kv {
|
|
684
|
+
font-family: var(--font-mono); font-size: 12px;
|
|
685
|
+
padding: 10px 12px;
|
|
686
|
+
display: grid; grid-template-columns: auto 1fr; gap: 4px 12px;
|
|
687
|
+
}
|
|
688
|
+
.kv .k { color: var(--fg3); }
|
|
689
|
+
.kv .v { color: var(--fg1); word-break: break-word; }
|
|
690
|
+
|
|
691
|
+
/* Media panel */
|
|
692
|
+
.media-panel {
|
|
693
|
+
border: 1px solid var(--border-subtle);
|
|
694
|
+
border-radius: var(--radius-sm);
|
|
695
|
+
background: var(--dd-paper);
|
|
696
|
+
overflow: hidden;
|
|
697
|
+
grid-column: 1 / -1;
|
|
698
|
+
}
|
|
699
|
+
.media-panel .dp-head { justify-content: space-between; }
|
|
700
|
+
.media-panel .mp-body {
|
|
701
|
+
padding: 10px;
|
|
702
|
+
display: flex; flex-wrap: wrap; gap: 10px;
|
|
703
|
+
background: #fafbfc;
|
|
704
|
+
}
|
|
705
|
+
.media-thumb {
|
|
706
|
+
position: relative;
|
|
707
|
+
border: 1px solid var(--border-subtle);
|
|
708
|
+
border-radius: var(--radius-xs);
|
|
709
|
+
overflow: hidden;
|
|
710
|
+
background: var(--dd-ink);
|
|
711
|
+
cursor: zoom-in;
|
|
712
|
+
transition: transform .15s, box-shadow .15s;
|
|
713
|
+
}
|
|
714
|
+
.media-thumb:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); }
|
|
715
|
+
.media-thumb img, .media-thumb video {
|
|
716
|
+
display: block; max-width: 360px; max-height: 240px;
|
|
717
|
+
width: 100%; object-fit: contain; background: #000;
|
|
718
|
+
}
|
|
719
|
+
.media-thumb .cap {
|
|
720
|
+
padding: 6px 10px;
|
|
721
|
+
font-family: var(--font-mono); font-size: 11px;
|
|
722
|
+
color: var(--fg3); background: var(--bg2);
|
|
723
|
+
border-top: 1px solid var(--border-subtle);
|
|
724
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 360px;
|
|
725
|
+
}
|
|
726
|
+
.media-thumb .kind {
|
|
727
|
+
position: absolute; top: 6px; left: 6px;
|
|
728
|
+
font-family: var(--font-mono); font-size: 10px; font-weight: 700;
|
|
729
|
+
padding: 2px 6px; border-radius: 3px;
|
|
730
|
+
background: rgba(13,14,17,0.78); color: #E6E8EC;
|
|
731
|
+
letter-spacing: 0.05em;
|
|
732
|
+
}
|
|
733
|
+
.media-thumb .changed-flag {
|
|
734
|
+
position: absolute; top: 6px; right: 6px;
|
|
735
|
+
font-family: var(--font-mono); font-size: 10px; font-weight: 700;
|
|
736
|
+
padding: 2px 6px; border-radius: 3px;
|
|
737
|
+
background: var(--dd-green-bright); color: #07150C;
|
|
738
|
+
letter-spacing: 0.05em;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* Lightbox */
|
|
742
|
+
.lightbox {
|
|
743
|
+
position: fixed; inset: 0; background: rgba(13,14,17,0.88);
|
|
744
|
+
display: flex; align-items: center; justify-content: center;
|
|
745
|
+
z-index: 100; padding: 40px;
|
|
746
|
+
backdrop-filter: blur(4px);
|
|
747
|
+
}
|
|
748
|
+
.lightbox img, .lightbox video { max-width: 100%; max-height: 100%; background: #000; }
|
|
749
|
+
.lightbox .close {
|
|
750
|
+
position: absolute; top: 18px; right: 18px;
|
|
751
|
+
width: 36px; height: 36px; border-radius: 8px;
|
|
752
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
753
|
+
background: #15171B; color: #E6E8EC; border: 1px solid #2A2D34;
|
|
754
|
+
cursor: pointer;
|
|
755
|
+
}
|
|
756
|
+
.lightbox .cap {
|
|
757
|
+
position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
|
|
758
|
+
font-family: var(--font-mono); font-size: 12px; color: #B7BCC5;
|
|
759
|
+
background: #15171B; padding: 6px 12px; border-radius: 6px;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* Empty state */
|
|
763
|
+
.empty {
|
|
764
|
+
padding: 40px; text-align: center;
|
|
765
|
+
color: var(--fg3); font-size: 14px;
|
|
766
|
+
border: 1px dashed var(--border-subtle);
|
|
767
|
+
border-radius: var(--radius-md); background: var(--dd-paper);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* Dark mode */
|
|
771
|
+
body.dark {
|
|
772
|
+
--fg1: #F1F3F6;
|
|
773
|
+
--fg2: #D2D6DD;
|
|
774
|
+
--fg3: #B0B6C0;
|
|
775
|
+
--fg-brand: var(--dd-green-bright);
|
|
776
|
+
--bg1: #0D0E11;
|
|
777
|
+
--bg2: #15171B;
|
|
778
|
+
--bg3: #1A1C21;
|
|
779
|
+
--border-subtle: #2A2D34;
|
|
780
|
+
--border-strong: #3A3F47;
|
|
781
|
+
--dd-pass: #4FC285;
|
|
782
|
+
--dd-pass-bg: rgba(62,177,110,0.18);
|
|
783
|
+
--dd-fail: #FF6A5E;
|
|
784
|
+
--dd-fail-bg: rgba(255,106,94,0.15);
|
|
785
|
+
--dd-warn: #F2B53A;
|
|
786
|
+
--dd-warn-bg: rgba(242,181,58,0.15);
|
|
787
|
+
--dd-skip: #B0B6C0;
|
|
788
|
+
--dd-skip-bg: rgba(176,182,192,0.12);
|
|
789
|
+
background: #0D0E11;
|
|
790
|
+
color: #F1F3F6;
|
|
791
|
+
}
|
|
792
|
+
body.dark .spec, body.dark .test, body.dark .detail-panel,
|
|
793
|
+
body.dark .media-panel, body.dark .verdict-card, body.dark .sum {
|
|
794
|
+
background: #15171B; border-color: #2A2D34; color: #E6E8EC;
|
|
795
|
+
}
|
|
796
|
+
body.dark .spec-body { background: #0D0E11; }
|
|
797
|
+
body.dark .test { background-color: rgba(0,0,0,0) !important; }
|
|
798
|
+
body.dark .steps { background-color: rgba(0,0,0,0) !important; }
|
|
799
|
+
body.dark .test-head .title, body.dark .spec-head .title, body.dark .step .desc { color: #F1F3F6 !important; }
|
|
800
|
+
body.dark .step .desc > span:first-child { color: #B0B6C0 !important; }
|
|
801
|
+
body.dark .spec-head .desc, body.dark .test-head .desc { color: #D2D6DD; }
|
|
802
|
+
body.dark .spec-head .path { color: #B0B6C0; }
|
|
803
|
+
body.dark .context-head { background: #0D0E11; border-color: #2A2D34; color: #D2D6DD; }
|
|
804
|
+
body.dark .context-head .meta { color: #F1F3F6; }
|
|
805
|
+
body.dark .context-head .meta svg { color: #D2D6DD; }
|
|
806
|
+
body.dark .step { border-color: rgba(42,45,52,0.6); }
|
|
807
|
+
body.dark .step:hover { background: #1A1C21; }
|
|
808
|
+
body.dark .step-detail { background: #0D0E11; border-color: #2A2D34; }
|
|
809
|
+
body.dark .step-detail .result-note { background: #15171B; border-color: #2A2D34; }
|
|
810
|
+
body.dark .step-detail.fail .result-note { background: #2a1414; border-color: #612323; }
|
|
811
|
+
body.dark .step-detail.warn .result-note { background: #2a210a; border-color: #5a4510; }
|
|
812
|
+
body.dark .detail-panel .dp-head { background: #0D0E11; border-color: #2A2D34; color: #B0B6C0; }
|
|
813
|
+
body.dark .detail-panel pre { color: #E6E8EC; }
|
|
814
|
+
body.dark .search-input { background: #15171B; border-color: #3A3F47; color: #F1F3F6; }
|
|
815
|
+
body.dark .search-input input { color: #F1F3F6; }
|
|
816
|
+
body.dark .search-input input::placeholder { color: #B0B6C0; }
|
|
817
|
+
body.dark .filter { background: #15171B; border-color: #3A3F47; color: #D2D6DD; }
|
|
818
|
+
body.dark main { color: #E6E8EC; }
|
|
819
|
+
body.dark .tag { background: #1A1C21; color: #F1F3F6; border-color: #3A3F47; }
|
|
820
|
+
body.dark .empty { background: #15171B; border-color: #2A2D34; color: #B0B6C0; }
|
|
821
|
+
body.dark .media-panel .mp-body { background: #0D0E11; }
|
|
822
|
+
body.dark .media-thumb .cap { background: #15171B; border-color: #2A2D34; color: #B0B6C0; }
|
|
823
|
+
body.dark .badge.pass { background: rgba(62,177,110,0.18); color: #6FD69A; }
|
|
824
|
+
body.dark .badge.pass .dot { background: #6FD69A; }
|
|
825
|
+
body.dark .badge.fail { background: rgba(255,106,94,0.18); color: #FF8A80; }
|
|
826
|
+
body.dark .badge.fail .dot { background: #FF8A80; }
|
|
827
|
+
body.dark .badge.warn { background: rgba(242,181,58,0.18); color: #F2B53A; }
|
|
828
|
+
body.dark .badge.warn .dot { background: #F2B53A; }
|
|
829
|
+
body.dark .badge.skip { background: rgba(176,182,192,0.12); color: #D2D6DD; }
|
|
830
|
+
body.dark .badge.skip .dot { background: #D2D6DD; }
|
|
831
|
+
body.dark .metastrip { color: #D2D6DD; }
|
|
832
|
+
body.dark .metastrip .m .k { color: #B0B6C0; }
|
|
833
|
+
body.dark .metastrip .m .v { color: #F1F3F6; }
|
|
834
|
+
body.dark .hdr-title .eyebrow { color: #B0B6C0; }
|
|
835
|
+
body.dark .hdr-title h1 .sub { color: #B0B6C0; }
|
|
836
|
+
body.dark .detail-panel pre .k { color: #6FB8FF; }
|
|
837
|
+
body.dark .detail-panel pre .s { color: #B8D88A; }
|
|
838
|
+
body.dark .detail-panel pre .n { color: #E7A45B; }
|
|
839
|
+
body.dark .detail-panel pre .b { color: #8A909B; }
|
|
840
|
+
|
|
841
|
+
@media print {
|
|
842
|
+
body { background: #fff; }
|
|
843
|
+
.hdr, .toolbar, .metastrip { display: none !important; }
|
|
844
|
+
.spec, .test { break-inside: avoid; box-shadow: none; }
|
|
845
|
+
.spec-body, .test-body, .step-detail { display: block !important; }
|
|
846
|
+
}
|
|
847
|
+
`;
|
|
848
|
+
const JS_CONTENT = `
|
|
849
|
+
(function() {
|
|
850
|
+
"use strict";
|
|
851
|
+
|
|
852
|
+
var report = window.REPORT_DATA;
|
|
853
|
+
function isValidReport(r) {
|
|
854
|
+
return r && r.summary && r.summary.specs && Array.isArray(r.specs);
|
|
855
|
+
}
|
|
856
|
+
if (!isValidReport(report)) {
|
|
857
|
+
var root = document.getElementById("root");
|
|
858
|
+
root.innerHTML = '';
|
|
859
|
+
var msg = document.createElement("div");
|
|
860
|
+
msg.style.cssText = "max-width:800px;margin:64px auto;padding:24px;font-family:system-ui,sans-serif;";
|
|
861
|
+
var h = document.createElement("h2");
|
|
862
|
+
h.textContent = "Unsupported report format";
|
|
863
|
+
h.style.cssText = "font-size:20px;margin:0 0 8px 0;";
|
|
864
|
+
msg.appendChild(h);
|
|
865
|
+
var p = document.createElement("p");
|
|
866
|
+
p.textContent = "The provided data does not match the expected Doc Detective report shape (summary.specs + specs[]). Raw data below:";
|
|
867
|
+
p.style.cssText = "color:#4A5058;margin:0 0 16px 0;";
|
|
868
|
+
msg.appendChild(p);
|
|
869
|
+
var pre = document.createElement("pre");
|
|
870
|
+
pre.textContent = JSON.stringify(report, null, 2);
|
|
871
|
+
pre.style.cssText = "background:#F1F3F6;border:1px solid #E2E5EA;border-radius:8px;padding:12px;overflow:auto;font-size:12px;line-height:1.5;max-height:70vh;";
|
|
872
|
+
msg.appendChild(pre);
|
|
873
|
+
root.appendChild(msg);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
var STATUS_ORDER = ["FAIL", "WARNING", "PASS", "SKIPPED"];
|
|
878
|
+
var STATUS_META = {
|
|
879
|
+
PASS: { slug: "pass", label: "PASS" },
|
|
880
|
+
FAIL: { slug: "fail", label: "FAIL" },
|
|
881
|
+
WARNING: { slug: "warn", label: "WARNING" },
|
|
882
|
+
SKIPPED: { slug: "skip", label: "SKIPPED" }
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
function statusSlug(s) { return (STATUS_META[s] && STATUS_META[s].slug) || "skip"; }
|
|
886
|
+
|
|
887
|
+
function fmtDuration(ms) {
|
|
888
|
+
if (ms == null) return "\\u2014";
|
|
889
|
+
if (ms < 1000) return ms + " ms";
|
|
890
|
+
if (ms < 60000) return (ms / 1000).toFixed(2).replace(/\\.?0+$/, "") + " s";
|
|
891
|
+
var m = Math.floor(ms / 60000), s = Math.round((ms % 60000) / 1000);
|
|
892
|
+
return m + "m " + s + "s";
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function esc(s) { var d = document.createElement("div"); d.textContent = s == null ? "" : String(s); return d.innerHTML; }
|
|
896
|
+
function escAttr(s) { return String(s == null ? "" : s).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">"); }
|
|
897
|
+
|
|
898
|
+
function wireDisclosure(node, expanded, toggle) {
|
|
899
|
+
node.setAttribute("role", "button");
|
|
900
|
+
node.setAttribute("tabindex", "0");
|
|
901
|
+
node.setAttribute("aria-expanded", String(expanded));
|
|
902
|
+
node.onclick = toggle;
|
|
903
|
+
node.onkeydown = function(e) {
|
|
904
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
905
|
+
e.preventDefault();
|
|
906
|
+
toggle(e);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function hlJson(json) {
|
|
912
|
+
return esc(json).replace(
|
|
913
|
+
/("(?:[^"\\\\\\\\]|\\\\\\\\.)*")(\\s*:)?|\\b(true|false|null)\\b|-?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?/g,
|
|
914
|
+
function(m, str, colon, bool) {
|
|
915
|
+
if (str) return colon ? '<span class="k">' + str + '</span>' + colon : '<span class="s">' + str + '</span>';
|
|
916
|
+
if (bool) return '<span class="b">' + m + '</span>';
|
|
917
|
+
return '<span class="n">' + m + '</span>';
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
var ACTIONS = ["goTo","find","click","screenshot","checkLink","httpRequest",
|
|
923
|
+
"runShell","runCode","type","typeKeys","wait","record","stopRecord",
|
|
924
|
+
"loadVariables","loadCookie","saveCookie","dragAndDrop","moveTo","scroll"];
|
|
925
|
+
function actionKey(step) {
|
|
926
|
+
for (var i = 0; i < ACTIONS.length; i++) if (ACTIONS[i] in step) return ACTIONS[i];
|
|
927
|
+
return "step";
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
var ICON = {
|
|
931
|
+
chevron: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
932
|
+
search: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.5"/><path d="M9.5 9.5L13 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
933
|
+
print: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3.5 5V1.5h7V5M3.5 10H2a.5.5 0 01-.5-.5v-4A.5.5 0 012 5h10a.5.5 0 01.5.5v4a.5.5 0 01-.5.5h-1.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><rect x="3.5" y="8" width="7" height="4.5" rx=".5" stroke="currentColor" stroke-width="1.2"/></svg>',
|
|
934
|
+
download: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5v8M3.5 6.5L7 10l3.5-3.5M2 12.5h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
935
|
+
sun: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="2.5" stroke="currentColor" stroke-width="1.3"/><path d="M7 1v1.5M7 11.5V13M1 7h1.5M11.5 7H13M2.75 2.75l1.06 1.06M10.19 10.19l1.06 1.06M11.25 2.75l-1.06 1.06M3.81 10.19l-1.06 1.06" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>',
|
|
936
|
+
moon: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M12.5 7.5a5.5 5.5 0 01-6-6 5.5 5.5 0 106 6z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
937
|
+
monitor: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="2" width="11" height="8" rx="1" stroke="currentColor" stroke-width="1.3"/><path d="M5 12.5h4M7 10v2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>',
|
|
938
|
+
close: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l8 8M11 3l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
|
939
|
+
check: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.5L5 9l4.5-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
940
|
+
warn: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1.5L1 12.5h12L7 1.5z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M7 6v3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><circle cx="7" cy="10.5" r=".6" fill="currentColor"/></svg>',
|
|
941
|
+
xmark: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="5.5" stroke="currentColor" stroke-width="1.3"/><path d="M5 5l4 4M9 5l-4 4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>',
|
|
942
|
+
skip: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 3l4 4-4 4M8 3v8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
943
|
+
copy: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="4" y="4" width="6.5" height="6.5" rx="1" stroke="currentColor" stroke-width="1.2"/><path d="M8 4V2.5A1 1 0 007 1.5H2.5A1 1 0 001.5 2.5V7a1 1 0 001 1H4" stroke="currentColor" stroke-width="1.2"/></svg>',
|
|
944
|
+
chip: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="3" y="3" width="6" height="6" rx="1" stroke="currentColor" stroke-width="1"/><path d="M5 1v2M7 1v2M5 9v2M7 9v2M1 5h2M1 7h2M9 5h2M9 7h2" stroke="currentColor" stroke-width="1" stroke-linecap="round"/></svg>',
|
|
945
|
+
file: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M7 1H3.5A1 1 0 002.5 2v8a1 1 0 001 1h5a1 1 0 001-1V3.5L7 1z" stroke="currentColor" stroke-width="1.1" stroke-linejoin="round"/><path d="M7 1v2.5h2.5" stroke="currentColor" stroke-width="1.1" stroke-linejoin="round"/></svg>',
|
|
946
|
+
finger: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M8 5.5V4a1 1 0 00-2 0v3L4.5 5.5a1 1 0 00-1.4 1.4L6 10h3l1.5-3.5V5.5A1 1 0 009 4.5" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
947
|
+
expand: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M4 5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 8l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
948
|
+
collapse: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M4 9l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 6l3-3 3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
var LOGO_SVG = '<svg width="30" height="30" viewBox="0 0 1256 1256" fill="none"><path d="M378.014 0.515785L828.006 0.5C848.561 0.499279 868.275 8.66442 882.809 23.1992L1232.81 373.199C1247.34 387.734 1255.51 407.446 1255.51 428.001L1255.5 1178C1255.5 1220.8 1220.8 1255.5 1178 1255.5H378C335.198 1255.5 300.5 1220.8 300.5 1178V997.53C129.173 961.767 0.5 809.934 0.5 628C0.5 446.064 129.176 294.23 300.505 258.469L300.516 78.0107C300.519 35.2117 335.215 0.517288 378.014 0.515785Z" fill="white"/><path fill-rule="evenodd" clip-rule="evenodd" d="M378.015 40.5158L828.007 40.5C837.953 40.4997 847.492 44.4506 854.525 51.4835L1204.53 401.483C1211.56 408.516 1215.51 418.055 1215.51 428L1215.5 1178C1215.5 1198.71 1198.71 1215.5 1178 1215.5H378C357.289 1215.5 340.5 1198.71 340.5 1178V963.44C171.752 944.786 40.5 801.721 40.5 628C40.5 454.278 171.753 311.213 340.502 292.56L340.516 78.0133C340.518 57.3041 357.306 40.5165 378.015 40.5158ZM415.502 292.56C584.249 311.215 715.5 454.28 715.5 628C715.5 707.673 687.857 780.941 641.694 838.661L804.516 1001.48C819.161 1016.13 819.161 1039.87 804.516 1054.52C789.872 1069.16 766.128 1069.16 751.484 1054.52L588.661 891.694C540.123 930.513 480.59 956.236 415.5 963.438V1140.5H1140.5L1140.51 465.5H828.009C807.298 465.5 790.509 448.711 790.509 428V115.501L415.514 115.514L415.502 292.56ZM865.509 168.533L1087.48 390.5H865.509V168.533ZM378 365.5C233.025 365.5 115.5 483.025 115.5 628C115.5 772.975 233.025 890.5 378 890.5C450.498 890.5 516.071 861.16 563.616 813.616C611.16 766.071 640.5 700.498 640.5 628C640.5 483.025 522.975 365.5 378 365.5Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M165.5 628C165.5 510.639 260.639 415.5 378 415.5C495.361 415.5 590.5 510.639 590.5 628C590.5 686.689 566.748 739.772 528.26 778.26C489.772 816.748 436.689 840.5 378 840.5C260.639 840.5 165.5 745.361 165.5 628ZM283 525.5C262.289 525.5 245.5 542.289 245.5 563C245.5 583.711 262.289 600.5 283 600.5H473C493.711 600.5 510.5 583.711 510.5 563C510.5 542.289 493.711 525.5 473 525.5H283ZM283 655.5C262.289 655.5 245.5 672.289 245.5 693C245.5 713.711 262.289 730.5 383 730.5H383C403.711 730.5 420.5 713.711 420.5 693C420.5 672.289 403.711 655.5 383 655.5H283Z" fill="#4B9A47"/></svg>';
|
|
952
|
+
|
|
953
|
+
function statusIcon(slug) {
|
|
954
|
+
if (slug === "pass") return ICON.check;
|
|
955
|
+
if (slug === "fail") return ICON.xmark;
|
|
956
|
+
if (slug === "warn") return ICON.warn;
|
|
957
|
+
return ICON.skip;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// State
|
|
961
|
+
var state = {
|
|
962
|
+
statusFilters: new Set(),
|
|
963
|
+
query: "",
|
|
964
|
+
openSpecs: {},
|
|
965
|
+
openTests: {},
|
|
966
|
+
openSteps: {},
|
|
967
|
+
themeMode: "system",
|
|
968
|
+
lightbox: null
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
// Detect system dark preference
|
|
972
|
+
var mql = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
973
|
+
function isDark() {
|
|
974
|
+
if (state.themeMode === "dark") return true;
|
|
975
|
+
if (state.themeMode === "light") return false;
|
|
976
|
+
return !!(mql && mql.matches);
|
|
977
|
+
}
|
|
978
|
+
function applyTheme() {
|
|
979
|
+
document.body.classList.toggle("dark", isDark());
|
|
980
|
+
}
|
|
981
|
+
if (mql) {
|
|
982
|
+
var onChange = function() { applyTheme(); };
|
|
983
|
+
mql.addEventListener ? mql.addEventListener("change", onChange) : mql.addListener(onChange);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Default expansion: open failures/warnings
|
|
987
|
+
function defaultOpen(node) {
|
|
988
|
+
return node.result === "FAIL" || node.result === "WARNING";
|
|
989
|
+
}
|
|
990
|
+
function isSpecOpen(id, spec) { return id in state.openSpecs ? state.openSpecs[id] : defaultOpen(spec); }
|
|
991
|
+
function isTestOpen(id, test) { return id in state.openTests ? state.openTests[id] : defaultOpen(test); }
|
|
992
|
+
|
|
993
|
+
// Helpers
|
|
994
|
+
function el(tag, cls, html) {
|
|
995
|
+
var e = document.createElement(tag);
|
|
996
|
+
if (cls) e.className = cls;
|
|
997
|
+
if (html != null) e.innerHTML = html;
|
|
998
|
+
return e;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function badge(status) {
|
|
1002
|
+
var m = STATUS_META[status] || STATUS_META.SKIPPED;
|
|
1003
|
+
return '<span class="badge ' + m.slug + '"><span class="dot"></span>' + m.label + '</span>';
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function tag(text) { return '<span class="tag">' + esc(text) + '</span>'; }
|
|
1007
|
+
|
|
1008
|
+
function metric(slug, n, label) {
|
|
1009
|
+
if (!n) return "";
|
|
1010
|
+
return '<span class="m ' + slug + '"><span class="dot" style="display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--dd-' + slug + ')"></span>' + n + (label ? " " + label : "") + '</span>';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function countTree(spec) {
|
|
1014
|
+
var tests = 0, contexts = 0, steps = 0;
|
|
1015
|
+
var sc = { pass: 0, fail: 0, warning: 0, skipped: 0 };
|
|
1016
|
+
(spec.tests || []).forEach(function(t) {
|
|
1017
|
+
tests++;
|
|
1018
|
+
(t.contexts || []).forEach(function(c) {
|
|
1019
|
+
contexts++;
|
|
1020
|
+
(c.steps || []).forEach(function(s) {
|
|
1021
|
+
steps++;
|
|
1022
|
+
var k = (s.result || "").toLowerCase();
|
|
1023
|
+
if (k in sc) sc[k]++;
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
return { tests: tests, contexts: contexts, steps: steps, stepCounts: sc };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function collectMedia(step) {
|
|
1031
|
+
var media = [];
|
|
1032
|
+
var outs = step.outputs || {};
|
|
1033
|
+
if (step.screenshot && (step.screenshot.path || outs.screenshotPath)) {
|
|
1034
|
+
media.push({ kind: "image", path: outs.screenshotPath || step.screenshot.path, changed: outs.changed });
|
|
1035
|
+
}
|
|
1036
|
+
if (outs.screenshotPath && !media.find(function(m) { return m.path === outs.screenshotPath; })) {
|
|
1037
|
+
media.push({ kind: "image", path: outs.screenshotPath, changed: outs.changed });
|
|
1038
|
+
}
|
|
1039
|
+
if (step.record && (step.record.path || outs.recordingPath)) {
|
|
1040
|
+
media.push({ kind: "video", path: outs.recordingPath || step.record.path });
|
|
1041
|
+
}
|
|
1042
|
+
if (step.stopRecord && outs.recordingPath) {
|
|
1043
|
+
media.push({ kind: "video", path: outs.recordingPath });
|
|
1044
|
+
}
|
|
1045
|
+
return media;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Build header
|
|
1049
|
+
function buildHeader() {
|
|
1050
|
+
var s = report.summary.specs;
|
|
1051
|
+
var tot = s.pass + s.fail + s.warning + s.skipped;
|
|
1052
|
+
var pctPass = tot ? (s.pass / tot * 100) + "%" : "0%";
|
|
1053
|
+
var pctFailEnd = tot ? ((s.pass + s.fail) / tot * 100) + "%" : "0%";
|
|
1054
|
+
var pctWarnEnd = tot ? ((s.pass + s.fail + s.warning) / tot * 100) + "%" : "0%";
|
|
1055
|
+
|
|
1056
|
+
var meta = report.meta || {};
|
|
1057
|
+
var started = meta.startedAt ? new Date(meta.startedAt) : null;
|
|
1058
|
+
var reportIdShort = (report.reportId || "").slice(0, 8);
|
|
1059
|
+
|
|
1060
|
+
var hdr = el("header", "hdr");
|
|
1061
|
+
hdr.style.setProperty("--pct-pass", pctPass);
|
|
1062
|
+
hdr.style.setProperty("--pct-fail-end", pctFailEnd);
|
|
1063
|
+
hdr.style.setProperty("--pct-warn-end", pctWarnEnd);
|
|
1064
|
+
|
|
1065
|
+
var inner = el("div", "hdr-inner");
|
|
1066
|
+
|
|
1067
|
+
// Brand
|
|
1068
|
+
inner.innerHTML = '<div class="brand">' + LOGO_SVG +
|
|
1069
|
+
'<div class="wm">Doc Detective <span class="tag">/ report</span></div>' +
|
|
1070
|
+
'<div class="divider"></div></div>';
|
|
1071
|
+
|
|
1072
|
+
// Title
|
|
1073
|
+
var title = el("div", "hdr-title");
|
|
1074
|
+
title.innerHTML = '<div class="eyebrow">Report' + (reportIdShort ? " \\u00B7 " + esc(reportIdShort) : "") + '</div>' +
|
|
1075
|
+
'<h1>Test run<span class="sub"> \\u00B7 ' + (started ? started.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) : "\\u2014") + '</span></h1>';
|
|
1076
|
+
inner.appendChild(title);
|
|
1077
|
+
|
|
1078
|
+
// Actions
|
|
1079
|
+
var actions = el("div", "hdr-actions");
|
|
1080
|
+
|
|
1081
|
+
var themeBtn = el("button", "hdr-btn");
|
|
1082
|
+
function updateThemeBtn() {
|
|
1083
|
+
var m = state.themeMode;
|
|
1084
|
+
themeBtn.innerHTML = (m === "dark" ? ICON.moon : m === "light" ? ICON.sun : ICON.monitor) +
|
|
1085
|
+
'<span style="text-transform:capitalize">' + m + '</span>';
|
|
1086
|
+
themeBtn.title = "Theme: " + m;
|
|
1087
|
+
}
|
|
1088
|
+
updateThemeBtn();
|
|
1089
|
+
themeBtn.onclick = function() {
|
|
1090
|
+
var order = ["system", "light", "dark"];
|
|
1091
|
+
state.themeMode = order[(order.indexOf(state.themeMode) + 1) % 3];
|
|
1092
|
+
updateThemeBtn();
|
|
1093
|
+
applyTheme();
|
|
1094
|
+
};
|
|
1095
|
+
actions.appendChild(themeBtn);
|
|
1096
|
+
|
|
1097
|
+
var printBtn = el("button", "hdr-btn", ICON.print + " Print");
|
|
1098
|
+
printBtn.onclick = function() { window.print(); };
|
|
1099
|
+
actions.appendChild(printBtn);
|
|
1100
|
+
|
|
1101
|
+
var jsonBtn = el("button", "hdr-btn primary", ICON.download + " JSON");
|
|
1102
|
+
jsonBtn.onclick = function() {
|
|
1103
|
+
var blob = new Blob([JSON.stringify(report, null, 2)], { type: "application/json" });
|
|
1104
|
+
var url = URL.createObjectURL(blob);
|
|
1105
|
+
var a = document.createElement("a");
|
|
1106
|
+
a.href = url;
|
|
1107
|
+
a.download = "testResults-" + (report.reportId || "report").slice(0, 8) + ".json";
|
|
1108
|
+
document.body.appendChild(a); a.click(); a.remove();
|
|
1109
|
+
URL.revokeObjectURL(url);
|
|
1110
|
+
};
|
|
1111
|
+
actions.appendChild(jsonBtn);
|
|
1112
|
+
|
|
1113
|
+
inner.appendChild(actions);
|
|
1114
|
+
hdr.appendChild(inner);
|
|
1115
|
+
|
|
1116
|
+
// Meta strip
|
|
1117
|
+
var ms = el("div", "metastrip");
|
|
1118
|
+
var msInner = el("div", "metastrip-inner");
|
|
1119
|
+
var fields = [
|
|
1120
|
+
["tool", (meta.tool || "doc-detective") + "@" + (meta.version || "\\u2014")],
|
|
1121
|
+
["runtime", (meta.platform || "\\u2014") + " \\u00B7 node " + (meta.node || "\\u2014")],
|
|
1122
|
+
["branch", meta.branch || meta.commit ? (meta.branch || "\\u2014") + (meta.commit ? "@" + meta.commit.slice(0, 7) : "") : "\\u2014"],
|
|
1123
|
+
["actor", meta.actor || "\\u2014"],
|
|
1124
|
+
["duration", fmtDuration(meta.startedAt && meta.finishedAt ? new Date(meta.finishedAt) - new Date(meta.startedAt) : null)],
|
|
1125
|
+
["cwd", meta.cwd || "\\u2014"]
|
|
1126
|
+
];
|
|
1127
|
+
fields.forEach(function(f) {
|
|
1128
|
+
msInner.innerHTML += '<span class="m"><span class="k">' + f[0] + '</span> <span class="v">' + esc(f[1]) + '</span></span>';
|
|
1129
|
+
});
|
|
1130
|
+
ms.appendChild(msInner);
|
|
1131
|
+
hdr.appendChild(ms);
|
|
1132
|
+
|
|
1133
|
+
return hdr;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Verdict banner
|
|
1137
|
+
function buildVerdict() {
|
|
1138
|
+
var s = report.summary.specs;
|
|
1139
|
+
var total = s.pass + s.fail + s.warning + s.skipped;
|
|
1140
|
+
var slug = s.fail ? "fail" : s.warning ? "warn" : s.pass ? "pass" : "skip";
|
|
1141
|
+
var bigNum, note;
|
|
1142
|
+
if (s.fail) {
|
|
1143
|
+
bigNum = s.fail;
|
|
1144
|
+
note = "spec" + (s.fail > 1 ? "s" : "") + " failing";
|
|
1145
|
+
} else if (s.warning) {
|
|
1146
|
+
bigNum = s.warning;
|
|
1147
|
+
note = "warning" + (s.warning > 1 ? "s" : "");
|
|
1148
|
+
} else if (s.pass) {
|
|
1149
|
+
bigNum = s.pass;
|
|
1150
|
+
note = "spec" + (s.pass > 1 ? "s" : "") + " passed";
|
|
1151
|
+
} else {
|
|
1152
|
+
bigNum = 0;
|
|
1153
|
+
note = "specs ran";
|
|
1154
|
+
}
|
|
1155
|
+
var pct = function(n) { return total ? n / total * 100 : 0; };
|
|
1156
|
+
|
|
1157
|
+
var card = el("div", "verdict-card " + slug);
|
|
1158
|
+
card.innerHTML = '<div class="vk">Overall verdict</div>' +
|
|
1159
|
+
'<div class="vv"><div class="big">' + esc(String(bigNum)) + '</div><div class="note">' + esc(note) + '</div></div>' +
|
|
1160
|
+
'<div class="vbar" style="grid-template-columns:' + pct(s.pass) + '% ' + pct(s.fail) + '% ' + pct(s.warning) + '% ' + pct(s.skipped) + '%">' +
|
|
1161
|
+
'<span class="pass"></span><span class="fail"></span><span class="warn"></span><span class="skip"></span></div>';
|
|
1162
|
+
return card;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function buildSumTile(label, counts, levelLabel) {
|
|
1166
|
+
var total = counts.pass + counts.fail + counts.warning + counts.skipped;
|
|
1167
|
+
var kind = counts.fail ? "fail" : counts.warning ? "warn" : counts.pass ? "pass" : "skip";
|
|
1168
|
+
var primaryN = kind === "pass" ? counts.pass : kind === "fail" ? counts.fail : kind === "warn" ? counts.warning : counts.skipped;
|
|
1169
|
+
var cols = total
|
|
1170
|
+
? (counts.pass / total * 100) + "% " + (counts.fail / total * 100) + "% " + (counts.warning / total * 100) + "% " + (counts.skipped / total * 100) + "%"
|
|
1171
|
+
: "1fr";
|
|
1172
|
+
|
|
1173
|
+
var barSegments = total
|
|
1174
|
+
? '<span class="p"></span><span class="f"></span><span class="w"></span><span class="s"></span>'
|
|
1175
|
+
: '';
|
|
1176
|
+
|
|
1177
|
+
var tile = el("div", "sum " + kind);
|
|
1178
|
+
tile.innerHTML = '<div class="corner-stripe"></div><div><div class="lbl">' + esc(label) + '</div>' +
|
|
1179
|
+
'<div class="row"><div class="num">' + primaryN + '</div><div class="of">of ' + total + " " + esc(levelLabel) + '</div></div></div>' +
|
|
1180
|
+
'<div><div class="miniBar" style="grid-template-columns:' + cols + '">' +
|
|
1181
|
+
barSegments + '</div>' +
|
|
1182
|
+
'<div class="legend">' +
|
|
1183
|
+
'<span><i style="background:var(--dd-pass)"></i>' + counts.pass + ' pass</span>' +
|
|
1184
|
+
'<span><i style="background:var(--dd-fail)"></i>' + counts.fail + ' fail</span>' +
|
|
1185
|
+
'<span><i style="background:var(--dd-warn)"></i>' + counts.warning + ' warn</span>' +
|
|
1186
|
+
'<span><i style="background:var(--dd-skip)"></i>' + counts.skipped + ' skip</span></div></div>';
|
|
1187
|
+
return tile;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Step detail
|
|
1191
|
+
function buildStepDetail(step) {
|
|
1192
|
+
var slug = statusSlug(step.result);
|
|
1193
|
+
var ak = actionKey(step);
|
|
1194
|
+
var media = collectMedia(step);
|
|
1195
|
+
var detail = el("div", "step-detail " + slug);
|
|
1196
|
+
detail.onclick = function(e) { e.stopPropagation(); };
|
|
1197
|
+
|
|
1198
|
+
if (step.resultDescription) {
|
|
1199
|
+
detail.innerHTML += '<div class="result-note">' + statusIcon(slug) +
|
|
1200
|
+
'<div class="body">' + esc(step.resultDescription) + '</div></div>';
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (media.length) {
|
|
1204
|
+
var mp = '<div class="media-panel"><div class="dp-head"><span>MEDIA \\u00B7 ' + media.length + ' item' + (media.length > 1 ? 's' : '') + '</span></div><div class="mp-body">';
|
|
1205
|
+
media.forEach(function(m) {
|
|
1206
|
+
mp += '<div class="media-thumb" data-media-path="' + escAttr(m.path || '') + '" data-media-kind="' + escAttr(m.kind) + '">' +
|
|
1207
|
+
'<span class="kind">' + (m.kind === "video" ? "MP4" : "PNG") + '</span>' +
|
|
1208
|
+
(m.changed ? '<span class="changed-flag">UPDATED</span>' : '') +
|
|
1209
|
+
(m.kind === "video"
|
|
1210
|
+
? '<video src="' + escAttr(m.path || '') + '" muted playsinline preload="metadata"></video>'
|
|
1211
|
+
: '<img src="' + escAttr(m.path || '') + '" alt="' + escAttr(m.path || '') + '" onerror="this.style.display=\\'none\\'"/>') +
|
|
1212
|
+
'<div class="cap">' + esc(m.path || '') + '</div></div>';
|
|
1213
|
+
});
|
|
1214
|
+
mp += '</div></div>';
|
|
1215
|
+
detail.innerHTML += mp;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Input/output panels
|
|
1219
|
+
var rest = Object.assign({}, step);
|
|
1220
|
+
delete rest.result; delete rest.resultDescription; delete rest.stepId; delete rest.outputs; delete rest.description; delete rest.duration;
|
|
1221
|
+
var inputJson = JSON.stringify(rest, null, 2);
|
|
1222
|
+
var outputJson = step.outputs && Object.keys(step.outputs).length ? JSON.stringify(step.outputs, null, 2) : null;
|
|
1223
|
+
|
|
1224
|
+
var grid = el("div", "detail-grid");
|
|
1225
|
+
grid.innerHTML = '<div class="detail-panel"><div class="dp-head"><span>INPUT \\u00B7 ' + esc(ak) + '</span><button class="copy-btn">' + ICON.copy + ' Copy</button></div><pre>' + hlJson(inputJson) + '</pre></div>';
|
|
1226
|
+
if (outputJson) {
|
|
1227
|
+
grid.innerHTML += '<div class="detail-panel"><div class="dp-head"><span>OUTPUTS</span><button class="copy-btn">' + ICON.copy + ' Copy</button></div><pre>' + hlJson(outputJson) + '</pre></div>';
|
|
1228
|
+
}
|
|
1229
|
+
detail.appendChild(grid);
|
|
1230
|
+
|
|
1231
|
+
return detail;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Build step row
|
|
1235
|
+
function stepKey(step, ctxId, idx) {
|
|
1236
|
+
return step.stepId || (ctxId + ":" + idx);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function buildStep(step, idx, ctxId, keyIdx) {
|
|
1240
|
+
var slug = statusSlug(step.result);
|
|
1241
|
+
var ak = actionKey(step);
|
|
1242
|
+
var sk = stepKey(step, ctxId, keyIdx == null ? idx : keyIdx);
|
|
1243
|
+
var primary = step.description
|
|
1244
|
+
|| (step.goTo && "Go to " + step.goTo)
|
|
1245
|
+
|| (step.httpRequest && (step.httpRequest.method || "GET") + " " + (step.httpRequest.url || ""))
|
|
1246
|
+
|| (step.runShell && (step.runShell.command || "") + " " + (step.runShell.args || []).join(" "))
|
|
1247
|
+
|| step.resultDescription || "(step)";
|
|
1248
|
+
|
|
1249
|
+
var isOpen = !!state.openSteps[sk];
|
|
1250
|
+
var row = el("div", "step " + slug + (isOpen ? " open" : ""));
|
|
1251
|
+
row.innerHTML = '<span class="chev">' + ICON.chevron + '</span>' +
|
|
1252
|
+
badge(step.result) + tag(ak) +
|
|
1253
|
+
'<span class="desc" title="' + escAttr(primary) + '"><span style="color:var(--fg3);font-family:var(--font-mono);font-size:11px;margin-right:8px">' + String(idx + 1).padStart(2, "0") + '</span>' + esc(primary) + '</span>' +
|
|
1254
|
+
'<span class="dur">' + fmtDuration(step.duration) + '</span>';
|
|
1255
|
+
|
|
1256
|
+
if (isOpen) {
|
|
1257
|
+
row.appendChild(buildStepDetail(step));
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
wireDisclosure(row, isOpen, function(e) {
|
|
1261
|
+
if (e && e.target && e.target.closest && e.target.closest(".step-detail")) return;
|
|
1262
|
+
state.openSteps[sk] = !state.openSteps[sk];
|
|
1263
|
+
render();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
return row;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Context block
|
|
1270
|
+
function buildContext(ctx) {
|
|
1271
|
+
var browser = ctx.browser && ctx.browser.name;
|
|
1272
|
+
var headless = ctx.browser && ctx.browser.headless;
|
|
1273
|
+
var vw = ctx.browser && ctx.browser.viewport;
|
|
1274
|
+
var contextLabel = browser ? browser + " / " + ctx.platform : ctx.platform || "shell";
|
|
1275
|
+
if (headless) contextLabel += " \\u00B7 headless";
|
|
1276
|
+
if (vw) contextLabel += " \\u00B7 " + vw.width + "\\u00D7" + vw.height;
|
|
1277
|
+
|
|
1278
|
+
var block = el("div", "context");
|
|
1279
|
+
var head = el("div", "context-head");
|
|
1280
|
+
head.innerHTML = ICON.chip +
|
|
1281
|
+
'<span class="what">' + esc(contextLabel) + '</span>' +
|
|
1282
|
+
'<span class="meta">' + ICON.finger + ' ' + esc((ctx.contextId || "").slice(0, 8)) + '</span>' +
|
|
1283
|
+
badge(ctx.result);
|
|
1284
|
+
block.appendChild(head);
|
|
1285
|
+
|
|
1286
|
+
var visibleSteps = (ctx.steps || [])
|
|
1287
|
+
.map(function(s, i) { return { step: s, origIdx: i }; })
|
|
1288
|
+
.filter(function(item) {
|
|
1289
|
+
var s = item.step;
|
|
1290
|
+
if (state.statusFilters.size && !state.statusFilters.has(s.result)) return false;
|
|
1291
|
+
if (state.query) return JSON.stringify(s).toLowerCase().indexOf(state.query.toLowerCase()) !== -1;
|
|
1292
|
+
return true;
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
if (visibleSteps.length === 0) {
|
|
1296
|
+
block.innerHTML += '<div class="empty" style="margin:8px 16px 10px;padding:16px">No steps match the current filter.</div>';
|
|
1297
|
+
} else {
|
|
1298
|
+
var stepsDiv = el("div", "steps");
|
|
1299
|
+
var cId = ctx.contextId || "";
|
|
1300
|
+
visibleSteps.forEach(function(item, i) {
|
|
1301
|
+
stepsDiv.appendChild(buildStep(item.step, i, cId, item.origIdx));
|
|
1302
|
+
});
|
|
1303
|
+
block.appendChild(stepsDiv);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
return block;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Test card
|
|
1310
|
+
function buildTest(test) {
|
|
1311
|
+
var slug = statusSlug(test.result);
|
|
1312
|
+
var isOpen = isTestOpen(test.testId, test);
|
|
1313
|
+
var card = el("div", "test " + slug + (isOpen ? " open" : ""));
|
|
1314
|
+
|
|
1315
|
+
var stepAgg = { pass: 0, fail: 0, warning: 0, skipped: 0 };
|
|
1316
|
+
(test.contexts || []).forEach(function(c) {
|
|
1317
|
+
(c.steps || []).forEach(function(s) {
|
|
1318
|
+
var k = (s.result || "").toLowerCase();
|
|
1319
|
+
if (k in stepAgg) stepAgg[k]++;
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
var nCtx = (test.contexts || []).length;
|
|
1324
|
+
var head = el("div", "test-head");
|
|
1325
|
+
head.innerHTML = '<span class="chev">' + ICON.chevron + '</span>' +
|
|
1326
|
+
badge(test.result) +
|
|
1327
|
+
'<div class="title-col"><div class="title">' + esc(test.description || test.testId) +
|
|
1328
|
+
' ' + tag(nCtx + " context" + (nCtx !== 1 ? "s" : "")) + '</div>' +
|
|
1329
|
+
(test.contentPath ? '<div class="desc" style="font-family:var(--font-mono);font-size:12px;color:var(--fg3)">' + ICON.file + ' ' + esc(test.contentPath) + '</div>' : '') +
|
|
1330
|
+
'</div>' +
|
|
1331
|
+
'<div class="metrics">' +
|
|
1332
|
+
metric("pass", stepAgg.pass, "pass") + metric("fail", stepAgg.fail, "fail") +
|
|
1333
|
+
metric("warn", stepAgg.warning, "warn") + metric("skip", stepAgg.skipped, "skip") + '</div>';
|
|
1334
|
+
|
|
1335
|
+
wireDisclosure(head, isOpen, function() {
|
|
1336
|
+
state.openTests[test.testId] = !isOpen;
|
|
1337
|
+
render();
|
|
1338
|
+
});
|
|
1339
|
+
card.appendChild(head);
|
|
1340
|
+
|
|
1341
|
+
if (isOpen) {
|
|
1342
|
+
var body = el("div", "test-body");
|
|
1343
|
+
(test.contexts || []).forEach(function(c) { body.appendChild(buildContext(c)); });
|
|
1344
|
+
card.appendChild(body);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
return card;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Spec card
|
|
1351
|
+
function buildSpec(spec) {
|
|
1352
|
+
var slug = statusSlug(spec.result);
|
|
1353
|
+
var isOpen = isSpecOpen(spec.specId, spec);
|
|
1354
|
+
var counts = countTree(spec);
|
|
1355
|
+
var card = el("div", "spec " + slug + (isOpen ? " open" : ""));
|
|
1356
|
+
|
|
1357
|
+
card.innerHTML = '<div class="stripe"></div>';
|
|
1358
|
+
|
|
1359
|
+
var head = el("div", "spec-head");
|
|
1360
|
+
head.innerHTML = '<span class="chev">' + ICON.chevron + '</span>' +
|
|
1361
|
+
badge(spec.result) +
|
|
1362
|
+
'<div class="title-col"><div class="title">' + esc(spec.description || spec.specId) +
|
|
1363
|
+
(spec.specPath ? ' <span class="path" title="' + escAttr(spec.specPath) + '">' + ICON.file + ' ' + esc(spec.specPath) + '</span>' : '') +
|
|
1364
|
+
'</div>' +
|
|
1365
|
+
(spec.contentPath && spec.contentPath !== spec.specPath
|
|
1366
|
+
? '<div class="desc">Source: <code style="font-family:var(--font-mono);font-size:12px">' + esc(spec.contentPath) + '</code></div>'
|
|
1367
|
+
: '') +
|
|
1368
|
+
'</div>' +
|
|
1369
|
+
'<div class="metrics"><span class="m">' + counts.tests + ' test' + (counts.tests !== 1 ? 's' : '') + '</span><span class="sep"></span>' +
|
|
1370
|
+
metric("pass", counts.stepCounts.pass, "") + metric("fail", counts.stepCounts.fail, "") +
|
|
1371
|
+
metric("warn", counts.stepCounts.warning, "") + metric("skip", counts.stepCounts.skipped, "") + '</div>';
|
|
1372
|
+
|
|
1373
|
+
wireDisclosure(head, isOpen, function() {
|
|
1374
|
+
state.openSpecs[spec.specId] = !isOpen;
|
|
1375
|
+
render();
|
|
1376
|
+
});
|
|
1377
|
+
card.appendChild(head);
|
|
1378
|
+
|
|
1379
|
+
if (isOpen) {
|
|
1380
|
+
var body = el("div", "spec-body");
|
|
1381
|
+
(spec.tests || []).forEach(function(t) { body.appendChild(buildTest(t)); });
|
|
1382
|
+
card.appendChild(body);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
return card;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Precompute lowercase search strings once per spec
|
|
1389
|
+
var specSearchCache = new WeakMap();
|
|
1390
|
+
function getSpecSearchStr(sp) {
|
|
1391
|
+
if (specSearchCache.has(sp)) return specSearchCache.get(sp);
|
|
1392
|
+
var s = JSON.stringify(sp).toLowerCase();
|
|
1393
|
+
specSearchCache.set(sp, s);
|
|
1394
|
+
return s;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Filter specs
|
|
1398
|
+
function getVisibleSpecs() {
|
|
1399
|
+
return (report.specs || []).filter(function(sp) {
|
|
1400
|
+
if (state.statusFilters.size && !state.statusFilters.has(sp.result)) {
|
|
1401
|
+
var hasMatching = (sp.tests || []).some(function(t) {
|
|
1402
|
+
return (t.contexts || []).some(function(c) {
|
|
1403
|
+
return (c.steps || []).some(function(s) { return state.statusFilters.has(s.result); });
|
|
1404
|
+
});
|
|
1405
|
+
});
|
|
1406
|
+
if (!hasMatching) return false;
|
|
1407
|
+
}
|
|
1408
|
+
if (state.query) {
|
|
1409
|
+
if (getSpecSearchStr(sp).indexOf(state.query.toLowerCase()) === -1) return false;
|
|
1410
|
+
}
|
|
1411
|
+
return true;
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// Render
|
|
1416
|
+
function render() {
|
|
1417
|
+
applyTheme();
|
|
1418
|
+
var root = document.getElementById("root");
|
|
1419
|
+
root.innerHTML = "";
|
|
1420
|
+
var app = el("div", "app");
|
|
1421
|
+
|
|
1422
|
+
app.appendChild(buildHeader());
|
|
1423
|
+
|
|
1424
|
+
var main = el("main");
|
|
1425
|
+
|
|
1426
|
+
// Verdict + summary
|
|
1427
|
+
var verdict = el("section", "verdict");
|
|
1428
|
+
verdict.appendChild(buildVerdict());
|
|
1429
|
+
var summary = el("div", "summary");
|
|
1430
|
+
summary.appendChild(buildSumTile("Specs", report.summary.specs, "specs"));
|
|
1431
|
+
summary.appendChild(buildSumTile("Tests", report.summary.tests, "tests"));
|
|
1432
|
+
summary.appendChild(buildSumTile("Contexts", report.summary.contexts, "contexts"));
|
|
1433
|
+
summary.appendChild(buildSumTile("Steps", report.summary.steps, "steps"));
|
|
1434
|
+
verdict.appendChild(summary);
|
|
1435
|
+
main.appendChild(verdict);
|
|
1436
|
+
|
|
1437
|
+
// Toolbar
|
|
1438
|
+
var visibleSpecs = getVisibleSpecs();
|
|
1439
|
+
var toolbar = el("div", "toolbar");
|
|
1440
|
+
toolbar.innerHTML = '<h2>Specifications</h2><span class="count">' + visibleSpecs.length + ' of ' + report.specs.length + ' shown</span>';
|
|
1441
|
+
|
|
1442
|
+
STATUS_ORDER.forEach(function(s) {
|
|
1443
|
+
var m = STATUS_META[s];
|
|
1444
|
+
var active = state.statusFilters.has(s);
|
|
1445
|
+
var n = report.summary.specs[m.slug === "warn" ? "warning" : m.slug === "skip" ? "skipped" : m.slug];
|
|
1446
|
+
var btn = el("button", "filter " + m.slug + (active ? " active" : ""), '<span class="d"></span>' + m.label + ' \\u00B7 ' + n);
|
|
1447
|
+
btn.onclick = function() {
|
|
1448
|
+
if (state.statusFilters.has(s)) state.statusFilters.delete(s); else state.statusFilters.add(s);
|
|
1449
|
+
render();
|
|
1450
|
+
};
|
|
1451
|
+
toolbar.appendChild(btn);
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
if (state.statusFilters.size > 0 || state.query) {
|
|
1455
|
+
var clearBtn = el("button", "linkbtn", "Clear filters");
|
|
1456
|
+
clearBtn.onclick = function() { state.statusFilters.clear(); state.query = ""; render(); };
|
|
1457
|
+
toolbar.appendChild(clearBtn);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
toolbar.appendChild(el("span", "spacer"));
|
|
1461
|
+
|
|
1462
|
+
var searchDiv = el("div", "search-input");
|
|
1463
|
+
searchDiv.innerHTML = ICON.search;
|
|
1464
|
+
var searchInput = document.createElement("input");
|
|
1465
|
+
searchInput.placeholder = "Search specs, tests, steps, paths\\u2026";
|
|
1466
|
+
searchInput.value = state.query;
|
|
1467
|
+
var searchTimer = null;
|
|
1468
|
+
searchInput.oninput = function() {
|
|
1469
|
+
state.query = searchInput.value;
|
|
1470
|
+
clearTimeout(searchTimer);
|
|
1471
|
+
searchTimer = setTimeout(render, 200);
|
|
1472
|
+
};
|
|
1473
|
+
searchDiv.appendChild(searchInput);
|
|
1474
|
+
toolbar.appendChild(searchDiv);
|
|
1475
|
+
|
|
1476
|
+
toolbar.appendChild(el("span", "spacer"));
|
|
1477
|
+
var expandBtn = el("button", "linkbtn", ICON.expand + ' Expand all');
|
|
1478
|
+
expandBtn.onclick = function() {
|
|
1479
|
+
(report.specs || []).forEach(function(sp) {
|
|
1480
|
+
state.openSpecs[sp.specId] = true;
|
|
1481
|
+
(sp.tests || []).forEach(function(t) {
|
|
1482
|
+
state.openTests[t.testId] = true;
|
|
1483
|
+
(t.contexts || []).forEach(function(c) {
|
|
1484
|
+
(c.steps || []).forEach(function(s, i) { state.openSteps[stepKey(s, c.contextId || "", i)] = true; });
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
render();
|
|
1489
|
+
};
|
|
1490
|
+
toolbar.appendChild(expandBtn);
|
|
1491
|
+
|
|
1492
|
+
var collapseBtn = el("button", "linkbtn", ICON.collapse + ' Collapse');
|
|
1493
|
+
collapseBtn.onclick = function() {
|
|
1494
|
+
(report.specs || []).forEach(function(sp) {
|
|
1495
|
+
state.openSpecs[sp.specId] = false;
|
|
1496
|
+
(sp.tests || []).forEach(function(t) {
|
|
1497
|
+
state.openTests[t.testId] = false;
|
|
1498
|
+
(t.contexts || []).forEach(function(c) {
|
|
1499
|
+
(c.steps || []).forEach(function(s, i) { state.openSteps[stepKey(s, c.contextId || "", i)] = false; });
|
|
1500
|
+
});
|
|
1501
|
+
});
|
|
1502
|
+
});
|
|
1503
|
+
render();
|
|
1504
|
+
};
|
|
1505
|
+
toolbar.appendChild(collapseBtn);
|
|
1506
|
+
|
|
1507
|
+
main.appendChild(toolbar);
|
|
1508
|
+
|
|
1509
|
+
// Spec list
|
|
1510
|
+
if (visibleSpecs.length === 0) {
|
|
1511
|
+
var empty = el("div", "empty");
|
|
1512
|
+
empty.innerHTML = '<div style="font-size:18px;color:var(--fg2);margin-bottom:6px">Nothing matches the current filter.</div>';
|
|
1513
|
+
var clrBtn = el("button", "linkbtn", "Clear filters");
|
|
1514
|
+
clrBtn.onclick = function() { state.statusFilters.clear(); state.query = ""; render(); };
|
|
1515
|
+
empty.appendChild(clrBtn);
|
|
1516
|
+
main.appendChild(empty);
|
|
1517
|
+
} else {
|
|
1518
|
+
visibleSpecs.forEach(function(sp) { main.appendChild(buildSpec(sp)); });
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
app.appendChild(main);
|
|
1522
|
+
root.appendChild(app);
|
|
1523
|
+
|
|
1524
|
+
// Lightbox
|
|
1525
|
+
if (state.lightbox) {
|
|
1526
|
+
var lb = el("div", "lightbox");
|
|
1527
|
+
var m = state.lightbox;
|
|
1528
|
+
lb.innerHTML = '<button class="close" aria-label="Close lightbox">' + ICON.close + '</button>' +
|
|
1529
|
+
(m.kind === "video"
|
|
1530
|
+
? '<video src="' + escAttr(m.path) + '" controls autoplay></video>'
|
|
1531
|
+
: '<img src="' + escAttr(m.path) + '" alt="' + escAttr(m.path) + '"/>') +
|
|
1532
|
+
'<div class="cap">' + esc(m.path) + '</div>';
|
|
1533
|
+
lb.onclick = function(e) {
|
|
1534
|
+
// only close on backdrop clicks
|
|
1535
|
+
if (e.target === lb) { state.lightbox = null; render(); }
|
|
1536
|
+
};
|
|
1537
|
+
var closeEl = lb.querySelector(".close");
|
|
1538
|
+
if (closeEl) closeEl.onclick = function(e) {
|
|
1539
|
+
e.stopPropagation();
|
|
1540
|
+
state.lightbox = null;
|
|
1541
|
+
render();
|
|
1542
|
+
};
|
|
1543
|
+
var mediaEl = lb.querySelector("img,video");
|
|
1544
|
+
if (mediaEl) mediaEl.onclick = function(e) { e.stopPropagation(); };
|
|
1545
|
+
root.appendChild(lb);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Attach copy button handlers — read text from sibling <pre> element
|
|
1549
|
+
root.querySelectorAll(".copy-btn").forEach(function(btn) {
|
|
1550
|
+
btn.onclick = function(e) {
|
|
1551
|
+
e.stopPropagation();
|
|
1552
|
+
var panel = btn.closest(".detail-panel");
|
|
1553
|
+
var pre = panel && panel.querySelector("pre");
|
|
1554
|
+
var text = pre ? pre.textContent : "";
|
|
1555
|
+
if (navigator.clipboard) {
|
|
1556
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
1557
|
+
btn.innerHTML = ICON.check + " Copied";
|
|
1558
|
+
setTimeout(function() { btn.innerHTML = ICON.copy + " Copy"; }, 1200);
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
// Attach media click handlers
|
|
1565
|
+
root.querySelectorAll(".media-thumb").forEach(function(thumb) {
|
|
1566
|
+
thumb.onclick = function(e) {
|
|
1567
|
+
e.stopPropagation();
|
|
1568
|
+
state.lightbox = { path: thumb.getAttribute("data-media-path"), kind: thumb.getAttribute("data-media-kind") };
|
|
1569
|
+
render();
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
// Focus search if it had focus
|
|
1574
|
+
if (document.activeElement === document.body && state.query) {
|
|
1575
|
+
var si = root.querySelector(".search-input input");
|
|
1576
|
+
if (si) { si.focus(); si.setSelectionRange(si.value.length, si.value.length); }
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Escape key closes lightbox
|
|
1581
|
+
document.addEventListener("keydown", function(e) {
|
|
1582
|
+
if (e.key === "Escape" && state.lightbox) { state.lightbox = null; render(); }
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
// Initial render
|
|
1586
|
+
render();
|
|
1587
|
+
})();
|
|
1588
|
+
`;
|
|
1589
|
+
//# sourceMappingURL=htmlReporter.js.map
|