argusqa-os 9.7.6 → 9.8.0
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/README.md +9 -9
- package/glama.json +2 -2
- package/package.json +6 -6
- package/src/cli/pr-validate.js +275 -56
- package/src/mcp-server.js +142 -26
- package/src/orchestration/crawl-and-report.js +1 -1
- package/src/orchestration/orchestrator.js +34 -0
- package/src/utils/audit-depth.js +148 -0
- package/src/utils/deploy-preview.js +210 -0
- package/src/utils/github-api.js +242 -0
- package/src/utils/github-reporter.js +251 -39
- package/src/utils/html-reporter.js +283 -92
- package/src/utils/import-graph.js +290 -0
- package/src/utils/parallel-crawler.js +202 -0
- package/src/utils/pr-baseline.js +230 -0
- package/src/utils/pr-diff-analyzer.js +378 -40
- package/src/utils/route-discoverer.js +25 -3
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* ARGUS HTML Report Generator — D7.1
|
|
4
4
|
*
|
|
5
5
|
* Converts the latest (or a specified) JSON report into a single self-contained
|
|
6
|
-
* report.html with screenshots inlined as base64 data URIs.
|
|
7
|
-
*
|
|
6
|
+
* report.html with screenshots inlined as base64 data URIs. Styled to match the
|
|
7
|
+
* Argus brand (argus-qa.com): #5E0ED7 accent, ring+dot logo, Inter / JetBrains
|
|
8
|
+
* Mono type. Fonts load from Google Fonts when online and fall back to a system
|
|
9
|
+
* stack offline, so the report still renders cleanly with no network — the only
|
|
10
|
+
* hard requirement (inlined screenshots) is met regardless.
|
|
8
11
|
*
|
|
9
12
|
* Usage:
|
|
10
13
|
* node src/utils/html-reporter.js # auto-picks latest report
|
|
@@ -24,22 +27,19 @@ const logger = childLogger('html-reporter');
|
|
|
24
27
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
28
|
const REPORTS_DIR = path.resolve(__dirname, '../../reports');
|
|
26
29
|
|
|
30
|
+
// ── Brand ───────────────────────────────────────────────────────────────────
|
|
31
|
+
const ACCENT = '#5E0ED7';
|
|
32
|
+
|
|
27
33
|
// ── Severity helpers ──────────────────────────────────────────────────────────
|
|
28
34
|
|
|
29
|
-
const
|
|
30
|
-
critical: {
|
|
31
|
-
warning: {
|
|
32
|
-
info: {
|
|
35
|
+
const SEV = {
|
|
36
|
+
critical: { cls: 'critical', label: 'CRITICAL' },
|
|
37
|
+
warning: { cls: 'warning', label: 'WARNING' },
|
|
38
|
+
info: { cls: 'info', label: 'INFO' },
|
|
33
39
|
};
|
|
34
40
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
return `background:${c.bg};border-left:4px solid ${c.border};color:${c.text}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function sevBadge(sev) {
|
|
41
|
-
const c = SEV_COLOR[sev] ?? SEV_COLOR.info;
|
|
42
|
-
return `<span style="background:${c.badge};color:#fff;border-radius:3px;font-size:11px;font-weight:700;padding:2px 7px;letter-spacing:.5px;white-space:nowrap">${c.label}</span>`;
|
|
41
|
+
function sevOf(sev) {
|
|
42
|
+
return SEV[sev] ? sev : 'info';
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function esc(str) {
|
|
@@ -55,6 +55,15 @@ function safeHref(url) {
|
|
|
55
55
|
return /^https?:\/\//i.test(s) ? esc(s) : '#';
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// ── Logo (ring + dot — replicates landing/public/favicon.svg) ──────────────────
|
|
59
|
+
|
|
60
|
+
function logoSvg(size = 34, ring = '#7b3fe4', dot = '#9b6cf2') {
|
|
61
|
+
return `<svg class="logo" viewBox="0 0 32 32" width="${size}" height="${size}" aria-hidden="true">
|
|
62
|
+
<circle cx="16" cy="16" r="14" fill="none" stroke="${ring}" stroke-width="2.5"/>
|
|
63
|
+
<circle cx="16" cy="16" r="5" fill="${dot}"/>
|
|
64
|
+
</svg>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
// ── Screenshot embedding ──────────────────────────────────────────────────────
|
|
59
68
|
|
|
60
69
|
function imgTag(filePath, alt = 'Screenshot', style = '') {
|
|
@@ -63,29 +72,29 @@ function imgTag(filePath, alt = 'Screenshot', style = '') {
|
|
|
63
72
|
const buf = fs.readFileSync(filePath);
|
|
64
73
|
const ext = path.extname(filePath).slice(1).toLowerCase() || 'png';
|
|
65
74
|
const mime = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : 'image/png';
|
|
66
|
-
return `<img src="data:${mime};base64,${buf.toString('base64')}" alt="${esc(alt)}" style="
|
|
75
|
+
return `<img class="shot" src="data:${mime};base64,${buf.toString('base64')}" alt="${esc(alt)}" style="${style}">`;
|
|
67
76
|
} catch {
|
|
68
|
-
return `<p
|
|
77
|
+
return `<p class="shot-missing">Screenshot not found: ${esc(path.basename(filePath))}</p>`;
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
// ── Finding renderer ──────────────────────────────────────────────────────────
|
|
73
82
|
|
|
74
83
|
function renderFinding(e) {
|
|
75
|
-
const sev = e.severity
|
|
84
|
+
const sev = sevOf(e.severity);
|
|
76
85
|
const type = esc(e.type ?? 'unknown');
|
|
77
86
|
const msg = esc(e.message ?? e.description ?? (e.requestUrl ? `HTTP ${e.status ?? '?'} ${e.requestUrl}` : ''));
|
|
78
|
-
const flaky = e.flaky ? ' <span
|
|
79
|
-
const isNew = e.isNew ? ' <span
|
|
87
|
+
const flaky = e.flaky ? ' <span class="tag tag-flaky">⚡ flaky</span>' : '';
|
|
88
|
+
const isNew = e.isNew ? ' <span class="tag tag-new">★ new</span>' : '';
|
|
80
89
|
return `
|
|
81
|
-
<div
|
|
82
|
-
<div
|
|
83
|
-
${
|
|
84
|
-
<code
|
|
90
|
+
<div class="finding ${sev}">
|
|
91
|
+
<div class="finding-head">
|
|
92
|
+
<span class="chip ${sev}">${SEV[sev].label}</span>
|
|
93
|
+
<code class="type">${type}</code>
|
|
85
94
|
${flaky}${isNew}
|
|
86
95
|
</div>
|
|
87
|
-
<div
|
|
88
|
-
${e.requestUrl ? `<div
|
|
96
|
+
<div class="finding-msg">${msg}</div>
|
|
97
|
+
${e.requestUrl ? `<div class="finding-url">${esc(e.requestUrl)}</div>` : ''}
|
|
89
98
|
</div>`;
|
|
90
99
|
}
|
|
91
100
|
|
|
@@ -97,19 +106,16 @@ function renderRoute(route) {
|
|
|
97
106
|
const warnings = errors.filter(e => e.severity === 'warning');
|
|
98
107
|
const infos = errors.filter(e => e.severity === 'info');
|
|
99
108
|
|
|
100
|
-
const
|
|
101
|
-
: warnings.length
|
|
102
|
-
: '
|
|
103
|
-
const headerBg = criticals.length > 0 ? '#fef2f2'
|
|
104
|
-
: warnings.length > 0 ? '#fffbeb'
|
|
105
|
-
: '#f0fdf4';
|
|
109
|
+
const state = criticals.length > 0 ? 'critical'
|
|
110
|
+
: warnings.length > 0 ? 'warning'
|
|
111
|
+
: 'ok';
|
|
106
112
|
|
|
107
113
|
// Summary pill row
|
|
108
114
|
const pills = [
|
|
109
|
-
criticals.length > 0 ? `<span
|
|
110
|
-
warnings.length > 0 ? `<span
|
|
111
|
-
infos.length > 0 ? `<span
|
|
112
|
-
errors.length === 0 ? `<span
|
|
115
|
+
criticals.length > 0 ? `<span class="pill critical">${criticals.length} critical</span>` : '',
|
|
116
|
+
warnings.length > 0 ? `<span class="pill warning">${warnings.length} warning</span>` : '',
|
|
117
|
+
infos.length > 0 ? `<span class="pill info">${infos.length} info</span>` : '',
|
|
118
|
+
errors.length === 0 ? `<span class="pill ok">✓ clean</span>` : '',
|
|
113
119
|
].filter(Boolean).join(' ');
|
|
114
120
|
|
|
115
121
|
// Screenshot
|
|
@@ -121,42 +127,43 @@ function renderRoute(route) {
|
|
|
121
127
|
const viewports = Object.entries(route.responsiveScreenshots)
|
|
122
128
|
.sort(([a], [b]) => Number(a) - Number(b))
|
|
123
129
|
.map(([vp, fp]) => `
|
|
124
|
-
<
|
|
125
|
-
<
|
|
130
|
+
<figure class="vp">
|
|
131
|
+
<figcaption>${esc(String(vp))}px</figcaption>
|
|
126
132
|
${imgTag(fp, `${route.route} at ${vp}px`, 'width:100%')}
|
|
127
|
-
</
|
|
133
|
+
</figure>`).join('');
|
|
128
134
|
responsiveGrid = `
|
|
129
|
-
<div
|
|
130
|
-
<h4
|
|
131
|
-
<div
|
|
135
|
+
<div class="route-block">
|
|
136
|
+
<h4 class="block-title">Responsive snapshots</h4>
|
|
137
|
+
<div class="vp-grid">${viewports}</div>
|
|
132
138
|
</div>`;
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
// Findings list
|
|
136
142
|
const findingRows = errors.length > 0
|
|
137
143
|
? errors.map(renderFinding).join('')
|
|
138
|
-
: `<p
|
|
144
|
+
: `<p class="all-clear">✓ No issues detected on this route.</p>`;
|
|
139
145
|
|
|
140
146
|
return `
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
+
<article class="route ${state}">
|
|
148
|
+
<header class="route-head">
|
|
149
|
+
<div class="route-id">
|
|
150
|
+
<span class="dot ${state}"></span>
|
|
151
|
+
<div>
|
|
152
|
+
<h3 class="route-name">${esc(route.route)}</h3>
|
|
153
|
+
<a class="route-url" href="${safeHref(route.url)}">${esc(route.url)}</a>
|
|
154
|
+
</div>
|
|
147
155
|
</div>
|
|
148
|
-
<div
|
|
149
|
-
</
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
${shot ? `<div style="margin-bottom:16px">${shot}</div>` : ''}
|
|
156
|
+
<div class="pills">${pills}</div>
|
|
157
|
+
</header>
|
|
158
|
+
<div class="route-body">
|
|
159
|
+
${shot ? `<div class="route-block">${shot}</div>` : ''}
|
|
153
160
|
${responsiveGrid}
|
|
154
|
-
<div
|
|
155
|
-
<h4
|
|
161
|
+
<div class="route-block">
|
|
162
|
+
<h4 class="block-title">Findings</h4>
|
|
156
163
|
${findingRows}
|
|
157
164
|
</div>
|
|
158
165
|
</div>
|
|
159
|
-
</
|
|
166
|
+
</article>`;
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
// ── Flow card ─────────────────────────────────────────────────────────────────
|
|
@@ -164,19 +171,22 @@ function renderRoute(route) {
|
|
|
164
171
|
function renderFlow(flow) {
|
|
165
172
|
const status = flow.status ?? 'unknown';
|
|
166
173
|
const findings = flow.findings ?? [];
|
|
167
|
-
const
|
|
174
|
+
const pass = status === 'pass';
|
|
168
175
|
const findingRows = findings.length > 0
|
|
169
176
|
? findings.map(renderFinding).join('')
|
|
170
|
-
: '<p
|
|
177
|
+
: '<p class="all-clear">✓ All assertions passed.</p>';
|
|
171
178
|
|
|
172
179
|
return `
|
|
173
|
-
<
|
|
174
|
-
<
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
<article class="route ${pass ? 'ok' : 'critical'}">
|
|
181
|
+
<header class="route-head">
|
|
182
|
+
<div class="route-id">
|
|
183
|
+
<span class="dot ${pass ? 'ok' : 'critical'}"></span>
|
|
184
|
+
<h3 class="route-name">${esc(flow.flowName ?? flow.name ?? 'Flow')}</h3>
|
|
185
|
+
</div>
|
|
186
|
+
<span class="pill ${pass ? 'ok' : 'critical'}">${esc(status.toUpperCase())} · ${flow.stepsCompleted ?? '?'}/${flow.totalSteps ?? '?'} steps</span>
|
|
187
|
+
</header>
|
|
188
|
+
<div class="route-body"><div class="route-block">${findingRows}</div></div>
|
|
189
|
+
</article>`;
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
// ── Full HTML document ────────────────────────────────────────────────────────
|
|
@@ -185,26 +195,36 @@ function buildHtml(report) {
|
|
|
185
195
|
const { generatedAt, baseUrl, summary, routes = [], flows = [] } = report;
|
|
186
196
|
const runDate = new Date(generatedAt).toLocaleString(undefined, { dateStyle: 'long', timeStyle: 'short' });
|
|
187
197
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
const crit = summary.critical ?? 0;
|
|
199
|
+
const warn = summary.warning ?? 0;
|
|
200
|
+
const info = summary.info ?? 0;
|
|
201
|
+
const total = summary.total ?? (crit + warn + info);
|
|
202
|
+
|
|
203
|
+
// Overall verdict
|
|
204
|
+
const verdict = crit > 0
|
|
205
|
+
? { cls: 'critical', icon: '✕', title: `${crit} critical issue${crit === 1 ? '' : 's'} need${crit === 1 ? 's' : ''} attention`,
|
|
206
|
+
sub: `${warn} warning${warn === 1 ? '' : 's'} · ${info} info · ${routes.length} route${routes.length === 1 ? '' : 's'} audited` }
|
|
207
|
+
: warn > 0
|
|
208
|
+
? { cls: 'warning', icon: '!', title: `${warn} warning${warn === 1 ? '' : 's'} to review`,
|
|
209
|
+
sub: `0 critical · ${info} info · ${routes.length} route${routes.length === 1 ? '' : 's'} audited` }
|
|
210
|
+
: { cls: 'ok', icon: '✓', title: 'All clear — no issues detected',
|
|
211
|
+
sub: `${routes.length} route${routes.length === 1 ? '' : 's'} audited · clean run` };
|
|
212
|
+
|
|
213
|
+
const cards = [
|
|
214
|
+
{ k: 'total', n: total, label: 'Total' },
|
|
215
|
+
{ k: 'critical', n: crit, label: 'Critical' },
|
|
216
|
+
{ k: 'warning', n: warn, label: 'Warning' },
|
|
217
|
+
{ k: 'info', n: info, label: 'Info' },
|
|
218
|
+
].map(c => `
|
|
219
|
+
<div class="card ${c.k}">
|
|
220
|
+
<div class="num">${c.n}</div>
|
|
221
|
+
<div class="lbl">${c.label}</div>
|
|
222
|
+
</div>`).join('');
|
|
203
223
|
|
|
204
224
|
const routeSections = routes.map(renderRoute).join('');
|
|
205
225
|
|
|
206
226
|
const flowSection = flows.length > 0 ? `
|
|
207
|
-
<h2
|
|
227
|
+
<h2 class="section">User Flows <span class="count">${flows.length}</span></h2>
|
|
208
228
|
${flows.map(renderFlow).join('')}` : '';
|
|
209
229
|
|
|
210
230
|
return `<!DOCTYPE html>
|
|
@@ -213,28 +233,199 @@ function buildHtml(report) {
|
|
|
213
233
|
<meta charset="UTF-8">
|
|
214
234
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
215
235
|
<title>Argus Report — ${esc(runDate)}</title>
|
|
236
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
237
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
238
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
|
216
239
|
<style>
|
|
240
|
+
:root {
|
|
241
|
+
--accent:#5E0ED7; --accent-soft:#9b6cf2; --ink:#0d0d0d;
|
|
242
|
+
--bg:#f6f4fc; --card:#ffffff; --border:#ece8f7; --border-strong:#ddd5f1;
|
|
243
|
+
--text:#1b1726; --muted:#6c6580;
|
|
244
|
+
--crit:#dc2626; --crit-bg:#fef2f3; --crit-bd:#f4cdcd;
|
|
245
|
+
--warn:#d97706; --warn-bg:#fffaeb; --warn-bd:#f3e0a8;
|
|
246
|
+
--info:#2563eb; --info-bg:#eef4ff; --info-bd:#cadcfb;
|
|
247
|
+
--ok:#15a34a; --ok-bg:#eefcf3; --ok-bd:#bce8cc;
|
|
248
|
+
}
|
|
217
249
|
*, *::before, *::after { box-sizing: border-box; }
|
|
218
|
-
body {
|
|
219
|
-
|
|
250
|
+
body {
|
|
251
|
+
margin: 0; color: var(--text);
|
|
252
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
253
|
+
background:
|
|
254
|
+
radial-gradient(1200px 480px at 100% -8%, rgba(94,14,215,.07), transparent 60%),
|
|
255
|
+
radial-gradient(900px 420px at -10% 0%, rgba(94,14,215,.05), transparent 55%),
|
|
256
|
+
var(--bg);
|
|
257
|
+
-webkit-font-smoothing: antialiased;
|
|
258
|
+
}
|
|
259
|
+
a { color: inherit; text-decoration: none; }
|
|
260
|
+
code, .mono { font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
|
|
261
|
+
|
|
262
|
+
/* top accent line + branded header */
|
|
263
|
+
.accent-line { height: 4px; background: linear-gradient(90deg, var(--accent), var(--accent-soft) 55%, #cbb4f7); }
|
|
264
|
+
.topbar {
|
|
265
|
+
position: relative; overflow: hidden; background: var(--ink); color: #fff;
|
|
266
|
+
padding: 22px 32px; display: flex; align-items: center; justify-content: space-between;
|
|
267
|
+
gap: 14px; flex-wrap: wrap;
|
|
268
|
+
}
|
|
269
|
+
.topbar::before { content:''; position:absolute; inset:0;
|
|
270
|
+
background: radial-gradient(130% 200% at 0% 0%, rgba(94,14,215,.55), transparent 52%); }
|
|
271
|
+
.topbar > * { position: relative; z-index: 1; }
|
|
272
|
+
.brand { display: flex; align-items: center; gap: 13px; }
|
|
273
|
+
.brand .logo-wrap { display:grid; place-items:center; width:46px; height:46px; border-radius:13px;
|
|
274
|
+
background: rgba(94,14,215,.16); border: 1px solid rgba(155,108,242,.35); }
|
|
275
|
+
.brand .name { font-size: 21px; font-weight: 800; letter-spacing: -.4px; line-height: 1; }
|
|
276
|
+
.brand .name .dim { color: rgba(255,255,255,.5); font-weight: 600; margin-left: 2px; }
|
|
277
|
+
.brand .tagline { font-size: 11.5px; color: rgba(255,255,255,.45); margin-top: 4px; letter-spacing: .2px; }
|
|
278
|
+
.meta { text-align: right; font-size: 12.5px; color: rgba(255,255,255,.72); line-height: 1.7; }
|
|
279
|
+
.meta .meta-url { color: var(--accent-soft); }
|
|
280
|
+
|
|
281
|
+
.wrap { max-width: 1080px; margin: 0 auto; padding: 30px 24px 56px; }
|
|
282
|
+
|
|
283
|
+
/* verdict banner */
|
|
284
|
+
.verdict { display: flex; align-items: center; gap: 16px; padding: 18px 22px;
|
|
285
|
+
border: 1px solid var(--border); border-radius: 18px; margin-bottom: 24px; background: var(--card);
|
|
286
|
+
box-shadow: 0 1px 2px rgba(13,13,13,.04), 0 20px 40px -28px rgba(94,14,215,.28); }
|
|
287
|
+
.verdict .v-icon { flex: 0 0 auto; width: 46px; height: 46px; border-radius: 14px; display: grid;
|
|
288
|
+
place-items: center; font-size: 22px; font-weight: 800; color: #fff; }
|
|
289
|
+
.verdict.ok { border-color: var(--ok-bd); }
|
|
290
|
+
.verdict.warning { border-color: var(--warn-bd); }
|
|
291
|
+
.verdict.critical { border-color: var(--crit-bd); }
|
|
292
|
+
.verdict.ok .v-icon { background: var(--ok); }
|
|
293
|
+
.verdict.warning .v-icon { background: var(--warn); }
|
|
294
|
+
.verdict.critical .v-icon { background: var(--crit); }
|
|
295
|
+
.verdict .v-title { font-weight: 700; font-size: 17px; letter-spacing: -.2px; }
|
|
296
|
+
.verdict .v-sub { color: var(--muted); font-size: 13px; margin-top: 3px; }
|
|
297
|
+
|
|
298
|
+
/* summary cards */
|
|
299
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
300
|
+
gap: 16px; margin-bottom: 34px; }
|
|
301
|
+
.card { position: relative; overflow: hidden; background: var(--card); border: 1px solid var(--border);
|
|
302
|
+
border-radius: 18px; padding: 22px 20px 20px; box-shadow: 0 1px 2px rgba(13,13,13,.04); }
|
|
303
|
+
.card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; }
|
|
304
|
+
.card .num { font-size: 42px; font-weight: 800; letter-spacing: -1.5px; line-height: 1; }
|
|
305
|
+
.card .lbl { margin-top: 8px; font-size: 11.5px; font-weight: 600; text-transform: uppercase;
|
|
306
|
+
letter-spacing: 1px; color: var(--muted); }
|
|
307
|
+
.card.total::before { background: linear-gradient(90deg, var(--accent), var(--accent-soft)); }
|
|
308
|
+
.card.total .num { color: var(--accent); }
|
|
309
|
+
.card.critical::before { background: var(--crit); }
|
|
310
|
+
.card.critical .num { color: var(--crit); }
|
|
311
|
+
.card.warning::before { background: var(--warn); }
|
|
312
|
+
.card.warning .num { color: var(--warn); }
|
|
313
|
+
.card.info::before { background: var(--info); }
|
|
314
|
+
.card.info .num { color: var(--info); }
|
|
315
|
+
|
|
316
|
+
/* section heading */
|
|
317
|
+
.section { display: flex; align-items: center; gap: 10px; font-size: 16px; font-weight: 700;
|
|
318
|
+
letter-spacing: -.2px; margin: 0 0 18px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
|
|
319
|
+
.section::before { content: ''; width: 4px; height: 18px; border-radius: 3px;
|
|
320
|
+
background: linear-gradient(180deg, var(--accent), var(--accent-soft)); }
|
|
321
|
+
.section .count { font-size: 12px; font-weight: 700; color: var(--accent);
|
|
322
|
+
background: rgba(94,14,215,.09); border: 1px solid rgba(94,14,215,.16);
|
|
323
|
+
padding: 2px 9px; border-radius: 999px; }
|
|
324
|
+
|
|
325
|
+
/* route card */
|
|
326
|
+
.route { background: var(--card); border: 1px solid var(--border); border-radius: 18px;
|
|
327
|
+
overflow: hidden; margin-bottom: 18px; box-shadow: 0 1px 2px rgba(13,13,13,.04), 0 16px 36px -30px rgba(94,14,215,.35); }
|
|
328
|
+
.route-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
|
|
329
|
+
flex-wrap: wrap; padding: 16px 22px; border-bottom: 1px solid var(--border); }
|
|
330
|
+
.route.ok .route-head { background: linear-gradient(180deg, var(--ok-bg), transparent); }
|
|
331
|
+
.route.warning .route-head { background: linear-gradient(180deg, var(--warn-bg), transparent); }
|
|
332
|
+
.route.critical .route-head { background: linear-gradient(180deg, var(--crit-bg), transparent); }
|
|
333
|
+
.route-id { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
|
334
|
+
.dot { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; margin-top: 6px;
|
|
335
|
+
box-shadow: 0 0 0 4px rgba(0,0,0,.04); }
|
|
336
|
+
.dot.ok { background: var(--ok); } .dot.warning { background: var(--warn); } .dot.critical { background: var(--crit); }
|
|
337
|
+
.route-name { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -.2px;
|
|
338
|
+
font-family: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace; }
|
|
339
|
+
.route-url { display: inline-block; margin-top: 3px; font-size: 12px; color: var(--muted); word-break: break-all; }
|
|
340
|
+
.route-url:hover { color: var(--accent); }
|
|
341
|
+
.pills { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
342
|
+
.route-body { padding: 18px 22px; }
|
|
343
|
+
.route-block + .route-block { margin-top: 18px; }
|
|
344
|
+
.block-title { margin: 0 0 10px; font-size: 11.5px; font-weight: 700; text-transform: uppercase;
|
|
345
|
+
letter-spacing: 1px; color: var(--muted); }
|
|
346
|
+
|
|
347
|
+
/* pills + chips */
|
|
348
|
+
.pill { font-size: 12px; font-weight: 600; padding: 3px 11px; border-radius: 999px; white-space: nowrap; }
|
|
349
|
+
.pill.critical { background: var(--crit-bg); color: var(--crit); border: 1px solid var(--crit-bd); }
|
|
350
|
+
.pill.warning { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-bd); }
|
|
351
|
+
.pill.info { background: var(--info-bg); color: var(--info); border: 1px solid var(--info-bd); }
|
|
352
|
+
.pill.ok { background: var(--ok-bg); color: var(--ok); border: 1px solid var(--ok-bd); }
|
|
353
|
+
|
|
354
|
+
/* findings */
|
|
355
|
+
.finding { border: 1px solid var(--border); border-left-width: 4px; border-radius: 12px;
|
|
356
|
+
padding: 12px 15px; margin: 9px 0; font-size: 13.5px; line-height: 1.55; }
|
|
357
|
+
.finding.critical { background: var(--crit-bg); border-left-color: var(--crit); }
|
|
358
|
+
.finding.warning { background: var(--warn-bg); border-left-color: var(--warn); }
|
|
359
|
+
.finding.info { background: var(--info-bg); border-left-color: var(--info); }
|
|
360
|
+
.finding-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
|
|
361
|
+
.chip { font-size: 10.5px; font-weight: 700; letter-spacing: .6px; color: #fff;
|
|
362
|
+
padding: 3px 8px; border-radius: 6px; white-space: nowrap; }
|
|
363
|
+
.chip.critical { background: var(--crit); } .chip.warning { background: var(--warn); } .chip.info { background: var(--info); }
|
|
364
|
+
.type { background: rgba(13,13,13,.06); border: 1px solid rgba(13,13,13,.05); border-radius: 6px;
|
|
365
|
+
padding: 2px 7px; font-size: 12px; color: #2a2535; white-space: nowrap; }
|
|
366
|
+
.tag { font-size: 11px; font-weight: 600; }
|
|
367
|
+
.tag-flaky { color: var(--muted); } .tag-new { color: var(--ok); }
|
|
368
|
+
.finding-msg { margin-top: 7px; color: var(--text); word-break: break-word; }
|
|
369
|
+
.finding-url { margin-top: 4px; font-size: 11.5px; color: var(--muted); word-break: break-all; }
|
|
370
|
+
.all-clear { margin: 6px 0; font-size: 13.5px; color: var(--ok); font-weight: 500; }
|
|
371
|
+
|
|
372
|
+
/* screenshots */
|
|
373
|
+
.shot { max-width: 100%; border-radius: 12px; border: 1px solid var(--border-strong);
|
|
374
|
+
box-shadow: 0 8px 24px -18px rgba(13,13,13,.4); display: block; }
|
|
375
|
+
.shot-missing { color: var(--muted); font-size: 13px; }
|
|
376
|
+
.vp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
|
377
|
+
.vp { margin: 0; text-align: center; }
|
|
378
|
+
.vp figcaption { font-size: 11px; color: var(--muted); margin-bottom: 5px; font-weight: 600; }
|
|
379
|
+
|
|
380
|
+
/* footer */
|
|
381
|
+
.foot { display: flex; align-items: center; justify-content: center; gap: 9px; margin-top: 40px;
|
|
382
|
+
color: var(--muted); font-size: 12.5px; }
|
|
383
|
+
.foot a { color: var(--accent); font-weight: 600; }
|
|
384
|
+
.foot .logo { opacity: .85; }
|
|
385
|
+
|
|
386
|
+
@media (max-width: 560px) {
|
|
387
|
+
.topbar { padding: 18px 20px; } .wrap { padding: 22px 16px 44px; }
|
|
388
|
+
.meta { text-align: left; }
|
|
389
|
+
}
|
|
220
390
|
</style>
|
|
221
391
|
</head>
|
|
222
392
|
<body>
|
|
223
|
-
|
|
224
|
-
<
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
393
|
+
<div class="accent-line"></div>
|
|
394
|
+
<header class="topbar">
|
|
395
|
+
<div class="brand">
|
|
396
|
+
<span class="logo-wrap">${logoSvg(26)}</span>
|
|
397
|
+
<div>
|
|
398
|
+
<div class="name">Argus<span class="dim">Report</span></div>
|
|
399
|
+
<div class="tagline">Automated QA · Chrome DevTools Protocol</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div class="meta">
|
|
403
|
+
<div>${esc(runDate)}</div>
|
|
404
|
+
<div class="meta-url">${esc(baseUrl)}</div>
|
|
405
|
+
</div>
|
|
406
|
+
</header>
|
|
228
407
|
|
|
229
|
-
<div
|
|
230
|
-
${
|
|
408
|
+
<div class="wrap">
|
|
409
|
+
<div class="verdict ${verdict.cls}">
|
|
410
|
+
<div class="v-icon">${verdict.icon}</div>
|
|
411
|
+
<div>
|
|
412
|
+
<div class="v-title">${esc(verdict.title)}</div>
|
|
413
|
+
<div class="v-sub">${esc(verdict.sub)}</div>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
231
416
|
|
|
232
|
-
<
|
|
417
|
+
<div class="cards">${cards}
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<h2 class="section">Routes <span class="count">${routes.length}</span></h2>
|
|
233
421
|
${routeSections}
|
|
234
422
|
|
|
235
423
|
${flowSection}
|
|
236
424
|
|
|
237
|
-
<
|
|
425
|
+
<div class="foot">
|
|
426
|
+
${logoSvg(18, ACCENT, ACCENT)}
|
|
427
|
+
<span>Generated by <strong>Argus</strong> · <a href="https://argus-qa.com">argus-qa.com</a> · ${esc(runDate)}</span>
|
|
428
|
+
</div>
|
|
238
429
|
</div>
|
|
239
430
|
</body>
|
|
240
431
|
</html>`;
|