@post-print/agent-test 1.1.0 → 1.1.2
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 +2 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +34 -6
- package/dist/cli.js.map +1 -1
- package/dist/compare-scenario.d.ts +10 -0
- package/dist/compare-scenario.d.ts.map +1 -1
- package/dist/compare-scenario.js +48 -3
- package/dist/compare-scenario.js.map +1 -1
- package/dist/expect.d.ts +1 -0
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +24 -0
- package/dist/expect.js.map +1 -1
- package/dist/html-report.d.ts +4 -1
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +224 -143
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/live-isolation.d.ts +2 -0
- package/dist/live-isolation.d.ts.map +1 -1
- package/dist/live-isolation.js +8 -2
- package/dist/live-isolation.js.map +1 -1
- package/dist/report-preview.d.ts +24 -0
- package/dist/report-preview.d.ts.map +1 -1
- package/dist/report-preview.js +46 -2
- package/dist/report-preview.js.map +1 -1
- package/dist/run-suite.d.ts +10 -0
- package/dist/run-suite.d.ts.map +1 -1
- package/dist/run-suite.js +146 -126
- package/dist/run-suite.js.map +1 -1
- package/dist/scenario-finalizer.d.ts +27 -0
- package/dist/scenario-finalizer.d.ts.map +1 -0
- package/dist/scenario-finalizer.js +39 -0
- package/dist/scenario-finalizer.js.map +1 -0
- package/dist/scenario-story.d.ts.map +1 -1
- package/dist/scenario-story.js +14 -8
- package/dist/scenario-story.js.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-suite.d.ts.map +1 -1
- package/dist/validate-suite.js +1 -0
- package/dist/validate-suite.js.map +1 -1
- package/dist/viewer/catalog.d.ts +14 -3
- package/dist/viewer/catalog.d.ts.map +1 -1
- package/dist/viewer/catalog.js +33 -6
- package/dist/viewer/catalog.js.map +1 -1
- package/dist/viewer/compare-metrics.d.ts.map +1 -1
- package/dist/viewer/compare-metrics.js +8 -9
- package/dist/viewer/compare-metrics.js.map +1 -1
- package/dist/viewer/events.d.ts +22 -1
- package/dist/viewer/events.d.ts.map +1 -1
- package/dist/viewer/events.js +1 -0
- package/dist/viewer/events.js.map +1 -1
- package/dist/viewer/live-runner.d.ts +7 -0
- package/dist/viewer/live-runner.d.ts.map +1 -1
- package/dist/viewer/live-runner.js +36 -23
- package/dist/viewer/live-runner.js.map +1 -1
- package/dist/viewer/page.d.ts +14 -1
- package/dist/viewer/page.d.ts.map +1 -1
- package/dist/viewer/page.js +758 -141
- package/dist/viewer/page.js.map +1 -1
- package/dist/viewer/run-controller.d.ts +13 -6
- package/dist/viewer/run-controller.d.ts.map +1 -1
- package/dist/viewer/run-controller.js +246 -74
- package/dist/viewer/run-controller.js.map +1 -1
- package/dist/viewer/server.d.ts +5 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +72 -17
- package/dist/viewer/server.js.map +1 -1
- package/dist/viewer/session-server.d.ts +2 -0
- package/dist/viewer/session-server.d.ts.map +1 -0
- package/dist/viewer/session-server.js +71 -0
- package/dist/viewer/session-server.js.map +1 -0
- package/package.json +2 -2
package/dist/viewer/page.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describeCompareGate } from "../compare-scenario.js";
|
|
2
2
|
function escapeHtml(value) {
|
|
3
3
|
return value
|
|
4
4
|
.replaceAll("&", "&")
|
|
@@ -25,6 +25,7 @@ function viewerCss() {
|
|
|
25
25
|
margin-bottom: 1.25rem;
|
|
26
26
|
}
|
|
27
27
|
.toolbar-block { display: grid; gap: 0.35rem; }
|
|
28
|
+
.run-history { min-width: min(26rem, 80vw); }
|
|
28
29
|
.toolbar-label { color: var(--muted); font-size: 0.75rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
29
30
|
.host-toggles, .toolbar-actions { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center; }
|
|
30
31
|
.host-toggles label { display: inline-flex; gap: 0.35rem; align-items: center; font-size: 0.85rem; }
|
|
@@ -72,6 +73,8 @@ function viewerCss() {
|
|
|
72
73
|
.host-tab[data-status="passed"] { color: var(--pass); }
|
|
73
74
|
.host-tab[data-status="failed"] { color: var(--fail); }
|
|
74
75
|
.host-tab[data-status="skipped"], .host-tab[data-status="skip"] { color: var(--muted); }
|
|
76
|
+
.host-tab[hidden], .host-empty-selection[hidden] { display: none; }
|
|
77
|
+
.host-empty-selection { color: var(--muted); font-size: .78rem; }
|
|
75
78
|
.host-status { display: inline-flex; align-items: center; }
|
|
76
79
|
.cell-status { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; }
|
|
77
80
|
.scenario-live { display: grid; gap: 0.75rem; }
|
|
@@ -268,52 +271,318 @@ function viewerCss() {
|
|
|
268
271
|
@media (prefers-reduced-motion: reduce) {
|
|
269
272
|
.chat-progress span, .bubble.is-streaming .bubble-text::after { animation: none; }
|
|
270
273
|
}
|
|
274
|
+
.scenario-card {
|
|
275
|
+
background: linear-gradient(135deg, color-mix(in oklch, var(--panel) 94%, white), var(--panel));
|
|
276
|
+
padding: 1rem 1.05rem;
|
|
277
|
+
}
|
|
278
|
+
.scenario-detail { gap: 0.4rem; }
|
|
279
|
+
.scenario-detail strong { font-size: 1.05rem; letter-spacing: -0.01em; }
|
|
280
|
+
.scenario-toolbar { gap: 0.45rem 0.85rem; padding-top: 0.6rem; border-top: 1px solid var(--border); }
|
|
281
|
+
.host-runs { display: flex; gap: 0.4rem; align-items: center; }
|
|
282
|
+
.host-runs-label { color: var(--muted); font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
283
|
+
.host-tablist { gap: 0.25rem; }
|
|
284
|
+
.host-tab { gap: 0.28rem; padding: 0.25rem 0.45rem; font-size: 0.82rem; }
|
|
285
|
+
body { background: oklch(0.155 0.015 250); }
|
|
286
|
+
.viewer-shell { max-width: none; min-height: 100dvh; padding: 0; }
|
|
287
|
+
.viewer-topbar {
|
|
288
|
+
position: sticky; inset-block-start: 0; z-index: 10;
|
|
289
|
+
display: grid; grid-template-columns: minmax(12rem, .7fr) minmax(18rem, 1.2fr) minmax(14rem, 1fr);
|
|
290
|
+
align-items: center; gap: 1rem; min-height: 4.5rem;
|
|
291
|
+
padding: .75rem clamp(1rem, 2.5vw, 2rem);
|
|
292
|
+
background: color-mix(in oklch, var(--bg) 92%, transparent);
|
|
293
|
+
border-block-end: 1px solid var(--border); backdrop-filter: blur(18px);
|
|
294
|
+
}
|
|
295
|
+
.viewer-identity { display: flex; align-items: baseline; gap: .65rem; min-width: 0; }
|
|
296
|
+
.viewer-identity .brand { color: var(--pass); }
|
|
297
|
+
.viewer-identity h1 { font-size: 1.05rem; white-space: nowrap; }
|
|
298
|
+
.run-context { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: .5rem; min-width: 0; }
|
|
299
|
+
.run-history { width: 100%; min-width: 0; border-radius: 7px; background: var(--panel-2); color: var(--text); border: 1px solid var(--border); padding: .42rem .6rem; }
|
|
300
|
+
.run-banner { margin: 0; overflow: hidden; color: var(--muted); font-size: .78rem; text-align: end; text-overflow: ellipsis; white-space: nowrap; }
|
|
301
|
+
.viewer-command-row { display: flex; flex-wrap: wrap; align-items: center; gap: .55rem 1rem; padding: .55rem clamp(1rem, 2.5vw, 2rem); background: var(--panel-2); border-block-end: 1px solid var(--border); }
|
|
302
|
+
.viewer-command-row .toolbar-block { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem .55rem; }
|
|
303
|
+
.viewer-command-row .host-toggles { gap: .25rem .55rem; }
|
|
304
|
+
.viewer-command-row .parallel-control { display: inline-flex; align-items: center; gap: .35rem; color: var(--muted); font-size: .78rem; white-space: nowrap; }
|
|
305
|
+
.viewer-command-row .run-toggle { min-width: 5.75rem; margin-inline-start: auto; padding-block: .3rem; font-size: .78rem; }
|
|
306
|
+
.run-progress { margin: 0; padding: .7rem clamp(1rem, 2.5vw, 2rem); border: 0; border-block-end: 1px solid var(--border); border-radius: 0; background: var(--bg); }
|
|
307
|
+
.run-progress-track { height: .35rem; }
|
|
308
|
+
.viewer-workspace { display: grid; grid-template-columns: minmax(15rem, 19rem) minmax(0, 1fr); min-height: calc(100dvh - 7.5rem); }
|
|
309
|
+
.test-navigator { position: sticky; inset-block-start: 7.5rem; align-self: start; height: calc(100dvh - 7.5rem); overflow: auto; padding: 1.15rem .75rem 2rem; background: color-mix(in oklch, var(--panel-2) 72%, var(--bg)); border-inline-end: 1px solid var(--border); }
|
|
310
|
+
.test-navigator-heading { display: flex; justify-content: space-between; align-items: end; padding-inline: .5rem; margin-block-end: 1rem; }
|
|
311
|
+
.section-label, .scenario-path { margin: 0; color: var(--muted); font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
|
|
312
|
+
.test-navigator-heading h2 { margin: .1rem 0 0; font-size: 1rem; }
|
|
313
|
+
.test-picker, .test-picker-label { display: none; }
|
|
314
|
+
.test-tree { display: grid; gap: 1.1rem; }
|
|
315
|
+
.test-nav-suite { display: grid; gap: .35rem; }
|
|
316
|
+
.test-nav-suite > header { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding-inline: .45rem; }
|
|
317
|
+
.test-nav-suite > header h2 { margin: 0; color: var(--muted); font-size: .72rem; letter-spacing: .05em; text-transform: uppercase; }
|
|
318
|
+
.test-nav-suite > header p { display: none; }
|
|
319
|
+
.test-nav-suite > header .run-suite { padding: .12rem .35rem; color: var(--muted); border: 0; background: transparent; font-size: .7rem; }
|
|
320
|
+
.test-nav-items { display: grid; gap: .18rem; }
|
|
321
|
+
.test-nav-item { display: grid; grid-template-columns: .6rem minmax(0, 1fr); align-items: start; gap: .55rem; width: 100%; padding: .5rem .55rem; border-color: transparent; background: transparent; text-align: start; }
|
|
322
|
+
.test-nav-item:hover { background: color-mix(in oklch, var(--text) 5%, transparent); }
|
|
323
|
+
.test-nav-item[aria-current="true"] { background: color-mix(in oklch, var(--pass) 9%, var(--panel)); border-color: color-mix(in oklch, var(--pass) 28%, var(--border)); }
|
|
324
|
+
.test-nav-status { width: .48rem; height: .48rem; margin-block-start: .28rem; border: 1px solid var(--muted); border-radius: 50%; }
|
|
325
|
+
.test-nav-item[data-status="running"] .test-nav-status { border-color: var(--skip); background: var(--skip); box-shadow: 0 0 0 3px color-mix(in oklch, var(--skip) 14%, transparent); }
|
|
326
|
+
.test-nav-item[data-status="passed"] .test-nav-status { border-color: var(--pass); background: var(--pass); }
|
|
327
|
+
.test-nav-item[data-status="failed"] .test-nav-status { border-color: var(--fail); background: var(--fail); }
|
|
328
|
+
.test-nav-item[data-status="skipped"] .test-nav-status { border-color: var(--skip); }
|
|
329
|
+
.test-nav-copy { display: grid; min-width: 0; }
|
|
330
|
+
.test-nav-copy strong { overflow: hidden; font-size: .82rem; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
|
331
|
+
.test-nav-copy small { overflow: hidden; color: var(--muted); font-size: .7rem; text-overflow: ellipsis; white-space: nowrap; }
|
|
332
|
+
.test-stage { min-width: 0; padding: clamp(1rem, 3vw, 2.25rem); }
|
|
333
|
+
.test-stage > .suite { display: block; margin: 0; }
|
|
334
|
+
.test-stage > .suite > .suite-header { display: none; }
|
|
335
|
+
.test-stage .scenario-list { display: contents; }
|
|
336
|
+
.scenario-card, .scenario-card[data-selected="true"] { max-width: 76rem; margin-inline: auto; padding: 0; gap: 0; overflow: hidden; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 12px 36px oklch(0 0 0 / .16); }
|
|
337
|
+
.scenario-card[hidden] { display: none; }
|
|
338
|
+
.scenario-focus-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: clamp(1rem, 2vw, 1.5rem); border-block-end: 1px solid var(--border); }
|
|
339
|
+
.scenario-heading-copy { display: grid; gap: .3rem; min-width: 0; }
|
|
340
|
+
.scenario-heading-copy h2 { margin: 0; font-size: clamp(1.25rem, 2.5vw, 1.75rem); letter-spacing: -.025em; }
|
|
341
|
+
.scenario-heading-copy .suite-description { max-width: 62ch; color: var(--muted); font-size: .9rem; }
|
|
342
|
+
.scenario-heading-copy .scenario-lede { max-width: 62ch; font-size: .9rem; }
|
|
343
|
+
.focus-verdict { display: inline-flex; align-items: center; gap: .4rem; flex: none; padding: .28rem .55rem; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; font-size: .72rem; font-weight: 700; text-transform: uppercase; }
|
|
344
|
+
.focus-verdict > span { width: .45rem; height: .45rem; border-radius: 50%; background: currentColor; }
|
|
345
|
+
.focus-verdict[data-status="running"] { color: var(--skip); }
|
|
346
|
+
.focus-verdict[data-status="passed"] { color: var(--pass); }
|
|
347
|
+
.focus-verdict[data-status="failed"] { color: var(--fail); }
|
|
348
|
+
.scenario-definition { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(15rem, .7fr); gap: .8rem; padding: clamp(1rem, 2vw, 1.5rem); background: color-mix(in oklch, var(--panel) 88%, var(--bg)); }
|
|
349
|
+
.test-intent, .criteria-panel, .compare-definition, .comparison-criteria { min-width: 0; padding: .9rem; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; }
|
|
350
|
+
.prompt-preview { margin: .45rem 0 0; color: var(--text); font-size: .94rem; line-height: 1.55; }
|
|
351
|
+
.task-metadata { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .55rem; margin-block-start: .75rem; padding-block-start: .65rem; border-block-start: 1px solid var(--border); }
|
|
352
|
+
.task-metadata + .task-metadata { margin-block-start: .55rem; padding-block-start: 0; border-block-start: 0; }
|
|
353
|
+
.task-metadata .task-meta-label { color: var(--muted); font-size: .66rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
|
|
354
|
+
.task-metadata p { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.6; }
|
|
355
|
+
.task-metadata code { padding: .08rem .28rem; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; overflow-wrap: anywhere; }
|
|
356
|
+
.criteria-panel > summary { display: flex; justify-content: space-between; gap: .5rem; cursor: pointer; font-size: .82rem; font-weight: 650; }
|
|
357
|
+
.criteria-summary { color: var(--muted); font-size: .72rem; font-weight: 400; }
|
|
358
|
+
.criterion-groups { display: grid; gap: .65rem; margin-block-start: .75rem; }
|
|
359
|
+
.criterion-group { display: grid; gap: .35rem; padding-block-start: .6rem; border-block-start: 1px solid var(--border); }
|
|
360
|
+
.criterion-group:first-child { padding-block-start: 0; border-block-start: 0; }
|
|
361
|
+
.criterion-group h4 { margin: 0; color: var(--muted); font-size: .66rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
|
|
362
|
+
.criterion-group p { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.4; }
|
|
363
|
+
.criterion-list { display: grid; gap: .28rem; margin: 0; padding: 0; list-style: none; }
|
|
364
|
+
.criterion-list li { color: var(--text); font-size: .78rem; line-height: 1.4; }
|
|
365
|
+
.criterion-list code { padding: .08rem .28rem; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; overflow-wrap: anywhere; }
|
|
366
|
+
.criteria-empty { margin: .7rem 0 0; color: var(--muted); font-size: .78rem; line-height: 1.45; }
|
|
367
|
+
.compare-definition { grid-column: 1 / -1; }
|
|
368
|
+
.comparison-intro { margin: .35rem 0 0; color: var(--muted); font-size: .78rem; }
|
|
369
|
+
.compare-definition { padding: 0; background: transparent; border: 0; }
|
|
370
|
+
.compare-definition-tabs { display: flex; flex-wrap: wrap; gap: .35rem; margin-block-start: .55rem; }
|
|
371
|
+
.compare-definition-tab-input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; pointer-events: none; }
|
|
372
|
+
.compare-definition-tab { padding: .35rem .65rem; color: var(--muted); background: var(--panel-2); border: 1px solid var(--border); border-radius: 7px; cursor: pointer; font-size: .78rem; }
|
|
373
|
+
.compare-definition-tab-input:checked + .compare-definition-tab { color: var(--text); background: color-mix(in oklch, var(--pass) 12%, var(--panel-2)); border-color: color-mix(in oklch, var(--pass) 36%, var(--border)); }
|
|
374
|
+
.compare-definition-tab-input:focus-visible + .compare-definition-tab { outline: 2px solid var(--pass); outline-offset: 2px; }
|
|
375
|
+
.compare-definition-grid { display: grid; grid-template-columns: minmax(0, 1fr); margin-block-start: .55rem; }
|
|
376
|
+
.compare-arm-definition { min-width: 0; overflow: hidden; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; }
|
|
377
|
+
.compare-definition-tabbed .compare-arm-definition { display: none; }
|
|
378
|
+
.compare-arm-header { display: grid; gap: .2rem; padding: .8rem .9rem; border-block-end: 1px solid var(--border); }
|
|
379
|
+
.compare-arm-header h3 { margin: 0; font-size: .95rem; }
|
|
380
|
+
.compare-arm-description { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.45; }
|
|
381
|
+
.compare-arm-definition .test-intent, .compare-arm-definition .criteria-panel { border: 0; border-radius: 0; }
|
|
382
|
+
.compare-arm-definition .test-intent { background: transparent; }
|
|
383
|
+
.compare-arm-definition .criteria-panel { border-block-start: 1px solid var(--border); background: color-mix(in oklch, var(--panel-2) 82%, var(--bg)); }
|
|
384
|
+
.scenario-toolbar { justify-content: space-between; gap: .75rem 1rem; padding: .8rem clamp(1rem, 2vw, 1.5rem); background: var(--panel); border-block: 1px solid var(--border); }
|
|
385
|
+
.host-runs-label { font-size: .65rem; }
|
|
386
|
+
.host-tab { padding: .3rem .5rem; background: transparent; }
|
|
387
|
+
.host-tab[aria-selected="true"] { color: var(--text); background: color-mix(in oklch, var(--pass) 11%, var(--panel-2)); }
|
|
388
|
+
.scenario-actions .run-toggle { min-width: 5.6rem; padding: .28rem .55rem; font-size: .78rem; }
|
|
389
|
+
.scenario-live { padding: clamp(1rem, 2vw, 1.5rem); background: var(--bg); }
|
|
390
|
+
.scenario-live .host-empty { padding: 2rem; text-align: center; border: 1px dashed var(--border); border-radius: 8px; }
|
|
391
|
+
@container (max-width: 48rem) { .scenario-definition { grid-template-columns: minmax(0, 1fr); } .compare-definition { grid-column: auto; } }
|
|
392
|
+
@media (max-width: 800px) {
|
|
393
|
+
.viewer-topbar { position: static; grid-template-columns: minmax(0, 1fr); gap: .6rem; padding-block: .85rem; }
|
|
394
|
+
.viewer-identity { justify-content: space-between; }
|
|
395
|
+
.run-context { grid-template-columns: minmax(0, 1fr); gap: .25rem; }
|
|
396
|
+
.run-banner { text-align: start; }
|
|
397
|
+
.viewer-command-row { position: sticky; inset-block-start: 0; z-index: 9; }
|
|
398
|
+
.viewer-workspace { display: block; min-height: 0; }
|
|
399
|
+
.test-navigator { position: static; height: auto; padding: .75rem 1rem; border-inline-end: 0; border-block-end: 1px solid var(--border); }
|
|
400
|
+
.test-navigator-heading, .test-tree { display: none; }
|
|
401
|
+
.test-picker-label { display: block; margin-block-end: .25rem; color: var(--muted); font-size: .68rem; font-weight: 700; text-transform: uppercase; }
|
|
402
|
+
.test-picker { display: block; width: 100%; padding: .5rem .6rem; border: 1px solid var(--border); border-radius: 7px; background: var(--panel); color: var(--text); }
|
|
403
|
+
.test-stage { padding: .8rem; }
|
|
404
|
+
.scenario-focus-header { align-items: center; }
|
|
405
|
+
.scenario-definition { grid-template-columns: minmax(0, 1fr); }
|
|
406
|
+
.compare-definition { grid-column: auto; }
|
|
407
|
+
.scenario-toolbar { align-items: flex-start; }
|
|
408
|
+
.host-runs { align-items: flex-start; flex-direction: column; }
|
|
409
|
+
}
|
|
410
|
+
@media (max-width: 480px) { .scenario-focus-header { display: grid; } .focus-verdict { justify-self: start; } .scenario-toolbar { display: grid; } .scenario-actions { width: 100%; } .scenario-actions .primary { flex: 1; } .viewer-command-row .run-toggle { width: 100%; margin-inline-start: 0; } }
|
|
271
411
|
`;
|
|
272
412
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
413
|
+
function criterionCode(value) {
|
|
414
|
+
return `<code>${escapeHtml(value)}</code>`;
|
|
415
|
+
}
|
|
416
|
+
function renderAllowedCommands(target) {
|
|
417
|
+
const commands = target.rubric.allowedCommands;
|
|
418
|
+
if (commands === undefined)
|
|
419
|
+
return "";
|
|
420
|
+
const list = commands.length
|
|
421
|
+
? commands.map(criterionCode).join('<span aria-hidden="true">, </span>')
|
|
422
|
+
: '<span class="task-command-empty">None</span>';
|
|
423
|
+
return `<div class="task-metadata task-allowed-commands"><span class="task-meta-label">Allowed commands</span><p>${list}</p></div>`;
|
|
424
|
+
}
|
|
425
|
+
function renderProvidedContext(target) {
|
|
426
|
+
if (!target.contextSources?.length)
|
|
427
|
+
return "";
|
|
428
|
+
const sources = target.contextSources
|
|
429
|
+
.map(criterionCode)
|
|
430
|
+
.join('<span aria-hidden="true">, </span>');
|
|
431
|
+
return `<div class="task-metadata task-provided-context"><span class="task-meta-label">Provided context</span><p>${sources}</p></div>`;
|
|
432
|
+
}
|
|
433
|
+
function renderSuppliedResources(target, showEmpty = false) {
|
|
434
|
+
const servers = target.suppliedMcp ?? [];
|
|
435
|
+
const tools = [...new Set(servers.flatMap((server) => server.tools))];
|
|
436
|
+
const skills = target.suppliedSkills ?? [];
|
|
437
|
+
const value = (items) => items.length
|
|
438
|
+
? items.map(criterionCode).join('<span aria-hidden="true">, </span>')
|
|
439
|
+
: '<span class="task-resource-empty">None</span>';
|
|
440
|
+
if (!showEmpty && servers.length === 0 && skills.length === 0)
|
|
441
|
+
return "";
|
|
442
|
+
return `<div class="task-resources">
|
|
443
|
+
<div class="task-metadata"><span class="task-meta-label">Supplied MCP servers</span><p>${value(servers.map((server) => server.name))}</p></div>
|
|
444
|
+
<div class="task-metadata"><span class="task-meta-label">Supplied MCP tools</span><p>${value(tools)}</p></div>
|
|
445
|
+
<div class="task-metadata"><span class="task-meta-label">Supplied skills</span><p>${value(skills)}</p></div>
|
|
446
|
+
</div>`;
|
|
447
|
+
}
|
|
448
|
+
function renderPassCriteria(target) {
|
|
449
|
+
const groups = [];
|
|
450
|
+
const rubric = target.rubric;
|
|
451
|
+
const reply = [];
|
|
452
|
+
for (const text of rubric.must ?? []) {
|
|
453
|
+
reply.push(`Final reply must include ${criterionCode(text)}.`);
|
|
454
|
+
}
|
|
455
|
+
for (const text of rubric.mustNot ?? []) {
|
|
456
|
+
reply.push(`The run must not contain ${criterionCode(text)}.`);
|
|
457
|
+
}
|
|
458
|
+
if (reply.length)
|
|
459
|
+
groups.push({ title: "Reply", items: reply });
|
|
460
|
+
if (rubric.mustRun?.length || rubric.mustRunSuccessfully?.length) {
|
|
461
|
+
groups.push({
|
|
462
|
+
title: "Required commands",
|
|
463
|
+
items: [
|
|
464
|
+
...(rubric.mustRun ?? []).map((command) => `Must run a command matching ${criterionCode(command)}.`),
|
|
465
|
+
...(rubric.mustRunSuccessfully ?? []).map((command) => `Must run a command matching ${criterionCode(command)} successfully.`),
|
|
466
|
+
],
|
|
467
|
+
});
|
|
300
468
|
}
|
|
301
|
-
|
|
302
|
-
|
|
469
|
+
const tools = [
|
|
470
|
+
...(rubric.mustCallTool ?? []).map((tool) => `Must call ${criterionCode(tool)}.`),
|
|
471
|
+
...(rubric.mustCallToolsInOrder ?? []).map((tool, index) => `${index + 1}. Call ${criterionCode(tool)}.`),
|
|
472
|
+
...(rubric.mustNotCallTool ?? []).map((tool) => `Must not call ${criterionCode(tool)}.`),
|
|
473
|
+
];
|
|
474
|
+
if (tools.length) {
|
|
475
|
+
groups.push({
|
|
476
|
+
title: "Tools",
|
|
477
|
+
intro: rubric.mustCallToolsInOrder?.length ? "Ordered tool calls are numbered." : undefined,
|
|
478
|
+
items: tools,
|
|
479
|
+
});
|
|
303
480
|
}
|
|
304
|
-
|
|
305
|
-
|
|
481
|
+
const files = [
|
|
482
|
+
...(rubric.mustReadPath ?? []).map((path) => `Must read a path matching ${criterionCode(path)}.`),
|
|
483
|
+
...(rubric.mustNotReadPath ?? []).map((path) => `Must not read a path matching ${criterionCode(path)}.`),
|
|
484
|
+
];
|
|
485
|
+
if (files.length)
|
|
486
|
+
groups.push({ title: "Files", items: files });
|
|
487
|
+
const skills = [
|
|
488
|
+
...(rubric.mustInvokeSkill ?? []).map((skill) => `Must use ${criterionCode(skill)}.`),
|
|
489
|
+
...(rubric.mustNotInvokeSkill ?? []).map((skill) => `Must not use ${criterionCode(skill)}.`),
|
|
490
|
+
];
|
|
491
|
+
if (skills.length)
|
|
492
|
+
groups.push({ title: "Skills", items: skills });
|
|
493
|
+
const routing = [
|
|
494
|
+
...(rubric.handsOnRouting
|
|
495
|
+
? ["Must announce hands-on routing before the first tool call."]
|
|
496
|
+
: []),
|
|
497
|
+
...(rubric.tier ? [`Must announce the ${criterionCode(rubric.tier)} tier.`] : []),
|
|
498
|
+
...(rubric.reviewDepth
|
|
499
|
+
? [`Must announce ${criterionCode(rubric.reviewDepth)} review depth.`]
|
|
500
|
+
: []),
|
|
501
|
+
...(rubric.routingBlock ? ["Must announce a routing block."] : []),
|
|
502
|
+
];
|
|
503
|
+
if (routing.length)
|
|
504
|
+
groups.push({ title: "Routing", items: routing });
|
|
505
|
+
if (rubric.judge?.length) {
|
|
506
|
+
groups.push({
|
|
507
|
+
title: "Judge",
|
|
508
|
+
intro: "A judge evaluates these questions after the run.",
|
|
509
|
+
items: rubric.judge.map((item) => escapeHtml(typeof item === "string" ? item : item.question)),
|
|
510
|
+
});
|
|
306
511
|
}
|
|
307
|
-
|
|
512
|
+
const count = groups.reduce((total, group) => total + Math.max(group.items.length, 1), 0);
|
|
513
|
+
const html = groups
|
|
514
|
+
.map((group) => `<section class="criterion-group"><h4>${escapeHtml(group.title)}</h4>${group.intro ? `<p>${escapeHtml(group.intro)}</p>` : ""}${group.items.length ? `<ul class="criterion-list">${group.items.map((item) => `<li>${item}</li>`).join("")}</ul>` : ""}</section>`)
|
|
515
|
+
.join("");
|
|
516
|
+
return { count, html };
|
|
517
|
+
}
|
|
518
|
+
function armLabel(scenario, id) {
|
|
519
|
+
return scenario.compare?.find((arm) => arm.id === id)?.label ?? id;
|
|
520
|
+
}
|
|
521
|
+
function renderComparisonCriteria(scenario) {
|
|
522
|
+
const items = scenario.gates?.length
|
|
523
|
+
? scenario.gates.map((gate) => `${escapeHtml(describeCompareGate(gate, (id) => armLabel(scenario, id)))}.`)
|
|
524
|
+
: (scenario.compare ?? []).map((arm) => `${escapeHtml(arm.label)} must pass all of its criteria.`);
|
|
525
|
+
return {
|
|
526
|
+
count: items.length,
|
|
527
|
+
html: `<section class="criterion-group"><h4>Arm results</h4><ul class="criterion-list">${items.map((item) => `<li>${item}</li>`).join("")}</ul></section>`,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function renderCompareArm(arm) {
|
|
531
|
+
const criteria = renderPassCriteria(arm);
|
|
532
|
+
return `<article class="compare-arm-definition" data-compare-arm-definition="${escapeHtml(arm.id)}">
|
|
533
|
+
<header class="compare-arm-header">
|
|
534
|
+
<p class="section-label">Arm</p>
|
|
535
|
+
<h3>${escapeHtml(arm.label)}</h3>
|
|
536
|
+
${arm.description ? `<p class="compare-arm-description">${escapeHtml(arm.description)}</p>` : ""}
|
|
537
|
+
</header>
|
|
538
|
+
<section class="test-intent">
|
|
539
|
+
<p class="section-label">Task</p>
|
|
540
|
+
<p class="prompt-preview">${escapeHtml(arm.prompt)}</p>
|
|
541
|
+
${renderAllowedCommands(arm)}
|
|
542
|
+
${renderProvidedContext(arm)}
|
|
543
|
+
${renderSuppliedResources(arm, true)}
|
|
544
|
+
</section>
|
|
545
|
+
<details class="criteria-panel" open>
|
|
546
|
+
<summary><span>Pass criteria</span><span class="criteria-summary">${criteria.count ? `${criteria.count} ${criteria.count === 1 ? "check" : "checks"}` : "Completion only"}</span></summary>
|
|
547
|
+
${criteria.html ? `<div class="criterion-groups">${criteria.html}</div>` : '<p class="criteria-empty">This arm passes when it completes without a runner error.</p>'}
|
|
548
|
+
</details>
|
|
549
|
+
</article>`;
|
|
550
|
+
}
|
|
551
|
+
function comparisonDefinitionGroupId(suite, scenario) {
|
|
552
|
+
return `cd-${suite.name}-${scenario.name}`
|
|
553
|
+
.toLowerCase()
|
|
554
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
555
|
+
.replace(/^-+|-+$/g, "");
|
|
556
|
+
}
|
|
557
|
+
function renderCompareDefinition(suite, scenario) {
|
|
558
|
+
const arms = scenario.compare ?? [];
|
|
559
|
+
const group = comparisonDefinitionGroupId(suite, scenario);
|
|
560
|
+
const tabs = arms
|
|
561
|
+
.map((arm, index) => {
|
|
562
|
+
const id = `${group}-${arm.id}`;
|
|
563
|
+
return `<input type="radio" class="compare-definition-tab-input" name="${escapeHtml(group)}" id="${escapeHtml(id)}"${index === 0 ? " checked" : ""}>
|
|
564
|
+
<label class="compare-definition-tab" role="tab" for="${escapeHtml(id)}">${escapeHtml(arm.label)}</label>`;
|
|
565
|
+
})
|
|
566
|
+
.join("");
|
|
567
|
+
const rules = arms
|
|
568
|
+
.map((arm) => `.${group}:has(#${group}-${arm.id}:checked) [data-compare-arm-definition="${arm.id}"]{display:block}`)
|
|
569
|
+
.join("");
|
|
570
|
+
return `<style>${rules}</style>
|
|
571
|
+
<section class="compare-definition compare-definition-tabbed ${escapeHtml(group)}">
|
|
572
|
+
<p class="section-label">Arms</p>
|
|
573
|
+
<div class="compare-definition-tabs" role="tablist" aria-label="Comparison arm definitions">${tabs}</div>
|
|
574
|
+
<div class="compare-definition-grid">${arms.map(renderCompareArm).join("")}</div>
|
|
575
|
+
</section>`;
|
|
308
576
|
}
|
|
309
|
-
function defaultScenarioHost(suite, scenario) {
|
|
577
|
+
function defaultScenarioHost(suite, scenario, defaultSelectedHosts) {
|
|
310
578
|
if (scenario.host && suite.hosts.includes(scenario.host)) {
|
|
311
579
|
return scenario.host;
|
|
312
580
|
}
|
|
313
|
-
|
|
314
|
-
|
|
581
|
+
const configured = suite.hosts.find((host) => defaultSelectedHosts.includes(host));
|
|
582
|
+
if (configured) {
|
|
583
|
+
return configured;
|
|
315
584
|
}
|
|
316
|
-
return suite.hosts[0] ?? "
|
|
585
|
+
return suite.hosts.length === 1 ? (suite.hosts[0] ?? "") : "";
|
|
317
586
|
}
|
|
318
587
|
function hostIsSkipped(scenario, host) {
|
|
319
588
|
return Boolean(scenario.skip || (scenario.host && scenario.host !== host));
|
|
@@ -333,67 +602,159 @@ function renderHostChrome(suite, scenario, selectedHost) {
|
|
|
333
602
|
return `<button type="button" class="host-tab" role="tab" aria-label="${escapeHtml(host)}" data-cell="${escapeHtml(`${suite.name}::${scenario.name}::${host}`)}" data-host="${escapeHtml(host)}" aria-selected="${selected ? "true" : "false"}"${skipped ? " disabled" : ""}>${escapeHtml(host)} <span class="cell-status muted" aria-hidden="true">${skipped ? "skip" : "idle"}</span></button>`;
|
|
334
603
|
})
|
|
335
604
|
.join("");
|
|
336
|
-
return `<div class="host-tablist" role="tablist">${tabs}</div>`;
|
|
605
|
+
return `<div class="host-runs"><span class="host-runs-label">Run host</span><div class="host-tablist" role="tablist">${tabs}</div><span class="host-empty-selection" hidden>No selected host can run this test.</span></div>`;
|
|
337
606
|
}
|
|
338
|
-
function renderHostPanels(suite, selectedHost) {
|
|
607
|
+
function renderHostPanels(suite, scenario, selectedHost, results, canRun) {
|
|
339
608
|
return suite.hosts
|
|
340
609
|
.map((host) => {
|
|
341
610
|
const selected = host === selectedHost;
|
|
342
|
-
|
|
611
|
+
const result = results.find((item) => item.suite === suite.name && item.scenario === scenario.name && item.host === host);
|
|
612
|
+
const hostHeading = !canRun && suite.hosts.length > 1 ? `<h3>${escapeHtml(host)}</h3>` : "";
|
|
613
|
+
return `<div class="host-panel" data-host-panel="${escapeHtml(host)}" role="tabpanel"${canRun && !selected ? " hidden" : ""}>${hostHeading}${result?.html ?? '<p class="host-empty">No run yet.</p>'}</div>`;
|
|
343
614
|
})
|
|
344
615
|
.join("");
|
|
345
616
|
}
|
|
346
|
-
function renderScenarioCard(suite, scenario) {
|
|
347
|
-
const selectedHost = defaultScenarioHost(suite, scenario);
|
|
348
|
-
const runDisabled = hostIsSkipped(scenario, selectedHost);
|
|
349
|
-
const
|
|
350
|
-
? scenario.compare
|
|
351
|
-
.map((arm) => `<p class="arm-note">${escapeHtml(arm.label)}: ${escapeHtml(arm.description ?? arm.prompt ?? "")}</p>`)
|
|
352
|
-
.join("")
|
|
353
|
-
: "";
|
|
617
|
+
function renderScenarioCard(suite, scenario, results, canRun, defaultSelectedHosts, selected) {
|
|
618
|
+
const selectedHost = defaultScenarioHost(suite, scenario, defaultSelectedHosts);
|
|
619
|
+
const runDisabled = !selectedHost || hostIsSkipped(scenario, selectedHost);
|
|
620
|
+
const hasResult = results.some((item) => item.suite === suite.name && item.scenario === scenario.name);
|
|
354
621
|
const key = escapeHtml(`${suite.name}::${scenario.name}`);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
622
|
+
const criteria = renderPassCriteria(scenario);
|
|
623
|
+
const comparisonCriteria = scenario.compare ? renderComparisonCriteria(scenario) : undefined;
|
|
624
|
+
const definition = comparisonCriteria
|
|
625
|
+
? `<section class="test-intent comparison-task">
|
|
626
|
+
<p class="section-label">Comparison task</p>
|
|
627
|
+
<p class="prompt-preview">${escapeHtml(scenario.prompt)}</p>
|
|
628
|
+
</section>
|
|
629
|
+
<section class="comparison-criteria">
|
|
630
|
+
<p class="section-label">Comparison pass criteria</p>
|
|
631
|
+
<p class="comparison-intro">Each arm runs independently. These checks decide whether the comparison passes.</p>
|
|
632
|
+
<div class="criterion-groups">${comparisonCriteria.html}</div>
|
|
633
|
+
</section>
|
|
634
|
+
${renderCompareDefinition(suite, scenario)}`
|
|
635
|
+
: `<section class="test-intent">
|
|
636
|
+
<p class="section-label">Task</p>
|
|
637
|
+
<p class="prompt-preview">${escapeHtml(scenario.prompt)}</p>
|
|
638
|
+
${renderAllowedCommands(scenario)}
|
|
639
|
+
${renderProvidedContext(scenario)}
|
|
640
|
+
${renderSuppliedResources(scenario)}
|
|
641
|
+
</section>
|
|
642
|
+
<details class="criteria-panel" open>
|
|
643
|
+
<summary><span>Pass criteria</span><span class="criteria-summary">${criteria.count ? `${criteria.count} ${criteria.count === 1 ? "check" : "checks"}` : "Completion only"}</span></summary>
|
|
644
|
+
${criteria.html ? `<div class="criterion-groups">${criteria.html}</div>` : '<p class="criteria-empty">The scenario passes when it completes without a runner error.</p>'}
|
|
645
|
+
</details>`;
|
|
646
|
+
return `<article class="scenario-card" data-scenario-card="${key}" data-selected="${selected ? "true" : "false"}"${selected ? "" : " hidden"}>
|
|
647
|
+
<header class="scenario-focus-header">
|
|
648
|
+
<div class="scenario-heading-copy">
|
|
649
|
+
<p class="scenario-path">${escapeHtml(suite.name)} <span aria-hidden="true">/</span> test</p>
|
|
650
|
+
${suite.description ? `<p class="suite-description">${escapeHtml(suite.description)}</p>` : ""}
|
|
651
|
+
<h2>${escapeHtml(scenario.name)}</h2>
|
|
652
|
+
${scenario.description ? `<p class="scenario-lede">${escapeHtml(scenario.description)}</p>` : ""}
|
|
653
|
+
</div>
|
|
654
|
+
<span class="focus-verdict" data-focus-verdict="${key}" data-status="idle"><span aria-hidden="true"></span>Not run</span>
|
|
655
|
+
</header>
|
|
656
|
+
<div class="scenario-definition">
|
|
657
|
+
${definition}
|
|
362
658
|
</div>
|
|
363
659
|
<div class="scenario-toolbar">
|
|
364
660
|
${renderHostChrome(suite, scenario, selectedHost)}
|
|
365
661
|
<div class="scenario-actions">
|
|
366
|
-
|
|
367
|
-
<button class="run-row" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}"${scenario.skip ? " disabled" : ""}>Run selected hosts</button>
|
|
662
|
+
<button class="primary run-toggle run-cell" data-start-label="Start test" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}" data-host="${escapeHtml(selectedHost)}"${scenario.skip || !canRun ? ' disabled data-fixed-disabled="1"' : runDisabled ? " disabled" : ""}>Start test</button>
|
|
368
663
|
</div>
|
|
369
664
|
</div>
|
|
370
|
-
<div class="scenario-live" hidden data-live-row="${key}" data-live-slot="${key}">
|
|
371
|
-
<div class="host-panels">${renderHostPanels(suite, selectedHost)}</div>
|
|
665
|
+
<div class="scenario-live"${hasResult ? "" : " hidden"} data-live-row="${key}" data-live-slot="${key}">
|
|
666
|
+
<div class="host-panels">${renderHostPanels(suite, scenario, selectedHost, results, canRun)}</div>
|
|
372
667
|
</div>
|
|
373
668
|
</article>`;
|
|
374
669
|
}
|
|
375
|
-
function renderSuiteSection(suite) {
|
|
376
|
-
const cards = suite.scenarios
|
|
670
|
+
function renderSuiteSection(suite, results, canRun, defaultSelectedHosts, firstScenarioKey) {
|
|
671
|
+
const cards = suite.scenarios
|
|
672
|
+
.map((scenario) => renderScenarioCard(suite, scenario, results, canRun, defaultSelectedHosts, `${suite.name}::${scenario.name}` === firstScenarioKey))
|
|
673
|
+
.join("");
|
|
377
674
|
return `
|
|
378
675
|
<section class="suite" data-suite="${escapeHtml(suite.name)}">
|
|
379
|
-
<header class="suite-header">
|
|
380
|
-
<div class="suite-title">
|
|
381
|
-
<h2>${escapeHtml(suite.name)}</h2>
|
|
382
|
-
${suite.description ? `<p class="muted">${escapeHtml(suite.description)}</p>` : ""}
|
|
383
|
-
</div>
|
|
384
|
-
<button class="run-suite" data-suite="${escapeHtml(suite.name)}">Run suite</button>
|
|
385
|
-
</header>
|
|
386
676
|
<div class="scenario-list">${cards}</div>
|
|
387
677
|
</section>`;
|
|
388
678
|
}
|
|
679
|
+
function renderTestNavigator(catalog, firstScenarioKey, canRun) {
|
|
680
|
+
const groups = catalog.suites
|
|
681
|
+
.map((suite) => {
|
|
682
|
+
const tests = suite.scenarios
|
|
683
|
+
.map((scenario) => {
|
|
684
|
+
const key = `${suite.name}::${scenario.name}`;
|
|
685
|
+
const selected = key === firstScenarioKey;
|
|
686
|
+
const status = scenario.skip ? "skipped" : "idle";
|
|
687
|
+
return `<button type="button" class="test-nav-item" data-select-scenario="${escapeHtml(key)}" data-status="${status}" aria-current="${selected ? "true" : "false"}">
|
|
688
|
+
<span class="test-nav-status" aria-hidden="true"></span>
|
|
689
|
+
<span class="test-nav-copy"><strong>${escapeHtml(scenario.name)}</strong>${scenario.description ? `<small>${escapeHtml(scenario.description)}</small>` : ""}</span>
|
|
690
|
+
</button>`;
|
|
691
|
+
})
|
|
692
|
+
.join("");
|
|
693
|
+
return `<section class="test-nav-suite" data-nav-suite="${escapeHtml(suite.name)}">
|
|
694
|
+
<header><div><h2>${escapeHtml(suite.name)}</h2>${suite.description ? `<p>${escapeHtml(suite.description)}</p>` : ""}</div><button class="run-toggle run-suite" data-start-label="Start suite" data-suite="${escapeHtml(suite.name)}"${canRun ? "" : ' disabled data-fixed-disabled="1"'}>Start suite</button></header>
|
|
695
|
+
<div class="test-nav-items">${tests}</div>
|
|
696
|
+
</section>`;
|
|
697
|
+
})
|
|
698
|
+
.join("");
|
|
699
|
+
const options = catalog.suites
|
|
700
|
+
.flatMap((suite) => suite.scenarios.map((scenario) => {
|
|
701
|
+
const key = `${suite.name}::${scenario.name}`;
|
|
702
|
+
return `<option value="${escapeHtml(key)}"${key === firstScenarioKey ? " selected" : ""}>${escapeHtml(suite.name)} / ${escapeHtml(scenario.name)}</option>`;
|
|
703
|
+
}))
|
|
704
|
+
.join("");
|
|
705
|
+
return `<aside class="test-navigator" aria-label="Tests">
|
|
706
|
+
<div class="test-navigator-heading"><div><p class="section-label">Test catalog</p><h2>${catalog.suites.reduce((sum, suite) => sum + suite.scenarios.length, 0)} tests</h2></div></div>
|
|
707
|
+
<label class="test-picker-label" for="test-picker">Selected test</label>
|
|
708
|
+
<select class="test-picker" id="test-picker">${options}</select>
|
|
709
|
+
<nav class="test-tree" aria-label="Test catalog">${groups}</nav>
|
|
710
|
+
</aside>`;
|
|
711
|
+
}
|
|
389
712
|
function clientScript() {
|
|
390
713
|
return `
|
|
391
714
|
(function () {
|
|
392
|
-
var
|
|
715
|
+
var bootstrap = JSON.parse(document.getElementById("bootstrap-data").textContent);
|
|
716
|
+
var catalog = bootstrap.catalog;
|
|
393
717
|
var source = null;
|
|
394
718
|
var runId = null;
|
|
719
|
+
var selectedRunId = bootstrap.selectedRunId || null;
|
|
395
720
|
var cancelRequested = false;
|
|
721
|
+
var activeRunRequest = null;
|
|
722
|
+
var viewedRunHosts = null;
|
|
396
723
|
var progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {}, experiments: {} };
|
|
724
|
+
var selectedScenarioKey = document.querySelector("[data-scenario-card][data-selected='true']")?.getAttribute("data-scenario-card") || null;
|
|
725
|
+
|
|
726
|
+
function selectScenario(key) {
|
|
727
|
+
if (!key) return;
|
|
728
|
+
selectedScenarioKey = key;
|
|
729
|
+
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
730
|
+
var selected = card.getAttribute("data-scenario-card") === key;
|
|
731
|
+
card.dataset.selected = selected ? "true" : "false";
|
|
732
|
+
card.hidden = !selected;
|
|
733
|
+
});
|
|
734
|
+
document.querySelectorAll("[data-select-scenario]").forEach(function (item) {
|
|
735
|
+
item.setAttribute("aria-current", item.getAttribute("data-select-scenario") === key ? "true" : "false");
|
|
736
|
+
});
|
|
737
|
+
var picker = document.getElementById("test-picker");
|
|
738
|
+
if (picker) picker.value = key;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function syncScenarioStatus(suite, scenario) {
|
|
742
|
+
var key = suite + "::" + scenario;
|
|
743
|
+
var card = document.querySelector('[data-scenario-card="' + key + '"]');
|
|
744
|
+
if (!card) return;
|
|
745
|
+
var labels = Array.prototype.map.call(card.querySelectorAll(".cell-status"), function (node) { return node.textContent; });
|
|
746
|
+
var status = labels.indexOf("failed") !== -1 ? "failed"
|
|
747
|
+
: labels.some(function (label) { return label === "running" || label === "starting" || label === "cancelling"; }) ? "running"
|
|
748
|
+
: labels.indexOf("passed") !== -1 ? "passed"
|
|
749
|
+
: labels.length > 0 && labels.every(function (label) { return label === "skip" || label === "skipped" || label === "cancelled"; }) ? "skipped"
|
|
750
|
+
: "idle";
|
|
751
|
+
document.querySelectorAll('[data-select-scenario="' + key + '"]').forEach(function (item) { item.dataset.status = status; });
|
|
752
|
+
var verdict = document.querySelector('[data-focus-verdict="' + key + '"]');
|
|
753
|
+
if (verdict) {
|
|
754
|
+
verdict.dataset.status = status;
|
|
755
|
+
verdict.lastChild.textContent = status === "idle" ? "Not run" : status.charAt(0).toUpperCase() + status.slice(1);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
397
758
|
|
|
398
759
|
function selectedHosts() {
|
|
399
760
|
return Array.prototype.map.call(document.querySelectorAll("[data-host-toggle]:checked"), function (box) {
|
|
@@ -401,6 +762,80 @@ function clientScript() {
|
|
|
401
762
|
});
|
|
402
763
|
}
|
|
403
764
|
|
|
765
|
+
function updateHostSelectionControls() {
|
|
766
|
+
var hasSelectedHost = selectedHosts().length > 0;
|
|
767
|
+
syncScenarioHostChoices();
|
|
768
|
+
document.querySelectorAll(".run-toggle").forEach(function (button) {
|
|
769
|
+
if (button.dataset.runAction !== "stop") {
|
|
770
|
+
var hasTarget = button.classList.contains("run-cell")
|
|
771
|
+
? !!button.getAttribute("data-host")
|
|
772
|
+
: hasSelectedHost;
|
|
773
|
+
button.disabled = !hasTarget || button.dataset.fixedDisabled === "1";
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function syncScenarioHostChoices() {
|
|
779
|
+
var hosts = Array.isArray(viewedRunHosts) ? viewedRunHosts : selectedHosts();
|
|
780
|
+
var isFiltered = hosts.length > 0;
|
|
781
|
+
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
782
|
+
var tabs = Array.prototype.slice.call(card.querySelectorAll(".host-tab"));
|
|
783
|
+
if (tabs.length === 0) return;
|
|
784
|
+
tabs.forEach(function (tab) {
|
|
785
|
+
var eligible = !tab.disabled && (!isFiltered || hosts.indexOf(tab.getAttribute("data-host")) !== -1);
|
|
786
|
+
tab.hidden = isFiltered && !eligible;
|
|
787
|
+
});
|
|
788
|
+
var current = tabs.find(function (tab) { return tab.getAttribute("aria-selected") === "true" && !tab.hidden && !tab.disabled; });
|
|
789
|
+
var available = tabs.find(function (tab) { return !tab.hidden && !tab.disabled; });
|
|
790
|
+
if (!current && isFiltered && available) {
|
|
791
|
+
selectHostTab(card, available.getAttribute("data-host"));
|
|
792
|
+
current = available;
|
|
793
|
+
}
|
|
794
|
+
var empty = card.querySelector(".host-empty-selection");
|
|
795
|
+
if (empty) empty.hidden = !!available;
|
|
796
|
+
if (!available) {
|
|
797
|
+
tabs.forEach(function (tab) { tab.setAttribute("aria-selected", "false"); });
|
|
798
|
+
card.querySelectorAll("[data-host-panel]").forEach(function (panel) { panel.hidden = true; });
|
|
799
|
+
var run = card.querySelector("button.run-cell");
|
|
800
|
+
if (run) {
|
|
801
|
+
run.setAttribute("data-host", "");
|
|
802
|
+
run.disabled = true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function scopedRunControl(request) {
|
|
809
|
+
if (!request) return null;
|
|
810
|
+
if (request.scenario) {
|
|
811
|
+
return document.querySelector('.run-cell[data-suite="' + request.suite + '"][data-scenario="' + request.scenario + '"]');
|
|
812
|
+
}
|
|
813
|
+
if (request.suite) return document.querySelector('.run-suite[data-suite="' + request.suite + '"]');
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function setRunControls(status, request) {
|
|
818
|
+
activeRunRequest = status === "idle" ? null : request || activeRunRequest;
|
|
819
|
+
document.querySelectorAll(".run-toggle").forEach(function (button) {
|
|
820
|
+
button.textContent = button.dataset.startLabel || "Start run";
|
|
821
|
+
button.dataset.runAction = "start";
|
|
822
|
+
button.classList.remove("danger");
|
|
823
|
+
});
|
|
824
|
+
updateHostSelectionControls();
|
|
825
|
+
if (status === "idle") return;
|
|
826
|
+
document.querySelectorAll(".run-toggle").forEach(function (button) { button.disabled = true; });
|
|
827
|
+
var controls = [document.getElementById("run-selection"), scopedRunControl(activeRunRequest)];
|
|
828
|
+
controls.forEach(function (button) {
|
|
829
|
+
if (!button) return;
|
|
830
|
+
button.dataset.runAction = "stop";
|
|
831
|
+
button.textContent = status === "cancelling"
|
|
832
|
+
? "Stopping…"
|
|
833
|
+
: button.id === "run-selection" ? "Stop run" : (button.classList.contains("run-suite") ? "Stop suite" : "Stop test");
|
|
834
|
+
button.classList.add("danger");
|
|
835
|
+
button.disabled = status === "cancelling";
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
404
839
|
function parallelHosts() {
|
|
405
840
|
return document.getElementById("parallel-hosts").checked;
|
|
406
841
|
}
|
|
@@ -410,7 +845,7 @@ function clientScript() {
|
|
|
410
845
|
}
|
|
411
846
|
|
|
412
847
|
function runnableTargets(body) {
|
|
413
|
-
var requestedHosts =
|
|
848
|
+
var requestedHosts = Array.isArray(body.hosts) ? body.hosts : catalog.defaultSelectedHosts;
|
|
414
849
|
var total = 0;
|
|
415
850
|
catalog.suites.forEach(function (suite) {
|
|
416
851
|
if (body.suite && suite.name !== body.suite) {
|
|
@@ -702,11 +1137,129 @@ function clientScript() {
|
|
|
702
1137
|
markRunningIdle();
|
|
703
1138
|
}
|
|
704
1139
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1140
|
+
function resetViewerCards() {
|
|
1141
|
+
closeAllChats();
|
|
1142
|
+
document.querySelectorAll("[data-host-panel]").forEach(function (panel) {
|
|
1143
|
+
panel.innerHTML = '<p class="host-empty">No run yet.</p>';
|
|
1144
|
+
});
|
|
1145
|
+
document.querySelectorAll(".scenario-live").forEach(function (live) { live.hidden = true; });
|
|
1146
|
+
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
1147
|
+
if (status.textContent !== "skip") {
|
|
1148
|
+
status.textContent = "idle";
|
|
1149
|
+
status.className = "cell-status muted";
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
catalog.suites.forEach(function (suite) {
|
|
1153
|
+
suite.scenarios.forEach(function (scenario) { syncScenarioStatus(suite.name, scenario.name); });
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function runLabel(run) {
|
|
1158
|
+
var scope = run.request && (run.request.scenario || run.request.suite) || "all tests";
|
|
1159
|
+
var time = new Date(run.startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1160
|
+
return time + " · " + scope + " · " + run.status;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function renderRunHistory(runs) {
|
|
1164
|
+
bootstrap.runs = runs;
|
|
1165
|
+
var select = document.getElementById("run-history");
|
|
1166
|
+
select.replaceChildren();
|
|
1167
|
+
var idle = document.createElement("option");
|
|
1168
|
+
idle.value = "";
|
|
1169
|
+
idle.textContent = "Tests · no run selected";
|
|
1170
|
+
select.appendChild(idle);
|
|
1171
|
+
runs.forEach(function (run) {
|
|
1172
|
+
var option = document.createElement("option");
|
|
1173
|
+
option.value = run.id;
|
|
1174
|
+
option.textContent = runLabel(run);
|
|
1175
|
+
select.appendChild(option);
|
|
1176
|
+
});
|
|
1177
|
+
select.value = selectedRunId || "";
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function completedRunBanner(run) {
|
|
1181
|
+
if (run.status === "cancelled") return "Run cancelled.";
|
|
1182
|
+
if (run.status !== "completed") return "Run " + run.id + " · " + run.status + ".";
|
|
1183
|
+
var counts = (run.reports || []).reduce(function (total, report) {
|
|
1184
|
+
total.passed += report.passed || 0;
|
|
1185
|
+
total.failed += report.failed || 0;
|
|
1186
|
+
total.skipped += report.skipped || 0;
|
|
1187
|
+
return total;
|
|
1188
|
+
}, { passed: 0, failed: 0, skipped: 0 });
|
|
1189
|
+
return "Run finished. " + counts.passed + " passed. " + counts.failed + " failed. " + counts.skipped + " skipped.";
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
async function refreshRunHistory() {
|
|
1193
|
+
if (!bootstrap.capabilities.canRun) return;
|
|
1194
|
+
var response = await fetch("/api/runs");
|
|
1195
|
+
if (!response.ok) return;
|
|
1196
|
+
renderRunHistory(await response.json());
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async function selectRun(id) {
|
|
1200
|
+
selectedRunId = id || null;
|
|
1201
|
+
viewedRunHosts = null;
|
|
1202
|
+
if (source) { source.close(); source = null; }
|
|
1203
|
+
runId = null;
|
|
1204
|
+
cancelRequested = false;
|
|
1205
|
+
resetViewerCards();
|
|
1206
|
+
if (!selectedRunId) {
|
|
1207
|
+
setRunBanner("Choose tests to run.");
|
|
1208
|
+
setRunControls("idle");
|
|
1209
|
+
renderRunHistory(bootstrap.runs || []);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
var response = await fetch("/api/runs/" + selectedRunId);
|
|
1213
|
+
if (!response.ok) return;
|
|
1214
|
+
var payload = await response.json();
|
|
1215
|
+
viewedRunHosts = Array.isArray(payload.run.request && payload.run.request.hosts) ? payload.run.request.hosts : null;
|
|
1216
|
+
syncScenarioHostChoices();
|
|
1217
|
+
(payload.fragments || []).forEach(function (fragment) {
|
|
1218
|
+
var card = document.querySelector('[data-scenario-card="' + fragment.suite + "::" + fragment.scenario + '"]');
|
|
1219
|
+
var panel = card && card.querySelector('[data-host-panel="' + fragment.host + '"]');
|
|
1220
|
+
if (!card || !panel) return;
|
|
1221
|
+
panel.innerHTML = fragment.html;
|
|
1222
|
+
var live = card.querySelector(".scenario-live");
|
|
1223
|
+
if (live) live.hidden = false;
|
|
1224
|
+
markCell(fragment, fragment.skipped ? "skipped" : fragment.passed ? "passed" : "failed", fragment.skipped ? "status-skipped" : fragment.passed ? "status-passed" : "status-failed");
|
|
1225
|
+
});
|
|
1226
|
+
var preferred = (payload.fragments || []).find(function (fragment) { return !fragment.skipped && !fragment.passed; }) || (payload.fragments || [])[0];
|
|
1227
|
+
if (preferred) selectScenario(preferred.suite + "::" + preferred.scenario, false);
|
|
1228
|
+
if (payload.run.status === "cancelled") {
|
|
1229
|
+
markRunTargets(payload.run.request || {}, "cancelled", "status-skipped");
|
|
1230
|
+
}
|
|
1231
|
+
setRunBanner(completedRunBanner(payload.run));
|
|
1232
|
+
renderRunHistory((bootstrap.runs || []).map(function (item) { return item.id === payload.run.id ? payload.run : item; }));
|
|
1233
|
+
if (payload.run.status === "running" || payload.run.status === "cancelling") {
|
|
1234
|
+
runId = payload.run.id;
|
|
1235
|
+
setRunControls(payload.run.status === "cancelling" ? "cancelling" : "running", payload.run.request);
|
|
1236
|
+
source = new EventSource("/api/runs/" + runId + "/events");
|
|
1237
|
+
source.onmessage = function (message) { handleEvent(JSON.parse(message.data)); };
|
|
1238
|
+
} else {
|
|
1239
|
+
setRunControls("idle");
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function requestRunStop() {
|
|
1244
|
+
if (!runId || cancelRequested) return;
|
|
1245
|
+
cancelRequested = true;
|
|
1246
|
+
setRunBanner("Stopping the run.");
|
|
1247
|
+
setRunControls("cancelling", activeRunRequest);
|
|
1248
|
+
cancelOpenChats();
|
|
1249
|
+
fetch("/api/runs/" + runId + "/cancel", { method: "POST" }).then(function (response) {
|
|
1250
|
+
if (!response.ok && cancelRequested) {
|
|
1251
|
+
setRunBanner("Stop failed.");
|
|
1252
|
+
cancelRequested = false;
|
|
1253
|
+
setRunControls("running", activeRunRequest);
|
|
1254
|
+
}
|
|
1255
|
+
}).catch(function () {
|
|
1256
|
+
if (cancelRequested) {
|
|
1257
|
+
setRunBanner("Stop failed.");
|
|
1258
|
+
cancelRequested = false;
|
|
1259
|
+
setRunControls("running", activeRunRequest);
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
710
1263
|
|
|
711
1264
|
function showCancelledNote(article, text) {
|
|
712
1265
|
if (!article) {
|
|
@@ -1238,7 +1791,7 @@ function clientScript() {
|
|
|
1238
1791
|
return { passed: false, line: gate.metric + ": one arm did not report a value. Fail." };
|
|
1239
1792
|
}
|
|
1240
1793
|
var passed = winnerValue < loserValue;
|
|
1241
|
-
return { passed: passed, line: winner.label + " must
|
|
1794
|
+
return { passed: passed, line: winner.label + " must use fewer " + gate.metric + " than " + loser.label + " (" + winnerValue + " vs " + loserValue + "). " + (passed ? "Pass." : "Fail.") };
|
|
1242
1795
|
});
|
|
1243
1796
|
}
|
|
1244
1797
|
|
|
@@ -1272,7 +1825,6 @@ function clientScript() {
|
|
|
1272
1825
|
summarizeMetric(rows, "turns", "Turns"),
|
|
1273
1826
|
summarizeMetric(rows, "tokens", "Tokens"),
|
|
1274
1827
|
summarizeMetric(rows, "tools", "Tools"),
|
|
1275
|
-
summarizeMetric(rows, "durationMs", "Duration"),
|
|
1276
1828
|
];
|
|
1277
1829
|
var gateLines = summarizeGates(rows, scenario.gates);
|
|
1278
1830
|
var existing = wrap.querySelector(".compare-winners");
|
|
@@ -1312,6 +1864,7 @@ function clientScript() {
|
|
|
1312
1864
|
node.setAttribute("data-status", label);
|
|
1313
1865
|
}
|
|
1314
1866
|
});
|
|
1867
|
+
syncScenarioStatus(event.suite, event.scenario);
|
|
1315
1868
|
}
|
|
1316
1869
|
|
|
1317
1870
|
function metricPart(count, one, many) {
|
|
@@ -1420,10 +1973,11 @@ function clientScript() {
|
|
|
1420
1973
|
return;
|
|
1421
1974
|
}
|
|
1422
1975
|
setRunBanner("Run " + event.runId + " started.");
|
|
1423
|
-
|
|
1976
|
+
setRunControls("running", activeRunRequest);
|
|
1424
1977
|
return;
|
|
1425
1978
|
}
|
|
1426
1979
|
if (event.type === "run_finished") {
|
|
1980
|
+
var wasCancelled = cancelRequested;
|
|
1427
1981
|
finishProgress(event);
|
|
1428
1982
|
hideAllRunning();
|
|
1429
1983
|
sealAllChats();
|
|
@@ -1435,13 +1989,16 @@ function clientScript() {
|
|
|
1435
1989
|
"Run finished. " + progress.passed + " passed. " + progress.failed + " failed. " + progress.skipped + " skipped."
|
|
1436
1990
|
);
|
|
1437
1991
|
}
|
|
1438
|
-
|
|
1992
|
+
setRunControls("idle");
|
|
1439
1993
|
cancelRequested = false;
|
|
1440
1994
|
runId = null;
|
|
1441
1995
|
if (source) {
|
|
1442
1996
|
source.close();
|
|
1443
1997
|
source = null;
|
|
1444
1998
|
}
|
|
1999
|
+
void refreshRunHistory().then(function () {
|
|
2000
|
+
if (!wasCancelled) return selectRun(event.runId);
|
|
2001
|
+
});
|
|
1445
2002
|
return;
|
|
1446
2003
|
}
|
|
1447
2004
|
if (cancelRequested) {
|
|
@@ -1458,6 +2015,21 @@ function clientScript() {
|
|
|
1458
2015
|
}
|
|
1459
2016
|
return;
|
|
1460
2017
|
}
|
|
2018
|
+
if (event.type === "scenario_result") {
|
|
2019
|
+
fetch("/api/runs/" + (runId || selectedRunId)).then(function (response) {
|
|
2020
|
+
return response.ok ? response.json() : null;
|
|
2021
|
+
}).then(function (payload) {
|
|
2022
|
+
if (!payload || selectedRunId !== payload.run.id) return;
|
|
2023
|
+
var fragment = (payload.fragments || []).find(function (item) {
|
|
2024
|
+
return item.suite === event.suite && item.scenario === event.scenario && item.host === event.host;
|
|
2025
|
+
});
|
|
2026
|
+
if (!fragment) return;
|
|
2027
|
+
var card = scenarioCard(event);
|
|
2028
|
+
var panel = card && card.querySelector('[data-host-panel="' + event.host + '"]');
|
|
2029
|
+
if (panel) panel.innerHTML = fragment.html;
|
|
2030
|
+
}).catch(function () {});
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
1461
2033
|
if (event.type === "cell_started") {
|
|
1462
2034
|
markCell(event, "running", "status-skipped");
|
|
1463
2035
|
startChat(event);
|
|
@@ -1568,7 +2140,7 @@ function clientScript() {
|
|
|
1568
2140
|
}
|
|
1569
2141
|
|
|
1570
2142
|
function markRunTargets(body, label, statusClass) {
|
|
1571
|
-
var hosts =
|
|
2143
|
+
var hosts = Array.isArray(body.hosts) ? body.hosts : catalog.defaultSelectedHosts;
|
|
1572
2144
|
catalog.suites.forEach(function (suite) {
|
|
1573
2145
|
if (body.suite && suite.name !== body.suite) {
|
|
1574
2146
|
return;
|
|
@@ -1591,6 +2163,11 @@ function clientScript() {
|
|
|
1591
2163
|
}
|
|
1592
2164
|
|
|
1593
2165
|
async function startRun(body) {
|
|
2166
|
+
if (!bootstrap.capabilities.canRun) return;
|
|
2167
|
+
if (Array.isArray(body.hosts) && body.hosts.length === 0) {
|
|
2168
|
+
setRunBanner("Select at least one host.");
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
1594
2171
|
if (runId) {
|
|
1595
2172
|
setRunBanner(
|
|
1596
2173
|
cancelRequested
|
|
@@ -1599,6 +2176,11 @@ function clientScript() {
|
|
|
1599
2176
|
);
|
|
1600
2177
|
return;
|
|
1601
2178
|
}
|
|
2179
|
+
resetViewerCards();
|
|
2180
|
+
activeRunRequest = body;
|
|
2181
|
+
viewedRunHosts = Array.isArray(body.hosts) ? body.hosts : null;
|
|
2182
|
+
syncScenarioHostChoices();
|
|
2183
|
+
setRunControls("running", body);
|
|
1602
2184
|
resetProgress(body);
|
|
1603
2185
|
markRunTargets(body, "starting", "status-skipped");
|
|
1604
2186
|
var firstCard = document.querySelector(
|
|
@@ -1618,6 +2200,7 @@ function clientScript() {
|
|
|
1618
2200
|
delete card.dataset.followHost;
|
|
1619
2201
|
});
|
|
1620
2202
|
if (firstCard) {
|
|
2203
|
+
selectScenario(firstCard.getAttribute("data-scenario-card"), false);
|
|
1621
2204
|
var live = firstCard.querySelector(".scenario-live");
|
|
1622
2205
|
if (live) {
|
|
1623
2206
|
live.hidden = false;
|
|
@@ -1642,12 +2225,15 @@ function clientScript() {
|
|
|
1642
2225
|
progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {} };
|
|
1643
2226
|
renderProgress();
|
|
1644
2227
|
markRunTargets(body, "idle", "muted");
|
|
2228
|
+
setRunControls("idle");
|
|
1645
2229
|
return;
|
|
1646
2230
|
}
|
|
1647
2231
|
runId = payload.runId;
|
|
2232
|
+
selectedRunId = runId;
|
|
2233
|
+
await refreshRunHistory();
|
|
1648
2234
|
cancelRequested = false;
|
|
1649
2235
|
setRunBanner("Run " + runId + " started.");
|
|
1650
|
-
|
|
2236
|
+
setRunControls("running", body);
|
|
1651
2237
|
if (source) {
|
|
1652
2238
|
source.close();
|
|
1653
2239
|
}
|
|
@@ -1665,39 +2251,29 @@ function clientScript() {
|
|
|
1665
2251
|
} catch (error) {
|
|
1666
2252
|
setRunBanner(error instanceof Error ? error.message : String(error));
|
|
1667
2253
|
markRunTargets(body, "idle", "muted");
|
|
2254
|
+
setRunControls("idle");
|
|
1668
2255
|
}
|
|
1669
2256
|
}
|
|
1670
2257
|
|
|
1671
|
-
document.getElementById("run-selection").addEventListener("click", function () {
|
|
2258
|
+
document.getElementById("run-selection").addEventListener("click", function (event) {
|
|
2259
|
+
if (event.currentTarget.dataset.runAction === "stop") return requestRunStop();
|
|
1672
2260
|
startRun({ hosts: selectedHosts(), parallelHosts: parallelHosts() });
|
|
1673
2261
|
});
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
setCancelButton("Cancelling", true);
|
|
1681
|
-
cancelOpenChats();
|
|
1682
|
-
fetch("/api/runs/" + runId + "/cancel", { method: "POST" }).then(function (response) {
|
|
1683
|
-
if (!response.ok && cancelRequested) {
|
|
1684
|
-
setRunBanner("Cancel failed.");
|
|
1685
|
-
setCancelButton("Cancel run", false);
|
|
1686
|
-
cancelRequested = false;
|
|
1687
|
-
}
|
|
1688
|
-
}).catch(function () {
|
|
1689
|
-
if (cancelRequested) {
|
|
1690
|
-
setRunBanner("Cancel failed.");
|
|
1691
|
-
setCancelButton("Cancel run", false);
|
|
1692
|
-
cancelRequested = false;
|
|
1693
|
-
}
|
|
1694
|
-
});
|
|
1695
|
-
});
|
|
2262
|
+
document.querySelectorAll("[data-host-toggle]").forEach(function (toggle) {
|
|
2263
|
+
toggle.addEventListener("change", updateHostSelectionControls);
|
|
2264
|
+
});
|
|
2265
|
+
document.getElementById("run-history").addEventListener("change", function (event) {
|
|
2266
|
+
void selectRun(event.target.value);
|
|
2267
|
+
});
|
|
1696
2268
|
document.body.addEventListener("click", function (event) {
|
|
1697
2269
|
var button = event.target.closest("button");
|
|
1698
2270
|
if (!button) {
|
|
1699
2271
|
return;
|
|
1700
2272
|
}
|
|
2273
|
+
if (button.classList.contains("test-nav-item")) {
|
|
2274
|
+
selectScenario(button.getAttribute("data-select-scenario"), false);
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
1701
2277
|
if (button.classList.contains("host-tab")) {
|
|
1702
2278
|
var card = button.closest("[data-scenario-card]");
|
|
1703
2279
|
if (card) {
|
|
@@ -1706,6 +2282,7 @@ function clientScript() {
|
|
|
1706
2282
|
}
|
|
1707
2283
|
}
|
|
1708
2284
|
if (button.classList.contains("run-cell")) {
|
|
2285
|
+
if (button.dataset.runAction === "stop") return requestRunStop();
|
|
1709
2286
|
startRun({
|
|
1710
2287
|
suite: button.getAttribute("data-suite"),
|
|
1711
2288
|
scenario: button.getAttribute("data-scenario"),
|
|
@@ -1713,67 +2290,103 @@ function clientScript() {
|
|
|
1713
2290
|
parallelHosts: false,
|
|
1714
2291
|
});
|
|
1715
2292
|
}
|
|
1716
|
-
if (button.classList.contains("run-row")) {
|
|
1717
|
-
startRun({
|
|
1718
|
-
suite: button.getAttribute("data-suite"),
|
|
1719
|
-
scenario: button.getAttribute("data-scenario"),
|
|
1720
|
-
hosts: selectedHosts(),
|
|
1721
|
-
parallelHosts: parallelHosts(),
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
2293
|
if (button.classList.contains("run-suite")) {
|
|
2294
|
+
if (button.dataset.runAction === "stop") return requestRunStop();
|
|
1725
2295
|
startRun({
|
|
1726
2296
|
suite: button.getAttribute("data-suite"),
|
|
1727
2297
|
hosts: selectedHosts(),
|
|
1728
2298
|
parallelHosts: parallelHosts(),
|
|
1729
2299
|
});
|
|
1730
2300
|
}
|
|
2301
|
+
});
|
|
2302
|
+
document.getElementById("test-picker")?.addEventListener("change", function (event) {
|
|
2303
|
+
selectScenario(event.target.value, false);
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
renderRunHistory(bootstrap.runs || []);
|
|
2307
|
+
selectScenario(selectedScenarioKey, false);
|
|
2308
|
+
updateHostSelectionControls();
|
|
2309
|
+
if (selectedRunId && bootstrap.capabilities.canRun) void selectRun(selectedRunId);
|
|
2310
|
+
if (bootstrap.capabilities.canRun) setInterval(function () { void refreshRunHistory(); }, 1000);
|
|
2311
|
+
})();
|
|
2312
|
+
`;
|
|
2313
|
+
}
|
|
2314
|
+
function staticNavigationScript() {
|
|
2315
|
+
return `
|
|
2316
|
+
(function () {
|
|
2317
|
+
function selectScenario(key) {
|
|
2318
|
+
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
2319
|
+
var selected = card.getAttribute("data-scenario-card") === key;
|
|
2320
|
+
card.dataset.selected = selected ? "true" : "false";
|
|
2321
|
+
card.hidden = !selected;
|
|
2322
|
+
});
|
|
2323
|
+
document.querySelectorAll("[data-select-scenario]").forEach(function (item) {
|
|
2324
|
+
item.setAttribute("aria-current", item.getAttribute("data-select-scenario") === key ? "true" : "false");
|
|
2325
|
+
});
|
|
2326
|
+
var picker = document.getElementById("test-picker");
|
|
2327
|
+
if (picker) picker.value = key;
|
|
2328
|
+
}
|
|
2329
|
+
document.body.addEventListener("click", function (event) {
|
|
2330
|
+
var item = event.target.closest("[data-select-scenario]");
|
|
2331
|
+
if (item) selectScenario(item.getAttribute("data-select-scenario"));
|
|
2332
|
+
});
|
|
2333
|
+
document.getElementById("test-picker")?.addEventListener("change", function (event) {
|
|
2334
|
+
selectScenario(event.target.value);
|
|
1731
2335
|
});
|
|
1732
2336
|
})();
|
|
1733
2337
|
`;
|
|
1734
2338
|
}
|
|
1735
|
-
export function renderViewerPage(
|
|
2339
|
+
export function renderViewerPage(input, options = {}) {
|
|
2340
|
+
const bootstrap = "catalog" in input ? input : { catalog: input, runs: [], capabilities: { canRun: true } };
|
|
2341
|
+
const catalog = bootstrap.catalog;
|
|
2342
|
+
const canRun = bootstrap.capabilities.canRun;
|
|
2343
|
+
const firstScenarioKey = catalog.suites[0]?.scenarios[0]
|
|
2344
|
+
? `${catalog.suites[0].name}::${catalog.suites[0].scenarios[0].name}`
|
|
2345
|
+
: "";
|
|
1736
2346
|
const hostToggles = Array.from(new Set(catalog.suites.flatMap((suite) => suite.hosts)))
|
|
1737
2347
|
.map((host) => {
|
|
1738
2348
|
const checked = catalog.defaultSelectedHosts.includes(host) ? " checked" : "";
|
|
1739
2349
|
return `<label><input type="checkbox" data-host-toggle value="${escapeHtml(host)}"${checked}>${escapeHtml(host)}</label>`;
|
|
1740
2350
|
})
|
|
1741
2351
|
.join("");
|
|
1742
|
-
const suites = catalog.suites
|
|
2352
|
+
const suites = catalog.suites
|
|
2353
|
+
.map((suite) => renderSuiteSection(suite, options.results ?? [], canRun, catalog.defaultSelectedHosts, firstScenarioKey))
|
|
2354
|
+
.join("\n");
|
|
2355
|
+
const navigator = renderTestNavigator(catalog, firstScenarioKey, canRun);
|
|
2356
|
+
const historyOptions = [
|
|
2357
|
+
`<option value="">Tests · no run selected</option>`,
|
|
2358
|
+
...bootstrap.runs.map((run) => {
|
|
2359
|
+
const selected = run.id === bootstrap.selectedRunId ? " selected" : "";
|
|
2360
|
+
return `<option value="${escapeHtml(run.id)}"${selected}>${escapeHtml(run.id)} · ${escapeHtml(run.status)}</option>`;
|
|
2361
|
+
}),
|
|
2362
|
+
].join("");
|
|
1743
2363
|
return `<!DOCTYPE html>
|
|
1744
2364
|
<html lang="en">
|
|
1745
2365
|
<head>
|
|
1746
2366
|
<meta charset="utf-8">
|
|
1747
2367
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1748
|
-
<title>agent-test viewer</title>
|
|
2368
|
+
<title>agent-test ${canRun ? "viewer" : "report"}</title>
|
|
1749
2369
|
<style>
|
|
1750
|
-
${
|
|
2370
|
+
${options.baseCss ?? ""}
|
|
1751
2371
|
${viewerCss()}
|
|
1752
2372
|
</style>
|
|
1753
2373
|
</head>
|
|
1754
2374
|
<body>
|
|
1755
|
-
<main>
|
|
1756
|
-
<header class="
|
|
1757
|
-
<p class="brand">agent-test</p>
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2375
|
+
<main class="viewer-shell">
|
|
2376
|
+
<header class="viewer-topbar">
|
|
2377
|
+
<div class="viewer-identity"><p class="brand">agent-test</p><h1>${canRun ? "Test viewer" : "Run report"}</h1></div>
|
|
2378
|
+
<div class="run-context">
|
|
2379
|
+
<label class="toolbar-label" for="run-history">Viewing run</label>
|
|
2380
|
+
<select class="run-history" id="run-history" aria-label="Run history"${canRun ? "" : " disabled"}>${historyOptions}</select>
|
|
2381
|
+
</div>
|
|
2382
|
+
<p class="run-banner${canRun ? "" : " lede"}" id="run-banner" aria-live="polite">${canRun ? "Choose a test to inspect or run." : escapeHtml(options.headerLede ?? "Read-only report.")}</p>
|
|
1761
2383
|
</header>
|
|
1762
|
-
|
|
1763
|
-
<div class="toolbar-block">
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
<p class="toolbar-label">Run order</p>
|
|
1769
|
-
<label><input type="checkbox" id="parallel-hosts"> Run hosts together</label>
|
|
1770
|
-
</div>
|
|
1771
|
-
<div class="toolbar-actions">
|
|
1772
|
-
<button class="primary" id="run-selection">Run selection</button>
|
|
1773
|
-
<button class="danger" id="cancel-run" disabled>Cancel run</button>
|
|
1774
|
-
</div>
|
|
1775
|
-
<p class="run-banner" id="run-banner"></p>
|
|
1776
|
-
</section>
|
|
2384
|
+
<div class="viewer-command-row">
|
|
2385
|
+
<div class="toolbar-block"><p class="toolbar-label">Hosts</p><div class="host-toggles">${hostToggles}</div></div>
|
|
2386
|
+
<label class="parallel-control"><input type="checkbox" id="parallel-hosts" checked> Run hosts together</label>
|
|
2387
|
+
<button class="primary run-toggle" id="run-selection" data-start-label="Start run"${canRun ? "" : ' disabled data-fixed-disabled="1"'}>Start run</button>
|
|
2388
|
+
</div>
|
|
2389
|
+
${options.overviewHtml ?? ""}
|
|
1777
2390
|
<section class="run-progress" id="run-progress" aria-live="polite" hidden>
|
|
1778
2391
|
<div class="run-progress-head">
|
|
1779
2392
|
<p class="run-progress-title">0 of 0 tests finished</p>
|
|
@@ -1788,11 +2401,15 @@ ${viewerCss()}
|
|
|
1788
2401
|
<span class="progress-remaining">Remaining <strong>0</strong></span>
|
|
1789
2402
|
</div>
|
|
1790
2403
|
</section>
|
|
1791
|
-
|
|
2404
|
+
<div class="viewer-workspace">
|
|
2405
|
+
${navigator}
|
|
2406
|
+
<section class="test-stage" aria-label="Selected test">${suites}</section>
|
|
2407
|
+
</div>
|
|
1792
2408
|
</main>
|
|
1793
|
-
|
|
1794
|
-
<script>
|
|
1795
|
-
|
|
2409
|
+
<script type="application/json" id="catalog-data">${embedJson(catalog)}</script>
|
|
2410
|
+
<script type="application/json" id="bootstrap-data">${embedJson(bootstrap)}</script>
|
|
2411
|
+
<script type="text/javascript">
|
|
2412
|
+
${canRun ? clientScript() : staticNavigationScript()}
|
|
1796
2413
|
</script>
|
|
1797
2414
|
</body>
|
|
1798
2415
|
</html>
|