@matware/e2e-runner 1.1.1 → 1.3.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/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +9 -0
- package/.mcp.json +9 -0
- package/.opencode/commands/create-test.md +63 -0
- package/.opencode/commands/run.md +50 -0
- package/.opencode/commands/verify-issue.md +62 -0
- package/.opencode/skills/e2e-testing/SKILL.md +181 -0
- package/.opencode/skills/e2e-testing/references/action-types.md +143 -0
- package/.opencode/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/.opencode/skills/e2e-testing/references/graphql.md +59 -0
- package/.opencode/skills/e2e-testing/references/issue-verification.md +59 -0
- package/.opencode/skills/e2e-testing/references/multi-pool.md +60 -0
- package/.opencode/skills/e2e-testing/references/network-debugging.md +62 -0
- package/.opencode/skills/e2e-testing/references/test-json-format.md +163 -0
- package/.opencode/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/.opencode/skills/e2e-testing/references/variables.md +41 -0
- package/.opencode/skills/e2e-testing/references/visual-verification.md +89 -0
- package/OPENCODE.md +166 -0
- package/README.md +990 -296
- package/agents/test-analyzer.md +81 -0
- package/agents/test-creator.md +155 -0
- package/agents/test-improver.md +177 -0
- package/bin/cli.js +602 -22
- package/commands/create-test.md +65 -0
- package/commands/run.md +49 -0
- package/commands/verify-issue.md +63 -0
- package/opencode.json +11 -0
- package/package.json +15 -2
- package/scripts/setup-opencode.sh +113 -0
- package/skills/e2e-testing/SKILL.md +173 -0
- package/skills/e2e-testing/references/action-types.md +143 -0
- package/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/skills/e2e-testing/references/graphql.md +59 -0
- package/skills/e2e-testing/references/issue-verification.md +59 -0
- package/skills/e2e-testing/references/multi-pool.md +60 -0
- package/skills/e2e-testing/references/network-debugging.md +62 -0
- package/skills/e2e-testing/references/test-json-format.md +163 -0
- package/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/skills/e2e-testing/references/variables.md +41 -0
- package/skills/e2e-testing/references/visual-verification.md +89 -0
- package/src/actions.js +597 -20
- package/src/ai-generate.js +142 -12
- package/src/config.js +171 -0
- package/src/dashboard.js +299 -17
- package/src/db.js +335 -13
- package/src/index.js +15 -8
- package/src/learner-markdown.js +177 -0
- package/src/learner-neo4j.js +255 -0
- package/src/learner-sqlite.js +658 -0
- package/src/learner.js +418 -0
- package/src/mcp-tools.js +1558 -50
- package/src/module-resolver.js +310 -0
- package/src/narrate.js +262 -0
- package/src/neo4j-pool.js +124 -0
- package/src/pool-manager.js +223 -0
- package/src/reporter.js +117 -3
- package/src/runner.js +274 -71
- package/src/sync/auth.js +354 -0
- package/src/sync/client.js +572 -0
- package/src/sync/hub-routes.js +816 -0
- package/src/sync/index.js +68 -0
- package/src/sync/middleware.js +347 -0
- package/src/sync/queue.js +209 -0
- package/src/sync/schema.js +540 -0
- package/src/verify.js +14 -9
- package/src/watch.js +384 -0
- package/templates/build-dashboard.js +69 -0
- package/templates/dashboard/js/api.js +60 -0
- package/templates/dashboard/js/init.js +13 -0
- package/templates/dashboard/js/keyboard.js +46 -0
- package/templates/dashboard/js/state.js +40 -0
- package/templates/dashboard/js/toast.js +41 -0
- package/templates/dashboard/js/utils.js +196 -0
- package/templates/dashboard/js/view-live.js +143 -0
- package/templates/dashboard/js/view-runs.js +572 -0
- package/templates/dashboard/js/view-tests.js +294 -0
- package/templates/dashboard/js/view-watch.js +242 -0
- package/templates/dashboard/js/websocket.js +110 -0
- package/templates/dashboard/styles/base.css +69 -0
- package/templates/dashboard/styles/components.css +110 -0
- package/templates/dashboard/styles/view-live.css +74 -0
- package/templates/dashboard/styles/view-runs.css +207 -0
- package/templates/dashboard/styles/view-tests.css +96 -0
- package/templates/dashboard/styles/view-watch.css +53 -0
- package/templates/dashboard/template.html +267 -0
- package/templates/dashboard.html +2171 -530
- package/templates/docker-compose-neo4j.yml +19 -0
- package/templates/e2e.config.js +3 -0
- package/templates/sample-test.json +0 -8
package/templates/dashboard.html
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
9
9
|
<style>
|
|
10
|
+
/* ── base.css ── */
|
|
11
|
+
/* ── Reset & Variables ── */
|
|
10
12
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
11
13
|
:root{
|
|
12
14
|
--bg:#090a10;--surface:#11131b;--surface2:#181b26;--surface3:#1f2333;
|
|
@@ -51,6 +53,24 @@ a{color:var(--accent);text-decoration:none}
|
|
|
51
53
|
.pool-info{font-size:11px;color:var(--text2);line-height:1.7}
|
|
52
54
|
.pool-info strong{color:var(--text)}
|
|
53
55
|
.ws-dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
56
|
+
.pool-list{margin-top:6px;display:flex;flex-direction:column;gap:3px}
|
|
57
|
+
.pool-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--text2);font-family:var(--mono);padding:2px 0}
|
|
58
|
+
.pool-item .pool-dot{width:6px;height:6px;margin-right:0}
|
|
59
|
+
.pool-item strong{color:var(--text);font-weight:500}
|
|
60
|
+
.pool-item .pool-sessions{margin-left:auto;color:var(--text3);font-size:9px}
|
|
61
|
+
|
|
62
|
+
/* ── Sync Status ── */
|
|
63
|
+
.sync-status{padding:12px 16px;border-top:1px solid var(--border)}
|
|
64
|
+
.sync-status .sync-header{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text2)}
|
|
65
|
+
.sync-status .sync-mode{font-size:9px;padding:2px 6px;border-radius:3px;text-transform:uppercase;font-weight:600}
|
|
66
|
+
.sync-status .sync-mode.hub{background:var(--purple-dim);color:var(--purple)}
|
|
67
|
+
.sync-status .sync-mode.agent{background:var(--accent-dim);color:var(--accent)}
|
|
68
|
+
.sync-status .sync-mode.standalone{background:var(--surface3);color:var(--text3)}
|
|
69
|
+
.sync-status .sync-details{margin-top:6px;font-size:10px;color:var(--text3)}
|
|
70
|
+
.sync-status .sync-instances{margin-top:6px;display:flex;flex-wrap:wrap;gap:4px}
|
|
71
|
+
.sync-status .sync-inst{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;border-radius:10px;font-size:9px;background:var(--surface3);border:1px solid var(--border)}
|
|
72
|
+
.sync-status .sync-inst.online{border-color:var(--green);color:var(--green)}
|
|
73
|
+
.sync-status .sync-inst.offline{color:var(--text3)}
|
|
54
74
|
|
|
55
75
|
/* ── Main ── */
|
|
56
76
|
.main{margin-left:232px;flex:1;min-height:100vh;display:flex;flex-direction:column}
|
|
@@ -61,6 +81,18 @@ a{color:var(--accent);text-decoration:none}
|
|
|
61
81
|
.view.active{display:block}
|
|
62
82
|
#view-live.active{display:flex;flex-direction:column;flex:1;min-height:calc(100vh - 0px);padding:16px}
|
|
63
83
|
|
|
84
|
+
/* ── Responsive ── */
|
|
85
|
+
@media(max-width:768px){
|
|
86
|
+
.sidebar{width:60px}.sidebar-logo h1,.sidebar-section-label,.nav-item span:not(.icon):not(.badge),.pool-info,.sidebar select,.sidebar-logo .ver{display:none}
|
|
87
|
+
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
88
|
+
.main{margin-left:60px}
|
|
89
|
+
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
90
|
+
.lr-test-grid{grid-template-columns:1fr}
|
|
91
|
+
.toast-container{right:12px;bottom:12px}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/* ── components.css ── */
|
|
64
96
|
/* ── Buttons ── */
|
|
65
97
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;white-space:nowrap}
|
|
66
98
|
.btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
@@ -92,12 +124,243 @@ td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
|
92
124
|
tbody tr{cursor:pointer;transition:background .1s}
|
|
93
125
|
tbody tr:hover td{background:var(--surface2)}
|
|
94
126
|
tbody tr.selected td{background:var(--accent-dim)}
|
|
127
|
+
|
|
128
|
+
/* ── Badges ── */
|
|
95
129
|
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
96
130
|
.badge.pass{background:var(--green-dim);color:var(--green)}
|
|
97
131
|
.badge.fail{background:var(--red-dim);color:var(--red)}
|
|
98
132
|
.badge.flaky{background:var(--amber-dim);color:var(--amber)}
|
|
99
133
|
.badge.run{background:var(--purple-dim);color:var(--purple)}
|
|
100
134
|
|
|
135
|
+
/* ── Empty ── */
|
|
136
|
+
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
137
|
+
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
138
|
+
|
|
139
|
+
/* ── Modal ── */
|
|
140
|
+
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
141
|
+
.modal.open{display:flex}
|
|
142
|
+
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
143
|
+
|
|
144
|
+
/* ── Toast Notifications ── */
|
|
145
|
+
.toast-container{position:fixed;bottom:24px;right:24px;z-index:300;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}
|
|
146
|
+
.toast{padding:10px 16px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;color:#fff;pointer-events:auto;animation:toastIn .3s ease;min-width:200px;max-width:380px;box-shadow:0 8px 24px rgba(0,0,0,.4);display:flex;align-items:center;gap:8px}
|
|
147
|
+
.toast.success{background:var(--green);border:1px solid rgba(255,255,255,.15)}
|
|
148
|
+
.toast.error{background:var(--red);border:1px solid rgba(255,255,255,.15)}
|
|
149
|
+
.toast.info{background:var(--accent);border:1px solid rgba(255,255,255,.15)}
|
|
150
|
+
.toast.fade-out{animation:toastOut .3s ease forwards}
|
|
151
|
+
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}
|
|
152
|
+
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(24px)}}
|
|
153
|
+
.toast.clickable{cursor:pointer}
|
|
154
|
+
.toast.clickable:hover{filter:brightness(1.1)}
|
|
155
|
+
|
|
156
|
+
/* ── Copy Button ── */
|
|
157
|
+
.copy-btn{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:4px;font-size:9px;font-family:var(--mono);font-weight:500;color:var(--text3);background:transparent;border:1px solid transparent;cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;flex-shrink:0}
|
|
158
|
+
.copy-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-dim)}
|
|
159
|
+
.copy-btn.copied{color:var(--green);border-color:var(--green);background:var(--green-dim)}
|
|
160
|
+
|
|
161
|
+
/* ── Screenshot Hash Badge ── */
|
|
162
|
+
.ss-hash{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:10px;font-family:var(--mono);font-size:9px;font-weight:500;background:var(--surface3);border:1px solid var(--border);color:var(--text2);cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;vertical-align:middle}
|
|
163
|
+
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
164
|
+
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
165
|
+
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
166
|
+
|
|
167
|
+
/* ── Trigger Source Badges ── */
|
|
168
|
+
.trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
169
|
+
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
170
|
+
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
171
|
+
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
172
|
+
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
173
|
+
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
174
|
+
|
|
175
|
+
/* ── Filter Bar ── */
|
|
176
|
+
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
177
|
+
.filter-btn{padding:5px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text2);font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .15s}
|
|
178
|
+
.filter-btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
179
|
+
.filter-btn.active{background:var(--accent-dim);border-color:var(--accent);color:var(--accent)}
|
|
180
|
+
.filter-bar input{padding:5px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px;max-width:200px}
|
|
181
|
+
.filter-bar input:focus{outline:none;border-color:var(--accent)}
|
|
182
|
+
.filter-bar input::placeholder{color:var(--text3)}
|
|
183
|
+
|
|
184
|
+
/* ── Inner Tabs ── */
|
|
185
|
+
.tab-bar{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px}
|
|
186
|
+
.tab-btn{padding:8px 16px;font-family:var(--mono);font-size:11px;font-weight:500;color:var(--text3);cursor:pointer;border:none;background:transparent;border-bottom:2px solid transparent;transition:all .15s}
|
|
187
|
+
.tab-btn:hover{color:var(--text2);background:var(--surface2)}
|
|
188
|
+
.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
189
|
+
.tab-pane{display:none}
|
|
190
|
+
.tab-pane.active{display:block}
|
|
191
|
+
|
|
192
|
+
/* ── Keyboard Shortcuts Modal ── */
|
|
193
|
+
.kb-modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:250;display:none;align-items:center;justify-content:center;padding:24px}
|
|
194
|
+
.kb-modal.open{display:flex}
|
|
195
|
+
.kb-modal-content{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:24px;max-width:420px;width:100%;max-height:80vh;overflow-y:auto}
|
|
196
|
+
.kb-modal-content h2{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
|
197
|
+
.kb-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
198
|
+
.kb-row:last-child{border-bottom:none}
|
|
199
|
+
.kb-key{display:inline-flex;align-items:center;justify-content:center;min-width:24px;padding:2px 8px;border-radius:4px;background:var(--surface3);border:1px solid var(--border);font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent)}
|
|
200
|
+
.kb-desc{font-size:12px;color:var(--text2)}
|
|
201
|
+
|
|
202
|
+
/* ── Serial / Pool Badges ── */
|
|
203
|
+
.serial-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:8px;font-size:9px;font-weight:600;background:var(--amber-dim);color:var(--amber);vertical-align:middle;margin-left:4px}
|
|
204
|
+
.pool-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:8px;font-size:9px;font-weight:600;background:rgba(99,102,241,.15);color:#818cf8;vertical-align:middle;margin-left:4px;font-family:var(--mono);letter-spacing:-.3px}
|
|
205
|
+
.pool-badge::before{content:'\1F517';font-size:8px}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
/* ── view-watch.css ── */
|
|
209
|
+
/* ── Watch View: Project Cards ── */
|
|
210
|
+
.watch-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-bottom:24px}
|
|
211
|
+
.watch-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;transition:border-color .2s,box-shadow .2s}
|
|
212
|
+
.watch-card:hover{border-color:var(--border-hi);box-shadow:0 2px 12px rgba(0,0,0,.25)}
|
|
213
|
+
.watch-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
|
214
|
+
.watch-card-name{font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
215
|
+
.watch-card-icons{display:flex;gap:6px;flex-shrink:0;margin-left:8px}
|
|
216
|
+
.watch-card-icons .btn{padding:3px 8px;font-size:10px}
|
|
217
|
+
|
|
218
|
+
/* ── Sparkline ── */
|
|
219
|
+
.watch-sparkline{height:40px;margin-bottom:10px}
|
|
220
|
+
.watch-sparkline svg{width:100%;height:100%;display:block}
|
|
221
|
+
|
|
222
|
+
/* ── Card Footer ── */
|
|
223
|
+
.watch-card-footer{display:flex;align-items:center;justify-content:space-between;font-size:11px}
|
|
224
|
+
.watch-card-status{display:flex;align-items:center;gap:6px}
|
|
225
|
+
.watch-card-status .status-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
226
|
+
.watch-card-status .status-dot.green{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
227
|
+
.watch-card-status .status-dot.red{background:var(--red);box-shadow:0 0 6px var(--red)}
|
|
228
|
+
.watch-card-status .status-dot.amber{background:var(--amber);box-shadow:0 0 6px var(--amber)}
|
|
229
|
+
.watch-card-status .status-dot.dim{background:var(--text3)}
|
|
230
|
+
.watch-card-rate{font-weight:600}
|
|
231
|
+
.watch-card-rate.green{color:var(--green)}
|
|
232
|
+
.watch-card-rate.amber{color:var(--amber)}
|
|
233
|
+
.watch-card-rate.red{color:var(--red)}
|
|
234
|
+
|
|
235
|
+
.watch-card-meta{display:flex;flex-direction:column;gap:4px;margin-top:10px;font-size:10px;color:var(--text3)}
|
|
236
|
+
.watch-card-meta span{display:flex;align-items:center;gap:6px}
|
|
237
|
+
.watch-card-countdown{color:var(--accent);font-weight:500;font-variant-numeric:tabular-nums}
|
|
238
|
+
.watch-card-commit{font-family:var(--mono);color:var(--text3)}
|
|
239
|
+
|
|
240
|
+
/* ── Event Log ── */
|
|
241
|
+
.watch-event-log{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden}
|
|
242
|
+
.watch-event-log-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
243
|
+
.watch-event-log-header .title{font-family:var(--sans);font-size:13px;font-weight:600}
|
|
244
|
+
.watch-event-log-body{max-height:400px;overflow-y:auto}
|
|
245
|
+
.watch-event-row{display:grid;grid-template-columns:120px 130px minmax(80px,1fr) 46px 74px 40px 52px 60px;align-items:center;gap:0;padding:7px 16px;border-bottom:1px solid var(--border);font-size:11px;transition:background .1s}
|
|
246
|
+
.watch-event-row:last-child{border-bottom:none}
|
|
247
|
+
.watch-event-row:hover{background:var(--surface2)}
|
|
248
|
+
.watch-event-row.we-header{font-size:9px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.5px;padding:6px 16px;background:var(--surface2);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:1}
|
|
249
|
+
.watch-event-row.we-header:hover{background:var(--surface2)}
|
|
250
|
+
.watch-event-time{color:var(--text3);font-variant-numeric:tabular-nums;font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
251
|
+
.watch-event-project{color:var(--text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
252
|
+
.watch-event-suite{color:var(--accent);font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
253
|
+
.watch-event-result{justify-self:center}
|
|
254
|
+
.watch-event-counts{font-family:var(--mono);font-size:10px;color:var(--text2);white-space:nowrap;text-align:center}
|
|
255
|
+
.watch-event-counts .we-counts-ok{color:var(--green)}
|
|
256
|
+
.watch-event-rate{font-weight:600;color:var(--text2);font-variant-numeric:tabular-nums;text-align:right}
|
|
257
|
+
.watch-event-duration{color:var(--text3);font-family:var(--mono);font-size:10px;text-align:right}
|
|
258
|
+
.we-trigger{color:var(--text3);font-size:9px;padding:1px 6px;border-radius:8px;background:var(--surface3);white-space:nowrap;text-align:center;justify-self:end}
|
|
259
|
+
|
|
260
|
+
/* ── Watch Table (legacy) ── */
|
|
261
|
+
.watch-jobs-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
/* ── view-tests.css ── */
|
|
265
|
+
/* ── Suite Cards ── */
|
|
266
|
+
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;padding:16px}
|
|
267
|
+
.suite-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;transition:border-color .2s,box-shadow .2s}
|
|
268
|
+
.suite-card:hover{border-color:var(--border-hi);box-shadow:0 2px 12px rgba(0,0,0,.25)}
|
|
269
|
+
.suite-card-head{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
270
|
+
.suite-card-icon{width:32px;height:32px;border-radius:6px;background:var(--accent-dim);display:flex;align-items:center;justify-content:center;font-size:14px;color:var(--accent);flex-shrink:0}
|
|
271
|
+
.suite-card-info{flex:1;min-width:0}
|
|
272
|
+
.suite-card-name{font-family:var(--sans);font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
273
|
+
.suite-card-file{font-size:10px;color:var(--text3);margin-top:1px;font-family:var(--mono)}
|
|
274
|
+
.suite-card-count{flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:40px;height:40px;border-radius:var(--r);background:var(--surface3);border:1px solid var(--border)}
|
|
275
|
+
.suite-card-count-num{font-size:16px;font-weight:700;color:var(--text);line-height:1}
|
|
276
|
+
.suite-card-count-lbl{font-size:8px;color:var(--text3);text-transform:uppercase;letter-spacing:.06em;line-height:1;margin-top:2px}
|
|
277
|
+
.suite-card-body{padding:0}
|
|
278
|
+
.suite-card-tests{list-style:none;max-height:180px;overflow-y:auto}
|
|
279
|
+
.suite-card-tests::-webkit-scrollbar{width:4px}
|
|
280
|
+
.suite-card-tests::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
|
|
281
|
+
.suite-card-tests li{font-size:11px;color:var(--text2);padding:6px 16px 6px 30px;position:relative;cursor:pointer;transition:all .15s;border-bottom:1px solid rgba(35,39,56,.5)}
|
|
282
|
+
.suite-card-tests li:last-child{border-bottom:none}
|
|
283
|
+
.suite-card-tests li:hover{color:var(--text);background:var(--surface2)}
|
|
284
|
+
.suite-card-tests li::before{content:"\25B8";position:absolute;left:14px;color:var(--text3);font-size:9px;top:7px;transition:transform .15s}
|
|
285
|
+
.suite-card-tests li.expanded{color:var(--accent);background:var(--accent-dim)}
|
|
286
|
+
.suite-card-tests li.expanded::before{content:"\25BE";color:var(--accent)}
|
|
287
|
+
.suite-test-steps{padding:8px 0 8px 14px;border-left:2px solid var(--accent-dim);margin:6px 0 4px 6px}
|
|
288
|
+
.suite-card-footer{padding:10px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;background:var(--surface)}
|
|
289
|
+
|
|
290
|
+
/* ── Suite Detail Modal ── */
|
|
291
|
+
.suite-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:200;display:none;align-items:flex-start;justify-content:center;padding:32px 24px;overflow-y:auto;backdrop-filter:blur(4px)}
|
|
292
|
+
.suite-modal-overlay.open{display:flex}
|
|
293
|
+
.suite-modal{width:100%;max-width:960px;background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden;animation:suiteModalIn .2s ease}
|
|
294
|
+
@keyframes suiteModalIn{from{opacity:0;transform:translateY(-12px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
295
|
+
.suite-modal-header{display:flex;align-items:center;gap:14px;padding:18px 24px;background:var(--surface2);border-bottom:1px solid var(--border)}
|
|
296
|
+
.suite-modal-header .suite-card-icon{width:36px;height:36px;font-size:16px}
|
|
297
|
+
.suite-modal-title{flex:1;min-width:0}
|
|
298
|
+
.suite-modal-title h2{font-family:var(--sans);font-size:16px;font-weight:700;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
299
|
+
.suite-modal-title span{font-size:10px;color:var(--text3);font-family:var(--mono)}
|
|
300
|
+
.suite-modal-actions{display:flex;gap:8px;align-items:center;flex-shrink:0}
|
|
301
|
+
.suite-modal-close{width:32px;height:32px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface3);color:var(--text2);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;line-height:1}
|
|
302
|
+
.suite-modal-close:hover{background:var(--red-dim);border-color:rgba(239,68,68,.3);color:var(--red)}
|
|
303
|
+
.suite-modal-body{padding:0}
|
|
304
|
+
.suite-modal-loading{padding:32px;text-align:center;color:var(--text3);font-size:12px}
|
|
305
|
+
.suite-modal-test{border-bottom:1px solid var(--border)}
|
|
306
|
+
.suite-modal-test:last-child{border-bottom:none}
|
|
307
|
+
.suite-modal-test-header{display:flex;align-items:center;gap:10px;padding:12px 24px;cursor:pointer;transition:background .15s;user-select:none}
|
|
308
|
+
.suite-modal-test-header:hover{background:var(--surface2)}
|
|
309
|
+
.suite-modal-test.open>.suite-modal-test-header{background:var(--surface2)}
|
|
310
|
+
.suite-modal-test-chevron{font-size:9px;color:var(--text3);transition:transform .15s;width:14px;text-align:center;flex-shrink:0}
|
|
311
|
+
.suite-modal-test.open>.suite-modal-test-header .suite-modal-test-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
312
|
+
.suite-modal-test-name{font-size:12px;font-weight:500;color:var(--text);flex:1}
|
|
313
|
+
.suite-modal-test-badge{font-size:9px;padding:2px 7px;border-radius:8px;background:var(--surface3);color:var(--text3);flex-shrink:0}
|
|
314
|
+
.suite-modal-test-actions{display:none;padding:0 24px 16px 48px}
|
|
315
|
+
.suite-modal-test.open>.suite-modal-test-actions{display:block}
|
|
316
|
+
.suite-modal-step{display:flex;align-items:flex-start;gap:8px;padding:5px 0;font-size:11px;font-family:var(--mono);border-bottom:1px solid rgba(35,39,56,.3)}
|
|
317
|
+
.suite-modal-step:last-child{border-bottom:none}
|
|
318
|
+
.suite-modal-step-num{width:22px;text-align:right;color:var(--text3);flex-shrink:0;font-size:10px;padding-top:1px}
|
|
319
|
+
.suite-modal-step-type{color:var(--purple);font-weight:600;flex-shrink:0;min-width:120px}
|
|
320
|
+
.suite-modal-step-detail{color:var(--text2);flex:1;min-width:0;word-break:break-word}
|
|
321
|
+
.suite-modal-step-detail .step-sel{color:var(--accent)}
|
|
322
|
+
.suite-modal-step-detail .step-arrow{color:var(--text3);margin:0 4px}
|
|
323
|
+
.suite-modal-step-detail .step-val{color:var(--text)}
|
|
324
|
+
.suite-modal-expect{padding:10px 24px 10px 48px;background:var(--green-dim);border-bottom:1px solid var(--border);font-size:11px;color:var(--green);display:flex;align-items:flex-start;gap:8px}
|
|
325
|
+
.suite-modal-expect-label{font-weight:600;flex-shrink:0}
|
|
326
|
+
|
|
327
|
+
/* ── Project Accordion ── */
|
|
328
|
+
.project-accordion{margin-bottom:2px}
|
|
329
|
+
.project-accordion-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);cursor:pointer;transition:all .15s;user-select:none}
|
|
330
|
+
.project-accordion-header:hover{background:var(--surface2);border-color:var(--border-hi)}
|
|
331
|
+
.project-accordion.open>.project-accordion-header{border-radius:var(--r) var(--r) 0 0;border-bottom-color:transparent;background:var(--surface2)}
|
|
332
|
+
.project-accordion-chevron{font-size:10px;color:var(--text3);transition:transform .2s ease;flex-shrink:0;width:16px;text-align:center}
|
|
333
|
+
.project-accordion.open>.project-accordion-header .project-accordion-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
334
|
+
.project-accordion-name{font-family:var(--sans);font-size:13px;font-weight:600;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
335
|
+
.project-accordion-meta{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
336
|
+
.project-accordion-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--surface3);color:var(--text2)}
|
|
337
|
+
.project-accordion-body{overflow:hidden;max-height:0;transition:max-height .3s ease;border:1px solid var(--border);border-top:none;border-radius:0 0 var(--r) var(--r);background:var(--bg)}
|
|
338
|
+
.project-accordion.open>.project-accordion-body{max-height:5000px}
|
|
339
|
+
|
|
340
|
+
/* ── Module Cards ── */
|
|
341
|
+
.module-section-title{font-family:var(--sans);font-size:14px;font-weight:600;margin:24px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);color:var(--text2);display:flex;align-items:center;gap:8px}
|
|
342
|
+
.module-section-title .mod-icon{color:var(--purple)}
|
|
343
|
+
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
|
344
|
+
.module-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;border-left:3px solid var(--purple);transition:border-color .15s}
|
|
345
|
+
.module-card:hover{border-color:var(--border-hi);border-left-color:var(--purple)}
|
|
346
|
+
.module-card-name{font-weight:600;font-size:13px;color:var(--purple);margin-bottom:4px}
|
|
347
|
+
.module-card-desc{font-size:11px;color:var(--text2);margin-bottom:8px}
|
|
348
|
+
.module-card-meta{font-size:10px;color:var(--text3);display:flex;gap:12px}
|
|
349
|
+
.module-card-params{list-style:none;font-size:10px;color:var(--text2);margin-top:6px}
|
|
350
|
+
.module-card-params li{padding:2px 0}
|
|
351
|
+
.module-card-params li::before{content:'$';color:var(--purple);margin-right:4px}
|
|
352
|
+
|
|
353
|
+
/* ── Variables ── */
|
|
354
|
+
.var-table{width:100%;border-collapse:collapse;font-size:12px}
|
|
355
|
+
.var-table th{text-align:left;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
356
|
+
.var-table td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
357
|
+
.var-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:11px}
|
|
358
|
+
.var-add-form{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:16px}
|
|
359
|
+
.var-add-form input,.var-add-form select{padding:6px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
360
|
+
.var-add-form input:focus,.var-add-form select:focus{outline:none;border-color:var(--accent)}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
/* ── view-runs.css ── */
|
|
101
364
|
/* ── Trend Chart ── */
|
|
102
365
|
.chart{display:flex;align-items:flex-end;gap:3px;height:60px}
|
|
103
366
|
.chart-bar{flex:1;min-width:3px;max-width:16px;border-radius:2px 2px 0 0;cursor:pointer;position:relative;transition:opacity .15s}
|
|
@@ -105,17 +368,209 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
105
368
|
.chart-bar .tip{display:none;position:absolute;bottom:calc(100% + 4px);left:50%;transform:translateX(-50%);background:var(--surface3);border:1px solid var(--border);padding:4px 8px;border-radius:4px;font-size:10px;white-space:nowrap;z-index:10;pointer-events:none}
|
|
106
369
|
.chart-bar:hover .tip{display:block}
|
|
107
370
|
|
|
108
|
-
/* ──
|
|
109
|
-
.
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.
|
|
113
|
-
.
|
|
114
|
-
.
|
|
115
|
-
.
|
|
116
|
-
.
|
|
117
|
-
.
|
|
371
|
+
/* ── Inline Run Detail ── */
|
|
372
|
+
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--purple)}
|
|
373
|
+
.run-detail-row:hover td{background:transparent!important}
|
|
374
|
+
.rd-wrap{overflow:hidden;transition:max-height .4s cubic-bezier(.4,0,.2,1);max-height:0}
|
|
375
|
+
.rd-wrap.open{max-height:10000px}
|
|
376
|
+
.rd-inner{padding:20px 24px;background:var(--bg);position:relative}
|
|
377
|
+
.rd-inner::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--purple),var(--accent),transparent 80%)}
|
|
378
|
+
.rd-summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:1px;background:var(--border);border-radius:8px;overflow:hidden;margin-bottom:20px}
|
|
379
|
+
.rd-summary>div{background:var(--surface);padding:14px 16px;text-align:center;transition:background .15s}
|
|
380
|
+
.rd-summary>div:hover{background:var(--surface2)}
|
|
381
|
+
.rd-s-label{font-size:9px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
382
|
+
.rd-s-val{font-weight:700;font-size:18px;margin-top:4px}
|
|
383
|
+
.rd-test{margin-bottom:12px;background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden;transition:border-color .2s}
|
|
384
|
+
.rd-test:hover{border-color:var(--border-hi)}
|
|
385
|
+
.rd-test:last-child{margin-bottom:0}
|
|
386
|
+
.rd-test-head{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--surface2);border-bottom:1px solid var(--border);position:relative;padding-left:20px}
|
|
387
|
+
.rd-test-head::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px}
|
|
388
|
+
.rd-test.pass .rd-test-head::before{background:var(--green)}
|
|
389
|
+
.rd-test.fail .rd-test-head::before{background:var(--red)}
|
|
390
|
+
.rd-test.flaky .rd-test-head::before{background:var(--amber)}
|
|
391
|
+
.rd-test-name{font-family:var(--sans);font-weight:600;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
392
|
+
.rd-test-dur{font-size:11px;color:var(--text2);flex-shrink:0;font-variant-numeric:tabular-nums}
|
|
393
|
+
.rd-test-body{padding:16px}
|
|
394
|
+
.rd-retries{font-size:11px;color:var(--amber);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
395
|
+
.rd-error-msg{font-size:12px;color:var(--red);padding:10px 14px;background:var(--red-dim);border-radius:var(--r);margin-bottom:12px;word-break:break-all;border-left:3px solid var(--red);line-height:1.5;position:relative;padding-right:60px}
|
|
396
|
+
.rd-error-msg .copy-btn{position:absolute;top:8px;right:8px;opacity:0}
|
|
397
|
+
.rd-error-msg:hover .copy-btn{opacity:1}
|
|
398
|
+
.rd-shots{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
399
|
+
.rd-shot{width:140px;border-radius:6px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--surface2)}
|
|
400
|
+
.rd-shot:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.35)}
|
|
401
|
+
.rd-shot img{width:100%;height:84px;object-fit:cover;display:block}
|
|
402
|
+
.rd-shot .rd-shot-cap{font-size:9px;color:var(--text3);padding:5px 8px;background:var(--surface)}
|
|
403
|
+
.rd-shot .rd-shot-cap .cap-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
404
|
+
.rd-shot .rd-shot-cap .ss-hash{margin-top:3px}
|
|
405
|
+
.rd-shot.err-shot{border-color:rgba(239,68,68,.4)}
|
|
406
|
+
.rd-shot.err-shot .rd-shot-cap{color:var(--red)}
|
|
407
|
+
.rd-logs{margin-bottom:12px}
|
|
408
|
+
.rd-log-label{font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.1em;text-transform:uppercase;margin-bottom:6px;display:flex;align-items:center;gap:8px}
|
|
409
|
+
.rd-log-item{font-size:11px;padding:4px 10px;border-left:2px solid var(--border);margin-bottom:2px;color:var(--text2);word-break:break-all;line-height:1.5}
|
|
410
|
+
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
411
|
+
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
412
|
+
|
|
413
|
+
/* ── Network Panel ── */
|
|
414
|
+
.rd-net-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
415
|
+
.rd-net-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
416
|
+
.rd-net-head:hover{background:var(--surface3)}
|
|
417
|
+
.rd-net-head .net-arrow{font-size:9px;color:var(--text3);transition:transform .2s}
|
|
418
|
+
.rd-net-head.open .net-arrow{transform:rotate(90deg)}
|
|
419
|
+
.rd-net-head .net-title{font-family:var(--sans);font-size:12px;font-weight:600;color:var(--text)}
|
|
420
|
+
.rd-net-head .net-stats{margin-left:auto;display:flex;gap:12px;font-size:10px}
|
|
421
|
+
.rd-net-head .net-stat{color:var(--text3)}
|
|
422
|
+
.rd-net-head .net-stat strong{color:var(--text2)}
|
|
423
|
+
.rd-net-head .net-stat.has-err strong{color:var(--red)}
|
|
424
|
+
.rd-net-body{display:none;max-height:600px;overflow-y:auto}
|
|
425
|
+
.rd-net-head.open~.rd-net-body{display:block}
|
|
426
|
+
.rd-net-cols{display:flex;align-items:center;gap:8px;padding:6px 14px;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:1}
|
|
427
|
+
.rd-net-cols .col-e{width:16px;flex-shrink:0}
|
|
428
|
+
.rd-net-cols .col-m{width:50px;flex-shrink:0}
|
|
429
|
+
.rd-net-cols .col-s{width:60px;flex-shrink:0}
|
|
430
|
+
.rd-net-cols .col-u{flex:1;min-width:0}
|
|
431
|
+
.rd-net-cols .col-d{width:60px;flex-shrink:0;text-align:right}
|
|
432
|
+
.rd-net-row{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:11px;font-family:var(--mono);border-bottom:1px solid var(--border);cursor:pointer;transition:background .1s}
|
|
433
|
+
.rd-net-row:hover{background:var(--surface2)}
|
|
434
|
+
.rd-net-row.has-error{background:rgba(239,68,68,.03)}
|
|
435
|
+
.rd-net-row.has-error:hover{background:rgba(239,68,68,.06)}
|
|
436
|
+
.rd-net-expand{width:16px;flex-shrink:0;font-size:8px;color:var(--text3);transition:transform .2s;text-align:center}
|
|
437
|
+
.rd-net-row.open .rd-net-expand{transform:rotate(90deg);color:var(--accent)}
|
|
438
|
+
.rd-net-method{width:50px;flex-shrink:0;display:inline-flex;justify-content:center;padding:2px 6px;border-radius:3px;font-size:9px;font-weight:700}
|
|
439
|
+
.rd-net-method.get{background:var(--accent-dim);color:var(--accent)}
|
|
440
|
+
.rd-net-method.post{background:var(--green-dim);color:var(--green)}
|
|
441
|
+
.rd-net-method.put,.rd-net-method.patch{background:var(--amber-dim);color:var(--amber)}
|
|
442
|
+
.rd-net-method.delete{background:var(--red-dim);color:var(--red)}
|
|
443
|
+
.rd-net-status{width:60px;flex-shrink:0;font-size:10px;font-weight:600}
|
|
444
|
+
.rd-net-status.s2xx{color:var(--green)}
|
|
445
|
+
.rd-net-status.s3xx{color:var(--amber)}
|
|
446
|
+
.rd-net-status.s4xx,.rd-net-status.s5xx{color:var(--red)}
|
|
447
|
+
.rd-net-op{flex-shrink:0;padding:1px 7px;border-radius:3px;font-size:9px;font-weight:600;background:var(--purple-dim,rgba(168,85,247,.12));color:var(--purple,#a855f7);font-family:var(--sans);letter-spacing:.01em;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
448
|
+
.rd-net-url{flex:1;min-width:0;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
449
|
+
.rd-net-dur{width:60px;flex-shrink:0;text-align:right;color:var(--text3);font-variant-numeric:tabular-nums}
|
|
450
|
+
.rd-net-detail{display:none;border-bottom:1px solid var(--border);background:var(--bg);overflow:hidden}
|
|
451
|
+
.rd-net-row.open+.rd-net-detail{display:block}
|
|
452
|
+
.rd-net-row .copy-btn{opacity:0;padding:1px 6px}
|
|
453
|
+
.rd-net-row:hover .copy-btn{opacity:1}
|
|
454
|
+
.rd-net-body .rd-log-item{padding:6px 14px;margin-bottom:0;border-left:3px solid var(--border);border-bottom:1px solid var(--border);font-size:11px}
|
|
455
|
+
.rd-net-body .rd-log-item:last-child{border-bottom:none}
|
|
456
|
+
.rd-net-body .rd-log-item.error{border-left-color:var(--red)}
|
|
457
|
+
.rd-net-body .rd-log-item.warn,.rd-net-body .rd-log-item.warning{border-left-color:var(--amber)}
|
|
458
|
+
|
|
459
|
+
/* ── Network Detail Sections ── */
|
|
460
|
+
.rd-nd-section{border-bottom:1px solid var(--border)}
|
|
461
|
+
.rd-nd-section:last-child{border-bottom:none}
|
|
462
|
+
.rd-nd-toggle{display:flex;align-items:center;gap:8px;padding:8px 16px;cursor:pointer;user-select:none;transition:background .15s;font-size:10px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.06em}
|
|
463
|
+
.rd-nd-toggle:hover{background:var(--surface2);color:var(--text2)}
|
|
464
|
+
.rd-nd-toggle .nd-arrow{font-size:8px;transition:transform .2s}
|
|
465
|
+
.rd-nd-toggle.open .nd-arrow{transform:rotate(90deg)}
|
|
466
|
+
.rd-nd-toggle .nd-count{margin-left:auto;font-size:9px;font-weight:400;letter-spacing:0;text-transform:none}
|
|
467
|
+
.rd-nd-content{display:none;padding:0 16px 10px;max-height:300px;overflow-y:auto}
|
|
468
|
+
.rd-nd-toggle.open+.rd-nd-content{display:block}
|
|
469
|
+
.rd-nd-content pre{white-space:pre-wrap;word-break:break-all;color:var(--text2);font-size:11px;line-height:1.6;margin:0;font-family:var(--mono)}
|
|
470
|
+
.rd-hdr-table{display:grid;grid-template-columns:minmax(120px,auto) 1fr;gap:0;font-size:11px}
|
|
471
|
+
.rd-hdr-row{display:contents}
|
|
472
|
+
.rd-hdr-row:hover .rd-hdr-key,.rd-hdr-row:hover .rd-hdr-val{background:var(--surface2)}
|
|
473
|
+
.rd-hdr-key{padding:3px 12px 3px 0;color:var(--accent);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
474
|
+
.rd-hdr-val{padding:3px 0;color:var(--text2);border-bottom:1px solid var(--border);word-break:break-all}
|
|
475
|
+
.rd-nd-empty{color:var(--text3);font-size:11px;padding:4px 0;font-style:italic}
|
|
476
|
+
|
|
477
|
+
tr.expanded td{background:var(--surface2)!important}
|
|
478
|
+
tr.expanded td:first-child{position:relative}
|
|
479
|
+
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
480
|
+
|
|
481
|
+
/* ── Actions Panel in Run Detail ── */
|
|
482
|
+
.rd-actions-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
483
|
+
.rd-actions-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
484
|
+
.rd-actions-head:hover{background:var(--surface3)}
|
|
485
|
+
.rd-actions-head .act-arrow{font-size:9px;color:var(--text3);transition:transform .2s}
|
|
486
|
+
.rd-actions-head.open .act-arrow{transform:rotate(90deg)}
|
|
487
|
+
.rd-actions-body{display:none;max-height:500px;overflow-y:auto;padding:8px 14px}
|
|
488
|
+
.rd-actions-head.open~.rd-actions-body{display:block}
|
|
489
|
+
|
|
490
|
+
/* ── Run Detail Insights ── */
|
|
491
|
+
.rd-insights{margin-bottom:16px;display:flex;flex-direction:column;gap:6px}
|
|
492
|
+
.rd-insights:empty{display:none}
|
|
493
|
+
.rd-ins-health{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:12px}
|
|
494
|
+
.rd-ins-rate{font-size:18px;font-weight:700}
|
|
495
|
+
.rd-ins-rate.green{color:var(--green)}
|
|
496
|
+
.rd-ins-rate.amber{color:var(--amber)}
|
|
497
|
+
.rd-ins-rate.red{color:var(--red)}
|
|
498
|
+
.rd-ins-trend{font-size:11px;color:var(--text2)}
|
|
499
|
+
.rd-ins-trend.green{color:var(--green)}
|
|
500
|
+
.rd-ins-trend.red{color:var(--red)}
|
|
501
|
+
.rd-ins-tag{padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
502
|
+
.rd-ins-tag.amber{background:var(--amber-dim);color:var(--amber)}
|
|
503
|
+
.rd-ins-tag.red{background:var(--red-dim);color:var(--red)}
|
|
504
|
+
.rd-ins-item{display:flex;align-items:flex-start;gap:8px;padding:6px 12px;border-radius:var(--r);font-size:11px;color:var(--text2);border-left:3px solid var(--border)}
|
|
505
|
+
.rd-ins-item.red{border-left-color:var(--red);background:var(--red-dim)}
|
|
506
|
+
.rd-ins-item.green{border-left-color:var(--green);background:var(--green-dim)}
|
|
507
|
+
.rd-ins-item.amber{border-left-color:var(--amber);background:var(--amber-dim)}
|
|
508
|
+
.rd-ins-icon{font-size:12px;flex-shrink:0;width:14px;text-align:center}
|
|
509
|
+
|
|
510
|
+
/* ── Health Banner ── */
|
|
511
|
+
.health-banner{display:flex;align-items:center;gap:1px;background:var(--border);border-radius:var(--r);overflow:hidden;margin-bottom:20px}
|
|
512
|
+
.health-banner:empty{display:none}
|
|
513
|
+
.hb-item{flex:1;background:var(--surface);padding:12px 16px;text-align:center;min-width:0;transition:background .15s}
|
|
514
|
+
.hb-item:hover{background:var(--surface2)}
|
|
515
|
+
.hb-val{font-size:20px;font-weight:700;margin-bottom:2px}
|
|
516
|
+
.hb-val.green{color:var(--green)}
|
|
517
|
+
.hb-val.amber{color:var(--amber)}
|
|
518
|
+
.hb-val.red{color:var(--red)}
|
|
519
|
+
.hb-val.accent{color:var(--accent)}
|
|
520
|
+
.hb-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
521
|
+
.hb-trend{font-size:10px;margin-top:2px}
|
|
522
|
+
.hb-trend.green{color:var(--green)}
|
|
523
|
+
.hb-trend.red{color:var(--red)}
|
|
524
|
+
.hb-trend.dim{color:var(--text3)}
|
|
525
|
+
.hb-link{background:var(--surface);padding:12px 16px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s;min-width:100px}
|
|
526
|
+
.hb-link:hover{background:var(--accent-dim)}
|
|
527
|
+
.hb-link span{font-size:11px;color:var(--accent);font-weight:600}
|
|
528
|
+
|
|
529
|
+
/* ── Screenshots ── */
|
|
530
|
+
.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
|
531
|
+
.gallery-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:border-color .15s}
|
|
532
|
+
.gallery-item:hover{border-color:var(--accent)}
|
|
533
|
+
.gallery-item img{width:100%;height:150px;object-fit:cover;display:block}
|
|
534
|
+
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
535
|
+
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
536
|
+
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
537
|
+
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
538
|
+
.ss-search input{flex:1;max-width:320px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
539
|
+
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
540
|
+
.ss-search input::placeholder{color:var(--text3)}
|
|
541
|
+
.ss-search button{padding:8px 16px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
542
|
+
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
543
|
+
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
544
|
+
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
545
|
+
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
546
|
+
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
118
547
|
|
|
548
|
+
/* ── Learnings ── */
|
|
549
|
+
.learn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;margin-bottom:20px}
|
|
550
|
+
.learn-stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px;text-align:center}
|
|
551
|
+
.learn-stat-val{font-size:24px;font-weight:700;margin-bottom:2px}
|
|
552
|
+
.learn-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
553
|
+
.learn-section{margin-bottom:20px}
|
|
554
|
+
.learn-section-title{font-family:var(--sans);font-size:13px;font-weight:600;margin-bottom:10px;color:var(--text)}
|
|
555
|
+
.learn-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
556
|
+
.learn-table th{text-align:left;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;padding:6px 10px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}
|
|
557
|
+
.learn-table th:hover{color:var(--text2)}
|
|
558
|
+
.learn-table th.sorted::after{content:' \25B2';font-size:8px}
|
|
559
|
+
.learn-table th.sorted.desc::after{content:' \25BC'}
|
|
560
|
+
.learn-table td{padding:6px 10px;border-bottom:1px solid var(--border);color:var(--text2)}
|
|
561
|
+
.learn-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:10px;color:var(--text)}
|
|
562
|
+
.learn-table tbody tr:hover td{background:var(--surface2);color:var(--text)}
|
|
563
|
+
.learn-trend-chart{width:100%;height:100px;margin-bottom:20px}
|
|
564
|
+
.learn-trend-chart svg{width:100%;height:100%}
|
|
565
|
+
|
|
566
|
+
/* ── Pool Distribution ── */
|
|
567
|
+
.pool-dist{display:flex;align-items:stretch;gap:0;border-radius:6px;overflow:hidden;height:22px;margin:8px 0;font-size:10px;font-weight:600;font-family:var(--mono)}
|
|
568
|
+
.pool-dist-seg{display:flex;align-items:center;justify-content:center;gap:4px;padding:0 8px;color:#fff;white-space:nowrap;min-width:40px;transition:flex .3s}
|
|
569
|
+
.pool-dist-legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin:4px 0 8px;font-size:10px;font-family:var(--mono);color:var(--text2)}
|
|
570
|
+
.pool-dist-legend span::before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;vertical-align:middle}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
/* ── view-live.css ── */
|
|
119
574
|
/* ── Live Execution ── */
|
|
120
575
|
.live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease;flex-direction:column}
|
|
121
576
|
.live-panel.active{display:flex;flex:1;min-height:0}
|
|
@@ -145,15 +600,18 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
145
600
|
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
146
601
|
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
147
602
|
.live-test .lt-icon{font-size:12px}
|
|
148
|
-
.lt-actions{
|
|
603
|
+
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
149
604
|
.lt-step{display:flex;align-items:flex-start;gap:6px;padding:2px 0;font-size:10px;font-family:var(--mono);line-height:1.4}
|
|
150
605
|
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
151
606
|
.lt-step .step-icon.ok{color:var(--green)}
|
|
152
607
|
.lt-step .step-icon.fail{color:var(--red)}
|
|
153
608
|
.lt-step .step-icon.run{color:var(--purple)}
|
|
154
609
|
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
155
|
-
.lt-step .step-detail{color:var(--text2);
|
|
610
|
+
.lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
|
|
156
611
|
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
612
|
+
.lt-step.pool-log{background:rgba(99,102,241,.06);border-left:2px solid rgba(99,102,241,.3);padding-left:8px}
|
|
613
|
+
.lt-step.pool-log .step-icon{color:#818cf8}
|
|
614
|
+
.lt-step.pool-log .step-type{color:#818cf8;font-weight:600}
|
|
157
615
|
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
158
616
|
.lt-screenshots-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;color:var(--text3);font-family:var(--mono);padding:2px 0;user-select:none}
|
|
159
617
|
.lt-screenshots-toggle:hover{color:var(--text)}
|
|
@@ -165,13 +623,15 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
165
623
|
.lt-ss-thumb img{width:100%;height:100%;object-fit:cover;display:block}
|
|
166
624
|
.lt-ss-thumb:hover{border-color:var(--purple);box-shadow:0 0 0 1px var(--purple)}
|
|
167
625
|
.lt-ss-label{font-size:8px;color:var(--text3);font-family:var(--mono);text-align:center;padding:2px 2px 0;display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap}
|
|
626
|
+
|
|
627
|
+
/* ── Live Run Sections ── */
|
|
168
628
|
.lr-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;margin:6px 0 2px;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;border-left:3px solid var(--purple)}
|
|
169
629
|
.lr-section-header.running{border-color:var(--purple);background:rgba(139,92,246,.08);color:var(--purple)}
|
|
170
630
|
.lr-section-header.pass{border-color:var(--green);background:rgba(52,211,153,.08);color:var(--green)}
|
|
171
631
|
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
172
632
|
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
173
633
|
.lr-project-name{letter-spacing:.5px}
|
|
174
|
-
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-
|
|
634
|
+
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
|
|
175
635
|
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
176
636
|
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
177
637
|
.live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
|
|
@@ -186,119 +646,6 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
186
646
|
.spinner-small{display:inline-block;width:8px;height:8px;border:1.5px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
187
647
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
188
648
|
|
|
189
|
-
/* ── Screenshots ── */
|
|
190
|
-
.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
|
191
|
-
.gallery-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:border-color .15s}
|
|
192
|
-
.gallery-item:hover{border-color:var(--accent)}
|
|
193
|
-
.gallery-item img{width:100%;height:150px;object-fit:cover;display:block}
|
|
194
|
-
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
195
|
-
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
196
|
-
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
197
|
-
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
198
|
-
.ss-search input{flex:1;max-width:320px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
199
|
-
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
200
|
-
.ss-search input::placeholder{color:var(--text3)}
|
|
201
|
-
.ss-search button{padding:8px 16px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
202
|
-
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
203
|
-
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
204
|
-
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
205
|
-
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
206
|
-
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
207
|
-
|
|
208
|
-
/* ── Inline Run Detail ── */
|
|
209
|
-
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--border-hi)}
|
|
210
|
-
.run-detail-row:hover td{background:transparent!important}
|
|
211
|
-
.rd-wrap{overflow:hidden;transition:max-height .35s cubic-bezier(.4,0,.2,1);max-height:0}
|
|
212
|
-
.rd-wrap.open{max-height:2000px}
|
|
213
|
-
.rd-inner{padding:16px 20px;background:var(--bg);border-left:3px solid var(--purple);margin:0 8px 8px}
|
|
214
|
-
.rd-test{margin-bottom:14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden}
|
|
215
|
-
.rd-test:last-child{margin-bottom:0}
|
|
216
|
-
.rd-test-head{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
217
|
-
.rd-test-name{font-weight:600;font-size:12px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
218
|
-
.rd-test-dur{font-size:11px;color:var(--text2);flex-shrink:0}
|
|
219
|
-
.rd-test-body{padding:10px 14px}
|
|
220
|
-
.rd-error-msg{font-size:11px;color:var(--red);padding:8px 12px;background:var(--red-dim);border-radius:var(--r);margin-bottom:10px;word-break:break-all;border-left:3px solid var(--red)}
|
|
221
|
-
.rd-shots{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
|
|
222
|
-
.rd-shot{width:120px;border-radius:4px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:border-color .15s,transform .15s}
|
|
223
|
-
.rd-shot:hover{border-color:var(--accent);transform:scale(1.03)}
|
|
224
|
-
.rd-shot img{width:100%;height:72px;object-fit:cover;display:block}
|
|
225
|
-
.rd-shot .rd-shot-cap{font-size:9px;color:var(--text3);padding:4px 6px;background:var(--surface2)}
|
|
226
|
-
.rd-shot .rd-shot-cap .cap-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
227
|
-
.rd-shot .rd-shot-cap .ss-hash{margin-top:3px}
|
|
228
|
-
.rd-shot.err-shot{border-color:rgba(239,68,68,.4)}
|
|
229
|
-
.rd-shot.err-shot .rd-shot-cap{color:var(--red)}
|
|
230
|
-
.rd-logs{margin-top:10px}
|
|
231
|
-
.rd-log-label{font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;margin-bottom:4px}
|
|
232
|
-
.rd-log-item{font-size:10px;padding:3px 8px;border-left:2px solid var(--border);margin-bottom:2px;color:var(--text2);word-break:break-all}
|
|
233
|
-
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
234
|
-
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
235
|
-
.rd-net-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;color:var(--text3);font-family:var(--mono);padding:2px 0;user-select:none;margin-top:10px}
|
|
236
|
-
.rd-net-toggle:hover{color:var(--text)}
|
|
237
|
-
.rd-net-toggle .net-arrow{transition:transform .2s;font-size:8px}
|
|
238
|
-
.rd-net-toggle.open .net-arrow{transform:rotate(90deg)}
|
|
239
|
-
.rd-net-list{display:none;margin-top:6px}
|
|
240
|
-
.rd-net-toggle.open+.rd-net-list{display:block}
|
|
241
|
-
.rd-net-row{display:flex;align-items:center;gap:8px;padding:3px 8px;font-size:10px;font-family:var(--mono);border-left:2px solid var(--border);margin-bottom:0;cursor:pointer;transition:background .15s}
|
|
242
|
-
.rd-net-row:hover{background:var(--surface2)}
|
|
243
|
-
.rd-net-row.has-error{border-left-color:var(--red)}
|
|
244
|
-
.rd-net-method{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:700;min-width:36px;text-align:center}
|
|
245
|
-
.rd-net-method.get{background:var(--accent-dim);color:var(--accent)}
|
|
246
|
-
.rd-net-method.post{background:var(--green-dim);color:var(--green)}
|
|
247
|
-
.rd-net-method.put,.rd-net-method.patch{background:var(--amber-dim);color:var(--amber)}
|
|
248
|
-
.rd-net-method.delete{background:var(--red-dim);color:var(--red)}
|
|
249
|
-
.rd-net-status{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:600;min-width:28px;text-align:center}
|
|
250
|
-
.rd-net-status.s2xx{background:var(--green-dim);color:var(--green)}
|
|
251
|
-
.rd-net-status.s3xx{background:var(--amber-dim);color:var(--amber)}
|
|
252
|
-
.rd-net-status.s4xx,.rd-net-status.s5xx{background:var(--red-dim);color:var(--red)}
|
|
253
|
-
.rd-net-url{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
254
|
-
.rd-net-dur{color:var(--text3);flex-shrink:0}
|
|
255
|
-
.rd-net-expand{font-size:8px;color:var(--text3);transition:transform .2s;flex-shrink:0}
|
|
256
|
-
.rd-net-row.open .rd-net-expand{transform:rotate(90deg)}
|
|
257
|
-
.rd-net-detail{display:none;margin:0 0 4px 0;padding:8px 12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:10px;font-family:var(--mono);overflow:hidden}
|
|
258
|
-
.rd-net-row.open+.rd-net-detail{display:block}
|
|
259
|
-
.rd-net-detail-section{margin-bottom:8px}
|
|
260
|
-
.rd-net-detail-section:last-child{margin-bottom:0}
|
|
261
|
-
.rd-net-detail-title{font-size:9px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin-bottom:4px}
|
|
262
|
-
.rd-net-detail-body{white-space:pre-wrap;word-break:break-all;color:var(--text2);max-height:300px;overflow-y:auto;line-height:1.5}
|
|
263
|
-
.rd-retries{font-size:10px;color:var(--amber);margin-bottom:6px}
|
|
264
|
-
.rd-summary{display:flex;gap:16px;margin-bottom:14px;padding:10px 14px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:11px;align-items:center}
|
|
265
|
-
.rd-summary .rd-s-label{color:var(--text3);font-size:9px;text-transform:uppercase;letter-spacing:.08em}
|
|
266
|
-
.rd-summary .rd-s-val{font-weight:700;font-size:16px;margin-top:2px}
|
|
267
|
-
tr.expanded td{background:var(--surface2)!important}
|
|
268
|
-
tr.expanded td:first-child{position:relative}
|
|
269
|
-
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
270
|
-
|
|
271
|
-
/* ── Empty ── */
|
|
272
|
-
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
273
|
-
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
274
|
-
|
|
275
|
-
/* ── Modal ── */
|
|
276
|
-
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
277
|
-
.modal.open{display:flex}
|
|
278
|
-
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
279
|
-
|
|
280
|
-
/* ── Screenshot Hash Badge ── */
|
|
281
|
-
.ss-hash{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:10px;font-family:var(--mono);font-size:9px;font-weight:500;background:var(--surface3);border:1px solid var(--border);color:var(--text2);cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;vertical-align:middle}
|
|
282
|
-
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
283
|
-
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
284
|
-
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
285
|
-
|
|
286
|
-
/* ── Trigger Source Badges ── */
|
|
287
|
-
.trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
288
|
-
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
289
|
-
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
290
|
-
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
291
|
-
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
292
|
-
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
293
|
-
|
|
294
|
-
/* ── Responsive ── */
|
|
295
|
-
@media(max-width:768px){
|
|
296
|
-
.sidebar{width:60px}.sidebar-logo h1,.sidebar-section-label,.nav-item span:not(.icon),.pool-info,.sidebar select,.sidebar-logo .ver{display:none}
|
|
297
|
-
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
298
|
-
.main{margin-left:60px}
|
|
299
|
-
.suite-grid,.gallery{grid-template-columns:1fr}
|
|
300
|
-
.lr-test-grid{grid-template-columns:1fr}
|
|
301
|
-
}
|
|
302
649
|
</style>
|
|
303
650
|
</head>
|
|
304
651
|
<body>
|
|
@@ -319,17 +666,20 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
319
666
|
<div class="sidebar-section">
|
|
320
667
|
<div class="sidebar-section-label">Navigation</div>
|
|
321
668
|
</div>
|
|
322
|
-
<div class="nav-item" data-view="
|
|
323
|
-
<i class="icon"
|
|
669
|
+
<div class="nav-item active" data-view="watch">
|
|
670
|
+
<i class="icon">⏲</i><span>Watch</span>
|
|
324
671
|
</div>
|
|
325
|
-
<div class="nav-item
|
|
326
|
-
<i class="icon">▷</i><span>
|
|
672
|
+
<div class="nav-item" data-view="tests">
|
|
673
|
+
<i class="icon">▷</i><span>Tests</span><span class="badge" id="badgeSuites">-</span>
|
|
327
674
|
</div>
|
|
328
675
|
<div class="nav-item" data-view="runs">
|
|
329
|
-
<i class="icon">☰</i><span>Runs</span>
|
|
676
|
+
<i class="icon">☰</i><span>Runs</span><span class="badge" id="badgeRuns">-</span>
|
|
330
677
|
</div>
|
|
331
|
-
<div class="nav-item" data-view="
|
|
332
|
-
<i class="icon"
|
|
678
|
+
<div class="nav-item" data-view="live" id="navLive" style="display:none">
|
|
679
|
+
<i class="icon"><span class="live-nav-dot"></span></i><span>Live</span><span class="badge" id="liveBadge" style="background:var(--purple-dim);color:var(--purple)">0</span>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="nav-item" data-view="instances" id="navInstances" style="display:none">
|
|
682
|
+
<i class="icon">◉</i><span>Instances</span><span class="badge" id="badgeInstances">-</span>
|
|
333
683
|
</div>
|
|
334
684
|
|
|
335
685
|
<div class="pool-status" id="poolStatus">
|
|
@@ -338,16 +688,214 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
338
688
|
<strong>Pool</strong> <span id="poolLabel">--</span>
|
|
339
689
|
</div>
|
|
340
690
|
<div class="pool-info">Sessions: <strong id="poolSessions">-/-</strong></div>
|
|
691
|
+
<div class="pool-list" id="poolList" style="display:none"></div>
|
|
341
692
|
<div class="pool-info" style="margin-top:6px">
|
|
342
693
|
<span class="ws-dot" id="wsDot" style="background:var(--red)"></span>
|
|
343
694
|
<span id="wsLabel" style="font-size:10px;color:var(--text3)">ws: connecting</span>
|
|
344
695
|
</div>
|
|
345
696
|
</div>
|
|
697
|
+
|
|
698
|
+
<!-- Sync Status -->
|
|
699
|
+
<div class="sync-status" id="syncStatus" style="display:none">
|
|
700
|
+
<div class="sync-header">
|
|
701
|
+
<span class="pool-dot" id="syncDot"></span>
|
|
702
|
+
<strong>Sync</strong>
|
|
703
|
+
<span class="sync-mode" id="syncMode">--</span>
|
|
704
|
+
</div>
|
|
705
|
+
<div class="sync-details" id="syncDetails"></div>
|
|
706
|
+
<div class="sync-instances" id="syncInstances"></div>
|
|
707
|
+
</div>
|
|
346
708
|
</aside>
|
|
347
709
|
|
|
348
710
|
<div class="main">
|
|
349
711
|
|
|
350
|
-
<!--
|
|
712
|
+
<!-- ════════════════ Watch View (default) ════════════════ -->
|
|
713
|
+
<div class="view active" id="view-watch">
|
|
714
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
715
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Watch</div>
|
|
716
|
+
</div>
|
|
717
|
+
<div class="watch-grid" id="watchCards"></div>
|
|
718
|
+
<div class="watch-event-log">
|
|
719
|
+
<div class="watch-event-log-header">
|
|
720
|
+
<span class="title">Recent Runs</span>
|
|
721
|
+
</div>
|
|
722
|
+
<div class="watch-event-log-body" id="watchEventLog"></div>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="empty" id="watchEmpty" style="display:none">
|
|
725
|
+
<div class="empty-icon">⏲</div>
|
|
726
|
+
<p>No projects registered yet. Run some tests to see project cards here.</p>
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
730
|
+
<!-- ════════════════ Tests View (inner tabs: Suites / Modules / Variables) ════════════════ -->
|
|
731
|
+
<div class="view" id="view-tests">
|
|
732
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
733
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Tests</div>
|
|
734
|
+
<div style="display:flex;gap:8px">
|
|
735
|
+
<button class="btn sm primary" id="btnRunAll">▷ Run All</button>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="tab-bar">
|
|
739
|
+
<button class="tab-btn active" data-tab="testsTabSuites">Suites</button>
|
|
740
|
+
<button class="tab-btn" data-tab="testsTabModules">Modules</button>
|
|
741
|
+
<button class="tab-btn" data-tab="testsTabVariables">Variables</button>
|
|
742
|
+
</div>
|
|
743
|
+
<!-- Suites tab -->
|
|
744
|
+
<div class="tab-pane active" id="testsTabSuites">
|
|
745
|
+
<div id="suiteAccordionContainer"></div>
|
|
746
|
+
<div class="suite-grid" id="suiteGrid"></div>
|
|
747
|
+
<div class="empty" id="suitesEmpty" style="display:none">
|
|
748
|
+
<div class="empty-icon">▷</div>
|
|
749
|
+
<p>No test suites found.</p>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
<!-- Modules tab -->
|
|
753
|
+
<div class="tab-pane" id="testsTabModules">
|
|
754
|
+
<div id="moduleSection"></div>
|
|
755
|
+
</div>
|
|
756
|
+
<!-- Variables tab -->
|
|
757
|
+
<div class="tab-pane" id="testsTabVariables">
|
|
758
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
759
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Variables</div>
|
|
760
|
+
<button class="btn sm primary" id="btnAddVar">+ Add Variable</button>
|
|
761
|
+
</div>
|
|
762
|
+
<div id="varAddForm" style="display:none"></div>
|
|
763
|
+
<div id="variablesContainer"></div>
|
|
764
|
+
<div class="empty" id="variablesEmpty" style="display:none">
|
|
765
|
+
<div class="empty-icon">⚙</div>
|
|
766
|
+
<p>No variables set. Add variables to use <code>{{var.KEY}}</code> in your tests.</p>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<!-- ════════════════ Runs View (inner tabs: History / Screenshots / Learnings) ════════════════ -->
|
|
772
|
+
<div class="view" id="view-runs">
|
|
773
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
774
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Runs</div>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="tab-bar">
|
|
777
|
+
<button class="tab-btn active" data-tab="runsTabHistory">History</button>
|
|
778
|
+
<button class="tab-btn" data-tab="runsTabScreenshots">Screenshots<span class="badge" id="badgeScreenshots" style="margin-left:6px">-</span></button>
|
|
779
|
+
<button class="tab-btn" data-tab="runsTabLearnings" id="runsTabLearnings">Learnings<span class="badge" id="badgeLearnings" style="margin-left:6px">-</span></button>
|
|
780
|
+
</div>
|
|
781
|
+
<!-- History tab -->
|
|
782
|
+
<div class="tab-pane active" id="runsTabHistory">
|
|
783
|
+
<div class="health-banner" id="runsHealthBanner"></div>
|
|
784
|
+
<div class="card">
|
|
785
|
+
<div class="card-label">Pass Rate Trend</div>
|
|
786
|
+
<div class="chart" id="trendChart"></div>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="filter-bar" id="filterBar">
|
|
789
|
+
<button class="filter-btn active" data-filter="all">All</button>
|
|
790
|
+
<button class="filter-btn" data-filter="pass">Pass</button>
|
|
791
|
+
<button class="filter-btn" data-filter="fail">Fail</button>
|
|
792
|
+
<button class="filter-btn" data-filter="mixed">Mixed</button>
|
|
793
|
+
<input type="text" id="runSearchInput" placeholder="Search suite..." spellcheck="false">
|
|
794
|
+
</div>
|
|
795
|
+
<div class="card" style="padding:0">
|
|
796
|
+
<div class="tbl-wrap">
|
|
797
|
+
<table>
|
|
798
|
+
<thead id="runsHead"><tr></tr></thead>
|
|
799
|
+
<tbody id="runsBody"></tbody>
|
|
800
|
+
</table>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
<div class="empty" id="runsEmpty" style="display:none">
|
|
804
|
+
<div class="empty-icon">☰</div>
|
|
805
|
+
<p>No runs recorded yet.</p>
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
<!-- Screenshots tab -->
|
|
809
|
+
<div class="tab-pane" id="runsTabScreenshots">
|
|
810
|
+
<div class="ss-search">
|
|
811
|
+
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
812
|
+
<button id="ssHashBtn">Search</button>
|
|
813
|
+
</div>
|
|
814
|
+
<div id="ssSearchResult"></div>
|
|
815
|
+
<div class="gallery" id="screenshotGallery"></div>
|
|
816
|
+
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
817
|
+
<div class="empty-icon">▣</div>
|
|
818
|
+
<p>Select a project to view screenshots.</p>
|
|
819
|
+
</div>
|
|
820
|
+
</div>
|
|
821
|
+
<!-- Learnings tab -->
|
|
822
|
+
<div class="tab-pane" id="runsTabLearnings">
|
|
823
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
824
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Learnings</div>
|
|
825
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
826
|
+
<select id="learningsDays" style="padding:5px 8px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px">
|
|
827
|
+
<option value="7">7 days</option>
|
|
828
|
+
<option value="14">14 days</option>
|
|
829
|
+
<option value="30" selected>30 days</option>
|
|
830
|
+
<option value="90">90 days</option>
|
|
831
|
+
</select>
|
|
832
|
+
<button class="btn sm" id="btnExportLearnings">Export MD</button>
|
|
833
|
+
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
<div id="learningsOverview"></div>
|
|
837
|
+
<div id="learningsTrend"></div>
|
|
838
|
+
<div id="learningsFlaky"></div>
|
|
839
|
+
<div id="learningsSelectors"></div>
|
|
840
|
+
<div id="learningsPages"></div>
|
|
841
|
+
<div id="learningsApis"></div>
|
|
842
|
+
<div id="learningsErrors"></div>
|
|
843
|
+
<div class="empty" id="learningsEmpty" style="display:none">
|
|
844
|
+
<div class="empty-icon">★</div>
|
|
845
|
+
<p>No learnings data yet. Run some tests to start building knowledge.</p>
|
|
846
|
+
</div>
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<!-- ════════════════ Instances View (Hub Mode) ════════════════ -->
|
|
851
|
+
<div class="view" id="view-instances">
|
|
852
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
853
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Connected Instances</div>
|
|
854
|
+
<div style="display:flex;gap:8px">
|
|
855
|
+
<select id="instanceFilter" style="padding:6px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px">
|
|
856
|
+
<option value="all">All Status</option>
|
|
857
|
+
<option value="active">Active</option>
|
|
858
|
+
<option value="pending">Pending</option>
|
|
859
|
+
<option value="suspended">Suspended</option>
|
|
860
|
+
</select>
|
|
861
|
+
<button class="btn sm" id="refreshInstances">Refresh</button>
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
|
|
865
|
+
<div class="stats" id="instanceStats">
|
|
866
|
+
<div class="stat-block"><div class="stat-val accent" id="statInstancesTotal">-</div><div class="stat-lbl">Total</div></div>
|
|
867
|
+
<div class="stat-block"><div class="stat-val green" id="statInstancesOnline">-</div><div class="stat-lbl">Online</div></div>
|
|
868
|
+
<div class="stat-block"><div class="stat-val purple" id="statInstancesActive">-</div><div class="stat-lbl">Active</div></div>
|
|
869
|
+
<div class="stat-block"><div class="stat-val" id="statInstancesPending" style="color:var(--amber)">-</div><div class="stat-lbl">Pending</div></div>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
<div class="card" style="padding:0">
|
|
873
|
+
<div class="tbl-wrap">
|
|
874
|
+
<table>
|
|
875
|
+
<thead>
|
|
876
|
+
<tr>
|
|
877
|
+
<th>Status</th>
|
|
878
|
+
<th>Instance ID</th>
|
|
879
|
+
<th>Display Name</th>
|
|
880
|
+
<th>Role</th>
|
|
881
|
+
<th>Environment</th>
|
|
882
|
+
<th>Last Seen</th>
|
|
883
|
+
<th>Actions</th>
|
|
884
|
+
</tr>
|
|
885
|
+
</thead>
|
|
886
|
+
<tbody id="instancesBody"></tbody>
|
|
887
|
+
</table>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
<div class="empty" id="instancesEmpty" style="display:none">
|
|
892
|
+
<div class="empty-icon">◉</div>
|
|
893
|
+
<p>No instances registered yet.</p>
|
|
894
|
+
<p style="margin-top:8px;font-size:11px;color:var(--text3)">Use <code>npx e2e-runner sync add-instance</code> to register agents.</p>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
|
|
898
|
+
<!-- ════════════════ Live View ════════════════ -->
|
|
351
899
|
<div class="view" id="view-live">
|
|
352
900
|
<div class="live-panel active" id="livePanel">
|
|
353
901
|
<div class="live-header">
|
|
@@ -370,67 +918,53 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
370
918
|
</div>
|
|
371
919
|
<div class="empty" id="liveEmpty">
|
|
372
920
|
<div class="empty-icon" style="font-size:48px;opacity:.3">●</div>
|
|
373
|
-
<p>No tests running. Start a test from the
|
|
921
|
+
<p>No tests running. Start a test from the Tests view or another console.</p>
|
|
374
922
|
<p style="margin-top:8px;font-size:11px;color:var(--text3)">This view activates automatically when tests are detected.</p>
|
|
375
923
|
</div>
|
|
376
924
|
</div>
|
|
377
925
|
|
|
378
|
-
|
|
379
|
-
<div class="view active" id="view-suites">
|
|
380
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
381
|
-
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Test Suites</div>
|
|
382
|
-
<button class="btn primary" id="btnRunAll">Run All Tests</button>
|
|
383
|
-
</div>
|
|
384
|
-
<div class="suite-grid" id="suiteGrid"></div>
|
|
385
|
-
<div class="empty" id="suitesEmpty" style="display:none">
|
|
386
|
-
<div class="empty-icon">▷</div>
|
|
387
|
-
<p>No test suites found.</p>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
926
|
+
</div>
|
|
390
927
|
|
|
391
|
-
|
|
392
|
-
<div class="
|
|
393
|
-
<div
|
|
394
|
-
|
|
395
|
-
<div class="
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
<div class="
|
|
400
|
-
<
|
|
401
|
-
|
|
402
|
-
<tbody id="runsBody"></tbody>
|
|
403
|
-
</table>
|
|
928
|
+
<div class="suite-modal-overlay" id="suiteModalOverlay">
|
|
929
|
+
<div class="suite-modal" id="suiteModal">
|
|
930
|
+
<div class="suite-modal-header">
|
|
931
|
+
<div class="suite-card-icon">▷</div>
|
|
932
|
+
<div class="suite-modal-title">
|
|
933
|
+
<h2 id="suiteModalName"></h2>
|
|
934
|
+
<span id="suiteModalFile"></span>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="suite-modal-actions">
|
|
937
|
+
<button class="btn sm primary" id="suiteModalRun">▷ Run</button>
|
|
938
|
+
<button class="suite-modal-close" id="suiteModalClose">×</button>
|
|
404
939
|
</div>
|
|
405
940
|
</div>
|
|
406
|
-
<div class="
|
|
407
|
-
<div class="
|
|
408
|
-
<p>No runs recorded yet.</p>
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
<!-- Screenshots View -->
|
|
413
|
-
<div class="view" id="view-screenshots">
|
|
414
|
-
<div style="font-family:var(--sans);font-size:16px;font-weight:600;margin-bottom:20px">Screenshots</div>
|
|
415
|
-
<div class="ss-search">
|
|
416
|
-
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
417
|
-
<button id="ssHashBtn">Search</button>
|
|
418
|
-
</div>
|
|
419
|
-
<div id="ssSearchResult"></div>
|
|
420
|
-
<div class="gallery" id="screenshotGallery"></div>
|
|
421
|
-
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
422
|
-
<div class="empty-icon">▣</div>
|
|
423
|
-
<p>Select a project to view screenshots.</p>
|
|
941
|
+
<div class="suite-modal-body" id="suiteModalBody">
|
|
942
|
+
<div class="suite-modal-loading">Loading...</div>
|
|
424
943
|
</div>
|
|
425
944
|
</div>
|
|
426
|
-
|
|
427
945
|
</div>
|
|
428
|
-
|
|
429
946
|
<div class="modal" id="modal"><img id="modalImg" src="" alt=""></div>
|
|
947
|
+
<div class="toast-container" id="toastContainer"></div>
|
|
948
|
+
<div class="kb-modal" id="kbModal">
|
|
949
|
+
<div class="kb-modal-content">
|
|
950
|
+
<h2>Keyboard Shortcuts</h2>
|
|
951
|
+
<div class="kb-row"><span class="kb-key">1</span><span class="kb-desc">Watch view</span></div>
|
|
952
|
+
<div class="kb-row"><span class="kb-key">2</span><span class="kb-desc">Tests view</span></div>
|
|
953
|
+
<div class="kb-row"><span class="kb-key">3</span><span class="kb-desc">Runs view</span></div>
|
|
954
|
+
<div class="kb-row"><span class="kb-key">4</span><span class="kb-desc">Live view</span></div>
|
|
955
|
+
<div class="kb-row"><span class="kb-key">j / k</span><span class="kb-desc">Navigate runs (next / previous)</span></div>
|
|
956
|
+
<div class="kb-row"><span class="kb-key">Enter</span><span class="kb-desc">Expand / collapse selected run</span></div>
|
|
957
|
+
<div class="kb-row"><span class="kb-key">Esc</span><span class="kb-desc">Close modal / collapse run</span></div>
|
|
958
|
+
<div class="kb-row"><span class="kb-key">r</span><span class="kb-desc">Refresh current view</span></div>
|
|
959
|
+
<div class="kb-row"><span class="kb-key">?</span><span class="kb-desc">Show this help</span></div>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
430
962
|
|
|
431
963
|
<script>
|
|
432
964
|
(function(){
|
|
433
965
|
'use strict';
|
|
966
|
+
/* ── utils.js ── */
|
|
967
|
+
/* ── DOM Helpers ── */
|
|
434
968
|
var $=function(s){return document.querySelector(s)};
|
|
435
969
|
var $$=function(s){return document.querySelectorAll(s)};
|
|
436
970
|
|
|
@@ -450,53 +984,106 @@ function css(n){return n.replace(/[^a-zA-Z0-9\-_]/g,'_')}
|
|
|
450
984
|
function dur(ms){return ms>=1000?(ms/1000).toFixed(1)+'s':ms+'ms'}
|
|
451
985
|
function fdate(iso){return iso?new Date(iso).toLocaleString():'--'}
|
|
452
986
|
|
|
453
|
-
/** Pretty-print a string if it's JSON, otherwise return as-is */
|
|
454
987
|
function prettyJson(str){
|
|
455
988
|
if(!str)return '';
|
|
456
989
|
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
457
990
|
}
|
|
458
991
|
|
|
459
|
-
/** Format headers object as readable string */
|
|
460
992
|
function fmtHeaders(h){
|
|
461
993
|
if(!h||typeof h!=='object')return '';
|
|
462
994
|
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
463
995
|
}
|
|
464
996
|
|
|
465
|
-
|
|
997
|
+
function buildHeaderKV(h){
|
|
998
|
+
if(!h||typeof h!=='object') return el('div',{className:'rd-nd-empty'},'No data');
|
|
999
|
+
var table=el('div',{className:'rd-hdr-table'});
|
|
1000
|
+
Object.keys(h).forEach(function(k){
|
|
1001
|
+
var row=el('div',{className:'rd-hdr-row'});
|
|
1002
|
+
row.appendChild(el('span',{className:'rd-hdr-key'},k));
|
|
1003
|
+
row.appendChild(el('span',{className:'rd-hdr-val'},String(h[k])));
|
|
1004
|
+
table.appendChild(row);
|
|
1005
|
+
});
|
|
1006
|
+
return table;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function makeCopyBtn(getTextFn){
|
|
1010
|
+
var btn=el('span',{className:'copy-btn',onclick:function(e){
|
|
1011
|
+
e.stopPropagation();
|
|
1012
|
+
var text=typeof getTextFn==='function'?getTextFn():String(getTextFn);
|
|
1013
|
+
navigator.clipboard.writeText(text).then(function(){
|
|
1014
|
+
btn.textContent='\u2713 Copied';
|
|
1015
|
+
btn.classList.add('copied');
|
|
1016
|
+
setTimeout(function(){btn.textContent='\u2398 Copy';btn.classList.remove('copied')},1200);
|
|
1017
|
+
});
|
|
1018
|
+
}},'\u2398 Copy');
|
|
1019
|
+
return btn;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function buildNdSection(title,contentEl,count,copyText){
|
|
1023
|
+
var toggle=el('div',{className:'rd-nd-toggle'},[
|
|
1024
|
+
el('span',{className:'nd-arrow'},'\u25B6'),
|
|
1025
|
+
el('span',null,title),
|
|
1026
|
+
count?el('span',{className:'nd-count'},count+' entries'):null,
|
|
1027
|
+
makeCopyBtn(copyText||function(){return contentWrap.textContent})
|
|
1028
|
+
]);
|
|
1029
|
+
var contentWrap=el('div',{className:'rd-nd-content'});
|
|
1030
|
+
contentWrap.appendChild(contentEl);
|
|
1031
|
+
toggle.addEventListener('click',function(e){
|
|
1032
|
+
e.stopPropagation();
|
|
1033
|
+
toggle.classList.toggle('open');
|
|
1034
|
+
});
|
|
1035
|
+
return el('div',{className:'rd-nd-section'},[toggle,contentWrap]);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function gqlOp(n){
|
|
1039
|
+
if(n.requestBody){
|
|
1040
|
+
try{
|
|
1041
|
+
var b=JSON.parse(n.requestBody);
|
|
1042
|
+
if(b.operationName)return b.operationName;
|
|
1043
|
+
if(b.query){var m=b.query.match(/^(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/);if(m)return m[1]}
|
|
1044
|
+
}catch(e){}
|
|
1045
|
+
}
|
|
1046
|
+
if(n.url){
|
|
1047
|
+
try{var u=new URL(n.url,location.href);var op=u.searchParams.get('operationName');if(op)return op}catch(e){}
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
466
1052
|
function buildNetRow(n){
|
|
467
|
-
var mCls='rd-net-method '+n.method.toLowerCase();
|
|
468
|
-
var
|
|
1053
|
+
var mCls='rd-net-method '+(n.method||'GET').toLowerCase();
|
|
1054
|
+
var sCode=n.status||0;
|
|
1055
|
+
var sCls='rd-net-status '+(sCode<300?'s2xx':sCode<400?'s3xx':sCode<500?'s4xx':'s5xx');
|
|
469
1056
|
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
470
|
-
var rowCls='rd-net-row'+(
|
|
471
|
-
var
|
|
472
|
-
|
|
473
|
-
el('span',{className:
|
|
474
|
-
el('span',{className:
|
|
475
|
-
el('span',{className:
|
|
476
|
-
|
|
477
|
-
|
|
1057
|
+
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
1058
|
+
var opName=gqlOp(n);
|
|
1059
|
+
var children=[
|
|
1060
|
+
el('span',{className:'rd-net-expand'},hasDetail?'\u25B6':''),
|
|
1061
|
+
el('span',{className:mCls},n.method||'GET'),
|
|
1062
|
+
el('span',{className:sCls},String(sCode))
|
|
1063
|
+
];
|
|
1064
|
+
if(opName)children.push(el('span',{className:'rd-net-op'},opName));
|
|
1065
|
+
children.push(el('span',{className:'rd-net-url'},n.url||''));
|
|
1066
|
+
children.push(makeCopyBtn(n.url||''));
|
|
1067
|
+
children.push(el('span',{className:'rd-net-dur'},dur(n.duration)));
|
|
1068
|
+
var row=el('div',{className:rowCls},children);
|
|
478
1069
|
var detail=null;
|
|
479
1070
|
if(hasDetail){
|
|
480
1071
|
var sections=[];
|
|
481
1072
|
if(n.requestHeaders){
|
|
482
|
-
var
|
|
483
|
-
|
|
484
|
-
sections.push(s);
|
|
1073
|
+
var hCount=Object.keys(n.requestHeaders).length;
|
|
1074
|
+
sections.push(buildNdSection('Request Headers',buildHeaderKV(n.requestHeaders),hCount,fmtHeaders(n.requestHeaders)));
|
|
485
1075
|
}
|
|
486
1076
|
if(n.requestBody){
|
|
487
|
-
var
|
|
488
|
-
|
|
489
|
-
sections.push(s2);
|
|
1077
|
+
var rbText=prettyJson(n.requestBody);
|
|
1078
|
+
sections.push(buildNdSection('Request Body',el('pre',null,rbText),null,rbText));
|
|
490
1079
|
}
|
|
491
1080
|
if(n.responseHeaders){
|
|
492
|
-
var
|
|
493
|
-
|
|
494
|
-
sections.push(s3);
|
|
1081
|
+
var rhCount=Object.keys(n.responseHeaders).length;
|
|
1082
|
+
sections.push(buildNdSection('Response Headers',buildHeaderKV(n.responseHeaders),rhCount,fmtHeaders(n.responseHeaders)));
|
|
495
1083
|
}
|
|
496
1084
|
if(n.responseBody){
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
sections.push(s4);
|
|
1085
|
+
var respText=prettyJson(n.responseBody);
|
|
1086
|
+
sections.push(buildNdSection('Response Body',el('pre',null,respText),null,respText));
|
|
500
1087
|
}
|
|
501
1088
|
detail=el('div',{className:'rd-net-detail'},sections);
|
|
502
1089
|
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
@@ -530,7 +1117,6 @@ function createHashBadge(hash){
|
|
|
530
1117
|
return badge;
|
|
531
1118
|
}
|
|
532
1119
|
|
|
533
|
-
/* ── Trigger source badge helper ── */
|
|
534
1120
|
function createTriggerBadge(source){
|
|
535
1121
|
var s=source||'unknown';
|
|
536
1122
|
var labels={dashboard:'Dashboard',mcp:'MCP',cli:'CLI',unknown:'--'};
|
|
@@ -542,35 +1128,367 @@ function createTriggerBadge(source){
|
|
|
542
1128
|
return badge;
|
|
543
1129
|
}
|
|
544
1130
|
|
|
545
|
-
/* ──
|
|
1131
|
+
/* ── Pool Distribution Summary ── */
|
|
1132
|
+
var POOL_COLORS=['#6366f1','#22d3ee','#f59e0b','#10b981','#ef4444','#8b5cf6','#ec4899','#14b8a6'];
|
|
1133
|
+
function buildPoolDistribution(tests){
|
|
1134
|
+
var pools={};var total=0;
|
|
1135
|
+
Object.keys(tests).forEach(function(n){
|
|
1136
|
+
if(n==='__error')return;var t=tests[n];
|
|
1137
|
+
if(!t.poolUrl)return;
|
|
1138
|
+
var label=t.poolUrl.replace('ws://','').replace('wss://','');
|
|
1139
|
+
if(!pools[label])pools[label]={count:0,passed:0,failed:0};
|
|
1140
|
+
pools[label].count++;total++;
|
|
1141
|
+
if(t.status==='passed'||t.success)pools[label].passed++;
|
|
1142
|
+
if(t.status==='failed'||t.success===false)pools[label].failed++;
|
|
1143
|
+
});
|
|
1144
|
+
var keys=Object.keys(pools);
|
|
1145
|
+
if(keys.length<2)return null;
|
|
1146
|
+
var bar=el('div',{className:'pool-dist'});
|
|
1147
|
+
var legend=el('div',{className:'pool-dist-legend'});
|
|
1148
|
+
keys.forEach(function(k,i){
|
|
1149
|
+
var pct=Math.round(pools[k].count/total*100);
|
|
1150
|
+
var color=POOL_COLORS[i%POOL_COLORS.length];
|
|
1151
|
+
var seg=el('div',{className:'pool-dist-seg'});
|
|
1152
|
+
seg.style.flex=pools[k].count;seg.style.background=color;
|
|
1153
|
+
seg.textContent=k+' ('+pools[k].count+')';
|
|
1154
|
+
bar.appendChild(seg);
|
|
1155
|
+
var lg=el('span',{},k+': '+pools[k].count+' tests ('+pct+'%)');
|
|
1156
|
+
lg.style.cssText='display:inline-flex;align-items:center;gap:4px';
|
|
1157
|
+
var dot=el('span',{});dot.style.cssText='width:8px;height:8px;border-radius:2px;background:'+color+';flex-shrink:0';
|
|
1158
|
+
lg.insertBefore(dot,lg.firstChild);
|
|
1159
|
+
legend.appendChild(lg);
|
|
1160
|
+
});
|
|
1161
|
+
return el('div',{style:'padding:4px 12px'},[bar,legend]);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
/* ── state.js ── */
|
|
1166
|
+
/* ── Global State ── */
|
|
546
1167
|
var S={
|
|
547
|
-
ws:null,project:null,view:'
|
|
548
|
-
liveRuns:{},
|
|
1168
|
+
ws:null,project:null,view:'watch',selectedRun:null,
|
|
1169
|
+
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
1170
|
+
runFilter:{status:'all',search:''},
|
|
1171
|
+
lastLearningsData:null,
|
|
1172
|
+
highlightedRunIdx:-1
|
|
549
1173
|
};
|
|
550
1174
|
|
|
551
1175
|
/* ── Navigation ── */
|
|
552
1176
|
$$('.nav-item').forEach(function(n){
|
|
553
1177
|
n.addEventListener('click',function(){
|
|
554
|
-
|
|
555
|
-
n.classList.add('active');
|
|
556
|
-
S.view=n.dataset.view;
|
|
557
|
-
$$('.view').forEach(function(v){v.classList.remove('active')});
|
|
558
|
-
$('#view-'+S.view).classList.add('active');
|
|
1178
|
+
showView(n.dataset.view);
|
|
559
1179
|
});
|
|
560
1180
|
});
|
|
561
1181
|
function showView(v){
|
|
562
1182
|
S.view=v;
|
|
563
1183
|
$$('.nav-item').forEach(function(n){n.classList.toggle('active',n.dataset.view===v)});
|
|
564
1184
|
$$('.view').forEach(function(x){x.classList.remove('active')});
|
|
565
|
-
|
|
1185
|
+
var viewEl=$('#view-'+v);
|
|
1186
|
+
if(viewEl)viewEl.classList.add('active');
|
|
1187
|
+
if(v==='watch'&&typeof startWatchPolling==='function')startWatchPolling();
|
|
1188
|
+
else if(typeof stopWatchPolling==='function')stopWatchPolling();
|
|
1189
|
+
if(v==='instances'&&typeof refreshInstances==='function')refreshInstances();
|
|
566
1190
|
}
|
|
567
1191
|
|
|
1192
|
+
/* ── Inner Tabs ── */
|
|
1193
|
+
function initTabs(){
|
|
1194
|
+
$$('.tab-bar').forEach(function(bar){
|
|
1195
|
+
var container=bar.parentElement;
|
|
1196
|
+
bar.querySelectorAll('.tab-btn').forEach(function(btn){
|
|
1197
|
+
btn.addEventListener('click',function(){
|
|
1198
|
+
bar.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active')});
|
|
1199
|
+
btn.classList.add('active');
|
|
1200
|
+
container.querySelectorAll('.tab-pane').forEach(function(p){p.classList.remove('active')});
|
|
1201
|
+
var pane=container.querySelector('#'+btn.dataset.tab);
|
|
1202
|
+
if(pane)pane.classList.add('active');
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
/* ── toast.js ── */
|
|
1210
|
+
/* ── Toast Notifications ── */
|
|
1211
|
+
function showToast(message,type,timeout){
|
|
1212
|
+
type=type||'info';
|
|
1213
|
+
timeout=timeout||5000;
|
|
1214
|
+
var container=$('#toastContainer');
|
|
1215
|
+
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
1216
|
+
var t=el('div',{className:'toast '+type},[
|
|
1217
|
+
el('span',null,icons[type]||''),
|
|
1218
|
+
el('span',null,message)
|
|
1219
|
+
]);
|
|
1220
|
+
container.appendChild(t);
|
|
1221
|
+
setTimeout(function(){
|
|
1222
|
+
t.classList.add('fade-out');
|
|
1223
|
+
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
1224
|
+
},timeout);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function showEnrichedToast(message,type){
|
|
1228
|
+
var container=$('#toastContainer');
|
|
1229
|
+
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
1230
|
+
var t=el('div',{className:'toast clickable '+type,onclick:function(){showView('runs');var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
1231
|
+
el('span',null,icons[type]||''),
|
|
1232
|
+
el('span',null,message)
|
|
1233
|
+
]);
|
|
1234
|
+
container.appendChild(t);
|
|
1235
|
+
setTimeout(function(){
|
|
1236
|
+
t.classList.add('fade-out');
|
|
1237
|
+
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
1238
|
+
},7000);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/* ── Download helper ── */
|
|
1242
|
+
function downloadFile(filename,content,mimeType){
|
|
1243
|
+
var blob=new Blob([content],{type:mimeType||'text/plain'});
|
|
1244
|
+
var url=URL.createObjectURL(blob);
|
|
1245
|
+
var a=document.createElement('a');
|
|
1246
|
+
a.href=url;a.download=filename;
|
|
1247
|
+
document.body.appendChild(a);a.click();
|
|
1248
|
+
document.body.removeChild(a);
|
|
1249
|
+
URL.revokeObjectURL(url);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
/* ── api.js ── */
|
|
1254
|
+
/* ── API & Pool ── */
|
|
1255
|
+
function api(p){return fetch(p).then(function(r){return r.json()})}
|
|
1256
|
+
function triggerRun(suite,projectId){
|
|
1257
|
+
if(anyLiveRunning())return;
|
|
1258
|
+
var body={};
|
|
1259
|
+
if(suite)body.suite=suite;
|
|
1260
|
+
if(projectId)body.projectId=projectId;
|
|
1261
|
+
else if(S.project)body.projectId=S.project;
|
|
1262
|
+
fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function renderPool(d){
|
|
1266
|
+
if(!d)return;
|
|
1267
|
+
var poolList=$('#poolList');
|
|
1268
|
+
if(d.pools&&d.pools.length>1){
|
|
1269
|
+
var anyAvail=d.availableCount>0;
|
|
1270
|
+
$('#poolDot').className='pool-dot '+(anyAvail?'on':'off');
|
|
1271
|
+
$('#poolLabel').textContent=anyAvail?d.availableCount+'/'+d.totalPools+' ready':'all busy';
|
|
1272
|
+
$('#poolSessions').textContent=(d.totalRunning||0)+'/'+(d.totalMaxConcurrent||0);
|
|
1273
|
+
poolList.textContent='';poolList.style.display='';
|
|
1274
|
+
d.pools.forEach(function(p){
|
|
1275
|
+
var label=(p.url||'').replace('ws://','').replace('wss://','');
|
|
1276
|
+
var ok=!p.error&&p.available;
|
|
1277
|
+
var dot=el('span',{className:'pool-dot '+(ok?'on':'off')});
|
|
1278
|
+
var name=el('strong',{},label);
|
|
1279
|
+
var status=el('span',{},p.error?'offline':p.available?'ready':'busy');
|
|
1280
|
+
var sess=el('span',{className:'pool-sessions'},(p.running||0)+'/'+(p.maxConcurrent||0));
|
|
1281
|
+
poolList.appendChild(el('div',{className:'pool-item'},[dot,name,status,sess]));
|
|
1282
|
+
});
|
|
1283
|
+
}else if(d.pools&&d.pools.length===1){
|
|
1284
|
+
var p=d.pools[0];
|
|
1285
|
+
$('#poolDot').className='pool-dot '+(p.error||!p.available?'off':'on');
|
|
1286
|
+
$('#poolLabel').textContent=p.error?'offline':p.available?'ready':'busy';
|
|
1287
|
+
$('#poolSessions').textContent=(p.running||0)+'/'+(p.maxConcurrent||0);
|
|
1288
|
+
poolList.style.display='none';
|
|
1289
|
+
}else{
|
|
1290
|
+
$('#poolDot').className='pool-dot '+(d.error||!d.available?'off':'on');
|
|
1291
|
+
$('#poolLabel').textContent=d.error?'offline':d.available?'ready':'busy';
|
|
1292
|
+
$('#poolSessions').textContent=(d.running||0)+'/'+(d.maxConcurrent||0);
|
|
1293
|
+
poolList.style.display='none';
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function refreshStatus(){
|
|
1297
|
+
api('/api/status').then(function(d){
|
|
1298
|
+
renderPool(d.pool);
|
|
1299
|
+
// Check if sync is enabled and update UI
|
|
1300
|
+
if(d.config && d.config.sync){
|
|
1301
|
+
renderSyncStatus(d.config.sync);
|
|
1302
|
+
}
|
|
1303
|
+
}).catch(function(){});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/* ── Sync ── */
|
|
1307
|
+
var syncMode = null;
|
|
1308
|
+
|
|
1309
|
+
function renderSyncStatus(sync){
|
|
1310
|
+
var status=$('#syncStatus');
|
|
1311
|
+
var dot=$('#syncDot');
|
|
1312
|
+
var mode=$('#syncMode');
|
|
1313
|
+
var details=$('#syncDetails');
|
|
1314
|
+
var instances=$('#syncInstances');
|
|
1315
|
+
|
|
1316
|
+
if(!sync || sync.mode === 'standalone'){
|
|
1317
|
+
status.style.display='none';
|
|
1318
|
+
$('#navInstances').style.display='none';
|
|
1319
|
+
syncMode = null;
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
status.style.display='block';
|
|
1324
|
+
syncMode = sync.mode;
|
|
1325
|
+
|
|
1326
|
+
mode.textContent = sync.mode;
|
|
1327
|
+
mode.className = 'sync-mode ' + sync.mode;
|
|
1328
|
+
|
|
1329
|
+
if(sync.mode === 'hub'){
|
|
1330
|
+
dot.className = 'pool-dot on';
|
|
1331
|
+
dot.style.background = 'var(--purple)';
|
|
1332
|
+
details.textContent = 'Accepting agent connections';
|
|
1333
|
+
$('#navInstances').style.display = 'flex';
|
|
1334
|
+
refreshInstances();
|
|
1335
|
+
} else if(sync.mode === 'agent'){
|
|
1336
|
+
var hubUrl = sync.agent && sync.agent.hubUrl;
|
|
1337
|
+
if(hubUrl){
|
|
1338
|
+
dot.className = 'pool-dot on';
|
|
1339
|
+
dot.style.background = 'var(--accent)';
|
|
1340
|
+
details.innerHTML = 'Hub: <strong>' + hubUrl + '</strong>';
|
|
1341
|
+
} else {
|
|
1342
|
+
dot.className = 'pool-dot off';
|
|
1343
|
+
details.textContent = 'Not connected';
|
|
1344
|
+
}
|
|
1345
|
+
$('#navInstances').style.display = 'none';
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function refreshInstances(){
|
|
1350
|
+
if(syncMode !== 'hub') return;
|
|
1351
|
+
|
|
1352
|
+
api('/api/sync/instances').then(function(d){
|
|
1353
|
+
var instances = d.instances || [];
|
|
1354
|
+
var online = 0;
|
|
1355
|
+
var active = 0;
|
|
1356
|
+
var pending = 0;
|
|
1357
|
+
var now = Date.now();
|
|
1358
|
+
|
|
1359
|
+
instances.forEach(function(inst){
|
|
1360
|
+
if(inst.status === 'active') active++;
|
|
1361
|
+
if(inst.status === 'pending') pending++;
|
|
1362
|
+
if(inst.lastSeen){
|
|
1363
|
+
var lastSeen = new Date(inst.lastSeen + 'Z').getTime();
|
|
1364
|
+
if(now - lastSeen < 5 * 60 * 1000) online++;
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
$('#statInstancesTotal').textContent = instances.length;
|
|
1369
|
+
$('#statInstancesOnline').textContent = online;
|
|
1370
|
+
$('#statInstancesActive').textContent = active;
|
|
1371
|
+
$('#statInstancesPending').textContent = pending;
|
|
1372
|
+
$('#badgeInstances').textContent = online + '/' + instances.length;
|
|
1373
|
+
|
|
1374
|
+
var tbody = $('#instancesBody');
|
|
1375
|
+
tbody.innerHTML = '';
|
|
1376
|
+
|
|
1377
|
+
if(instances.length === 0){
|
|
1378
|
+
$('#instancesEmpty').style.display = 'block';
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
$('#instancesEmpty').style.display = 'none';
|
|
1382
|
+
|
|
1383
|
+
instances.forEach(function(inst){
|
|
1384
|
+
var isOnline = inst.lastSeen && (now - new Date(inst.lastSeen + 'Z').getTime() < 5 * 60 * 1000);
|
|
1385
|
+
var statusClass = inst.status === 'active' ? 'pass' : inst.status === 'pending' ? 'flaky' : 'fail';
|
|
1386
|
+
|
|
1387
|
+
var tr = el('tr', null, [
|
|
1388
|
+
el('td', null, [
|
|
1389
|
+
el('span', {className: 'pool-dot ' + (isOnline ? 'on' : 'off'), style: 'margin-right:6px'}),
|
|
1390
|
+
el('span', {className: 'badge ' + statusClass}, inst.status)
|
|
1391
|
+
]),
|
|
1392
|
+
el('td', {style: 'font-family:var(--mono)'}, inst.instanceId),
|
|
1393
|
+
el('td', null, inst.displayName),
|
|
1394
|
+
el('td', null, inst.role),
|
|
1395
|
+
el('td', null, inst.environment || '-'),
|
|
1396
|
+
el('td', {style: 'color:var(--text3);font-size:11px'}, inst.lastSeen ? fdate(inst.lastSeen) : 'never'),
|
|
1397
|
+
el('td', null, [
|
|
1398
|
+
inst.status === 'pending' ? el('button', {className: 'btn sm', onclick: function(e){
|
|
1399
|
+
e.stopPropagation();
|
|
1400
|
+
approveInstance(inst.instanceId);
|
|
1401
|
+
}}, 'Approve') : null,
|
|
1402
|
+
inst.status === 'active' ? el('button', {className: 'btn sm danger', onclick: function(e){
|
|
1403
|
+
e.stopPropagation();
|
|
1404
|
+
revokeInstance(inst.instanceId);
|
|
1405
|
+
}}, 'Suspend') : null
|
|
1406
|
+
])
|
|
1407
|
+
]);
|
|
1408
|
+
tbody.appendChild(tr);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
// Update sync status sidebar with online instances
|
|
1412
|
+
var syncInst = $('#syncInstances');
|
|
1413
|
+
syncInst.innerHTML = '';
|
|
1414
|
+
instances.slice(0, 5).forEach(function(inst){
|
|
1415
|
+
var isOnline = inst.lastSeen && (now - new Date(inst.lastSeen + 'Z').getTime() < 5 * 60 * 1000);
|
|
1416
|
+
var span = el('span', {className: 'sync-inst ' + (isOnline ? 'online' : 'offline')}, [
|
|
1417
|
+
el('span', {className: 'pool-dot ' + (isOnline ? 'on' : 'off'), style: 'width:5px;height:5px'}),
|
|
1418
|
+
document.createTextNode(inst.instanceId.slice(0, 12))
|
|
1419
|
+
]);
|
|
1420
|
+
syncInst.appendChild(span);
|
|
1421
|
+
});
|
|
1422
|
+
if(instances.length > 5){
|
|
1423
|
+
syncInst.appendChild(el('span', {style: 'font-size:9px;color:var(--text3)'}, '+' + (instances.length - 5) + ' more'));
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
}).catch(function(err){
|
|
1427
|
+
console.error('Failed to load instances:', err);
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function approveInstance(instanceId){
|
|
1432
|
+
api('/api/sync/instances/' + instanceId, {
|
|
1433
|
+
method: 'PATCH',
|
|
1434
|
+
body: JSON.stringify({status: 'active'})
|
|
1435
|
+
}).then(function(){
|
|
1436
|
+
showToast('Instance approved', 'success');
|
|
1437
|
+
refreshInstances();
|
|
1438
|
+
}).catch(function(err){
|
|
1439
|
+
showToast('Failed to approve: ' + err.message, 'error');
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function revokeInstance(instanceId){
|
|
1444
|
+
api('/api/sync/instances/' + instanceId, {
|
|
1445
|
+
method: 'PATCH',
|
|
1446
|
+
body: JSON.stringify({status: 'suspended'})
|
|
1447
|
+
}).then(function(){
|
|
1448
|
+
showToast('Instance suspended', 'success');
|
|
1449
|
+
refreshInstances();
|
|
1450
|
+
}).catch(function(err){
|
|
1451
|
+
showToast('Failed to suspend: ' + err.message, 'error');
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
$('#refreshInstances').addEventListener('click', refreshInstances);
|
|
1456
|
+
$('#instanceFilter').addEventListener('change', function(){
|
|
1457
|
+
// For now just refresh - could add client-side filtering
|
|
1458
|
+
refreshInstances();
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
/* ── Projects ── */
|
|
1462
|
+
function refreshProjects(){
|
|
1463
|
+
api('/api/db/projects').then(function(projects){
|
|
1464
|
+
var sel=$('#projectSelect'),prev=sel.value;
|
|
1465
|
+
while(sel.options.length>1)sel.remove(1);
|
|
1466
|
+
if(Array.isArray(projects))projects.forEach(function(p){
|
|
1467
|
+
var o=document.createElement('option');o.value=p.id;o.textContent=p.name;sel.appendChild(o);
|
|
1468
|
+
});
|
|
1469
|
+
sel.value=prev||'';
|
|
1470
|
+
}).catch(function(){});
|
|
1471
|
+
}
|
|
1472
|
+
$('#projectSelect').addEventListener('change',function(){
|
|
1473
|
+
S.project=this.value?parseInt(this.value,10):null;
|
|
1474
|
+
S.selectedRun=null;
|
|
1475
|
+
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();refreshWatch();
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
/* ── websocket.js ── */
|
|
568
1480
|
/* ── WebSocket ── */
|
|
569
1481
|
function connectWS(){
|
|
570
1482
|
var proto=location.protocol==='https:'?'wss:':'ws:';
|
|
571
1483
|
S.ws=new WebSocket(proto+'//'+location.host);
|
|
572
|
-
S.ws.onopen=function(){
|
|
573
|
-
|
|
1484
|
+
S.ws.onopen=function(){
|
|
1485
|
+
$('#wsDot').style.background='var(--green)';$('#wsLabel').textContent='ws: connected';$('#wsLabel').style.color='var(--green)';
|
|
1486
|
+
showToast('WebSocket connected','info');
|
|
1487
|
+
};
|
|
1488
|
+
S.ws.onclose=function(){
|
|
1489
|
+
$('#wsDot').style.background='var(--red)';$('#wsLabel').textContent='ws: disconnected';$('#wsLabel').style.color='var(--text3)';
|
|
1490
|
+
setTimeout(connectWS,3000);
|
|
1491
|
+
};
|
|
574
1492
|
S.ws.onerror=function(){};
|
|
575
1493
|
S.ws.onmessage=function(e){try{handleWS(JSON.parse(e.data))}catch(x){}};
|
|
576
1494
|
}
|
|
@@ -583,22 +1501,16 @@ function getLiveRun(m){
|
|
|
583
1501
|
}
|
|
584
1502
|
function anyLiveRunning(){for(var k in S.liveRuns)if(S.liveRuns[k].on)return true;return false}
|
|
585
1503
|
|
|
586
|
-
/* Staleness guard: auto-finish stuck runs, garbage-collect old finished runs */
|
|
587
1504
|
setInterval(function(){
|
|
588
1505
|
var changed=false;
|
|
589
1506
|
for(var k in S.liveRuns){
|
|
590
1507
|
var r=S.liveRuns[k];
|
|
591
1508
|
var age=Date.now()-r._lastEvent;
|
|
592
|
-
/* Mark stuck runs as done */
|
|
593
1509
|
if(r.on&&!r.done){
|
|
594
|
-
/* 0/0 runs (never received any tests) — mark stale after 10s */
|
|
595
1510
|
if(r.total===0&&age>10000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
596
|
-
/* All tests completed but run:complete never arrived */
|
|
597
1511
|
else if(r.completed>=r.total&&r.total>0&&age>15000){r.on=false;r.done=true;r.active=0;changed=true}
|
|
598
|
-
/* General staleness — no events for 30s */
|
|
599
1512
|
else if(age>30000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
600
1513
|
}
|
|
601
|
-
/* Auto-remove stale 0/0 runs after 15s, finished runs after 120s */
|
|
602
1514
|
if(r.done&&r.stale&&r.total===0&&age>15000){delete S.liveRuns[k];changed=true}
|
|
603
1515
|
else if(r.done&&age>120000){delete S.liveRuns[k];changed=true}
|
|
604
1516
|
}
|
|
@@ -609,148 +1521,704 @@ function handleWS(m){
|
|
|
609
1521
|
switch(m.event){
|
|
610
1522
|
case 'pool:status':renderPool(m.data);break;
|
|
611
1523
|
case 'run:start':
|
|
612
|
-
/* Clear all finished/stale runs when a new one starts */
|
|
613
1524
|
for(var dk in S.liveRuns){if(S.liveRuns[dk].done)delete S.liveRuns[dk]}
|
|
614
1525
|
var r=getLiveRun(m);
|
|
615
1526
|
r.total=m.total;r.on=true;r.done=false;
|
|
616
|
-
S.
|
|
1527
|
+
S.liveCollapsed=new Set();S.liveSSOpen=new Set();
|
|
617
1528
|
showView('live');renderLive();break;
|
|
618
1529
|
case 'test:start':
|
|
619
|
-
var
|
|
620
|
-
|
|
621
|
-
|
|
1530
|
+
var r2=getLiveRun(m);if(!r2)break;
|
|
1531
|
+
r2.active=m.activeCount;
|
|
1532
|
+
r2.tests[m.name]={status:'running',actions:0,totalActions:0,error:null,actionLog:[],screenshots:[],serial:m.serial||false};
|
|
1533
|
+
renderLive();break;
|
|
1534
|
+
case 'test:pool':
|
|
1535
|
+
var rp=getLiveRun(m);if(!rp||!rp.tests[m.name])break;
|
|
1536
|
+
rp.tests[m.name].poolUrl=m.poolUrl||null;
|
|
1537
|
+
rp.tests[m.name].actionLog.unshift({type:'pool',narrative:'\uD83D\uDD17 '+m.name+' \u2192 '+(m.poolUrl||'').replace('ws://','').replace('wss://',''),success:true,duration:null,isPoolLog:true});
|
|
622
1538
|
renderLive();break;
|
|
623
1539
|
case 'test:action':
|
|
624
|
-
var
|
|
625
|
-
var t=
|
|
1540
|
+
var r3=getLiveRun(m);if(!r3||!r3.tests[m.name])break;
|
|
1541
|
+
var t=r3.tests[m.name];
|
|
626
1542
|
t.actions=m.actionIndex+1;t.totalActions=m.totalActions;t.actionType=m.action.type;
|
|
627
|
-
t.actionLog.push({type:m.action.type,selector:m.action.selector||null,value:m.action.value||null,text:m.action.text||null,success:m.success,duration:m.duration,error:m.error||null});
|
|
1543
|
+
t.actionLog.push({type:m.action.type,selector:m.action.selector||null,value:m.action.value||null,text:m.action.text||null,success:m.success,duration:m.duration,error:m.error||null,narrative:m.narrative||null,actionRetries:m.action.retries||0});
|
|
628
1544
|
if(m.screenshotPath)t.screenshots.push(m.screenshotPath);
|
|
629
1545
|
renderLive();break;
|
|
630
1546
|
case 'test:retry':
|
|
631
|
-
var
|
|
632
|
-
|
|
1547
|
+
var r4=getLiveRun(m);if(!r4||!r4.tests[m.name])break;
|
|
1548
|
+
r4.tests[m.name].retry=m.attempt+'/'+m.maxAttempts;
|
|
633
1549
|
renderLive();break;
|
|
634
1550
|
case 'test:complete':
|
|
635
|
-
var
|
|
636
|
-
|
|
637
|
-
if(m.success){
|
|
638
|
-
else{
|
|
639
|
-
if(
|
|
640
|
-
|
|
641
|
-
if(m.screenshots&&m.screenshots.length)
|
|
642
|
-
if(m.errorScreenshot)
|
|
643
|
-
if(m.networkLogs&&m.networkLogs.length)
|
|
1551
|
+
var r5=getLiveRun(m);if(!r5)break;
|
|
1552
|
+
r5.completed++;
|
|
1553
|
+
if(m.success){r5.passed++;if(r5.tests[m.name])r5.tests[m.name].status='passed'}
|
|
1554
|
+
else{r5.failed++;if(r5.tests[m.name]){r5.tests[m.name].status='failed';r5.tests[m.name].error=m.error}}
|
|
1555
|
+
if(r5.tests[m.name]){
|
|
1556
|
+
r5.tests[m.name].duration=m.duration;
|
|
1557
|
+
if(m.screenshots&&m.screenshots.length)r5.tests[m.name].screenshots=m.screenshots;
|
|
1558
|
+
if(m.errorScreenshot)r5.tests[m.name].errorScreenshot=m.errorScreenshot;
|
|
1559
|
+
if(m.networkLogs&&m.networkLogs.length)r5.tests[m.name].networkLogs=m.networkLogs;
|
|
1560
|
+
if(m.poolUrl)r5.tests[m.name].poolUrl=m.poolUrl;
|
|
644
1561
|
}
|
|
645
|
-
|
|
1562
|
+
r5.active=Math.max(0,r5.active-1);
|
|
646
1563
|
renderLive();break;
|
|
647
1564
|
case 'run:complete':
|
|
648
|
-
var
|
|
649
|
-
|
|
1565
|
+
var r6=getLiveRun(m);if(r6){r6.on=false;r6.done=true;r6.active=0}
|
|
1566
|
+
var summary=m.summary||{};
|
|
1567
|
+
var baseMsg='Run complete: '+(summary.failed>0?summary.failed+' failed':'all '+(summary.total||0)+' passed');
|
|
1568
|
+
var baseType=summary.failed>0?'error':'success';
|
|
1569
|
+
var healthUrl=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
1570
|
+
fetch(healthUrl).then(function(r){return r.json()}).then(function(h){
|
|
1571
|
+
if(h&&h.passRate!==undefined){
|
|
1572
|
+
var extra='. Pass rate: '+h.passRate+'%';
|
|
1573
|
+
if(h.passRateTrend==='declining')extra+=' (declining, '+h.trendDelta+'%)';
|
|
1574
|
+
else if(h.passRateTrend==='improving')extra+=' (improving, +'+h.trendDelta+'%)';
|
|
1575
|
+
if(h.flakyCount>0)extra+='. '+h.flakyCount+' flaky test(s)';
|
|
1576
|
+
showEnrichedToast(baseMsg+extra,baseType);
|
|
1577
|
+
} else {
|
|
1578
|
+
showToast(baseMsg,baseType);
|
|
1579
|
+
}
|
|
1580
|
+
}).catch(function(){showToast(baseMsg,baseType)});
|
|
1581
|
+
renderLive();refreshRuns();refreshProjects();refreshWatch();break;
|
|
650
1582
|
case 'run:error':
|
|
651
|
-
var
|
|
1583
|
+
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
1584
|
+
showToast('Run error: '+m.error,'error');
|
|
652
1585
|
renderLive();break;
|
|
653
1586
|
case 'db:updated':
|
|
654
|
-
refreshRuns();refreshProjects();refreshScreenshots();break;
|
|
1587
|
+
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();refreshWatch();break;
|
|
655
1588
|
}
|
|
656
1589
|
}
|
|
657
1590
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1591
|
+
|
|
1592
|
+
/* ── view-watch.js ── */
|
|
1593
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1594
|
+
Watch View — Project Cards + Sparklines + Event Log
|
|
1595
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1596
|
+
var _watchInterval=null;
|
|
1597
|
+
var _countdownInterval=null;
|
|
1598
|
+
var _watchData=null;
|
|
1599
|
+
|
|
1600
|
+
function refreshWatch(){
|
|
1601
|
+
// Fetch projects overview (sparklines)
|
|
1602
|
+
api('/api/db/projects/overview').then(function(projects){
|
|
1603
|
+
if(!Array.isArray(projects)||!projects.length){
|
|
1604
|
+
$('#watchCards').textContent='';
|
|
1605
|
+
$('#watchEmpty').style.display='block';
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
$('#watchEmpty').style.display='none';
|
|
1609
|
+
_watchData=projects;
|
|
1610
|
+
renderWatchCards(projects);
|
|
1611
|
+
}).catch(function(){
|
|
1612
|
+
// Fallback: use regular projects list
|
|
1613
|
+
api('/api/db/projects').then(function(projects){
|
|
1614
|
+
if(!Array.isArray(projects)||!projects.length){$('#watchEmpty').style.display='block';return}
|
|
1615
|
+
$('#watchEmpty').style.display='none';
|
|
1616
|
+
_watchData=projects.map(function(p){return Object.assign({},p,{sparkline:[]})});
|
|
1617
|
+
renderWatchCards(_watchData);
|
|
1618
|
+
}).catch(function(){});
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
// Fetch event log (recent runs)
|
|
1622
|
+
var runsUrl=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
1623
|
+
api(runsUrl).then(function(runs){
|
|
1624
|
+
renderEventLog(runs);
|
|
1625
|
+
}).catch(function(){});
|
|
1626
|
+
|
|
1627
|
+
// Fetch watch jobs status for countdown
|
|
1628
|
+
fetch('/api/watch/status').then(function(r){
|
|
1629
|
+
if(!r.ok)throw new Error('not running');
|
|
1630
|
+
return r.json();
|
|
1631
|
+
}).then(function(jobs){
|
|
1632
|
+
applyWatchJobData(jobs);
|
|
1633
|
+
}).catch(function(){
|
|
1634
|
+
// Watch engine not running — that's fine, cards still show
|
|
1635
|
+
});
|
|
667
1636
|
}
|
|
668
1637
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1638
|
+
function renderWatchCards(projects){
|
|
1639
|
+
var container=$('#watchCards');
|
|
1640
|
+
container.textContent='';
|
|
1641
|
+
|
|
1642
|
+
projects.forEach(function(p){
|
|
1643
|
+
var sparkline=p.sparkline||[];
|
|
1644
|
+
var lastRate=sparkline.length?sparkline[sparkline.length-1]:null;
|
|
1645
|
+
var rateColor=lastRate===null?'dim':lastRate>=90?'green':lastRate>=70?'amber':'red';
|
|
1646
|
+
var dotColor=rateColor;
|
|
1647
|
+
|
|
1648
|
+
var sparkEl=el('div',{className:'watch-sparkline'});
|
|
1649
|
+
if(sparkline.length>=2){
|
|
1650
|
+
sparkEl.appendChild(buildSparkline(sparkline));
|
|
1651
|
+
} else {
|
|
1652
|
+
sparkEl.style.cssText='height:40px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:10px';
|
|
1653
|
+
sparkEl.textContent=sparkline.length?'1 run':'No runs yet';
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
var triggerBtn=el('button',{className:'btn sm',onclick:function(e){e.stopPropagation();triggerRun(null,p.id)}},'\u25B6');
|
|
1657
|
+
var detailBtn=el('button',{className:'btn sm',onclick:function(e){
|
|
1658
|
+
e.stopPropagation();
|
|
1659
|
+
S.project=p.id;$('#projectSelect').value=p.id;
|
|
1660
|
+
showView('runs');
|
|
1661
|
+
refreshRuns();refreshSuites();
|
|
1662
|
+
}},'\uD83D\uDD0D');
|
|
1663
|
+
|
|
1664
|
+
var card=el('div',{className:'watch-card',id:'watch-card-'+p.id},[
|
|
1665
|
+
el('div',{className:'watch-card-header'},[
|
|
1666
|
+
el('div',{className:'watch-card-name'},p.name),
|
|
1667
|
+
el('div',{className:'watch-card-icons'},[triggerBtn,detailBtn])
|
|
1668
|
+
]),
|
|
1669
|
+
sparkEl,
|
|
1670
|
+
el('div',{className:'watch-card-footer'},[
|
|
1671
|
+
el('div',{className:'watch-card-status'},[
|
|
1672
|
+
el('span',{className:'status-dot '+dotColor}),
|
|
1673
|
+
el('span',{className:'watch-card-rate '+rateColor},lastRate!==null?lastRate+'%':'—')
|
|
1674
|
+
]),
|
|
1675
|
+
el('span',{style:'color:var(--text3);font-size:10px'},p.runCount?p.runCount+' runs':'')
|
|
1676
|
+
]),
|
|
1677
|
+
el('div',{className:'watch-card-meta'},[
|
|
1678
|
+
el('span',{className:'watch-card-countdown',id:'watch-countdown-'+p.id},''),
|
|
1679
|
+
p.lastCommit?el('span',{className:'watch-card-commit'},'\u{1F4CB} '+p.lastCommit.slice(0,8)):null
|
|
1680
|
+
])
|
|
1681
|
+
]);
|
|
1682
|
+
|
|
1683
|
+
container.appendChild(card);
|
|
1684
|
+
});
|
|
675
1685
|
}
|
|
676
|
-
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
677
1686
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1687
|
+
function buildSparkline(data){
|
|
1688
|
+
var ns='http://www.w3.org/2000/svg';
|
|
1689
|
+
var svg=document.createElementNS(ns,'svg');
|
|
1690
|
+
svg.setAttribute('viewBox','0 0 200 40');
|
|
1691
|
+
svg.setAttribute('preserveAspectRatio','none');
|
|
1692
|
+
|
|
1693
|
+
var n=data.length;
|
|
1694
|
+
var w=200/(n-1||1);
|
|
1695
|
+
var pts=data.map(function(v,i){return (i*w)+','+(40-v*0.4)}).join(' ');
|
|
1696
|
+
|
|
1697
|
+
// Gradient fill
|
|
1698
|
+
var poly=document.createElementNS(ns,'polygon');
|
|
1699
|
+
poly.setAttribute('points','0,40 '+pts+' '+((n-1)*w)+',40');
|
|
1700
|
+
poly.setAttribute('fill','var(--accent-dim)');
|
|
1701
|
+
svg.appendChild(poly);
|
|
1702
|
+
|
|
1703
|
+
// Line
|
|
1704
|
+
var pl=document.createElementNS(ns,'polyline');
|
|
1705
|
+
pl.setAttribute('points',pts);
|
|
1706
|
+
pl.setAttribute('fill','none');
|
|
1707
|
+
pl.setAttribute('stroke','var(--accent)');
|
|
1708
|
+
pl.setAttribute('stroke-width','1.5');
|
|
1709
|
+
svg.appendChild(pl);
|
|
1710
|
+
|
|
1711
|
+
// End dot
|
|
1712
|
+
if(n>0){
|
|
1713
|
+
var lastVal=data[n-1];
|
|
1714
|
+
var dotColor=lastVal>=90?'var(--green)':lastVal>=70?'var(--amber)':'var(--red)';
|
|
1715
|
+
var circle=document.createElementNS(ns,'circle');
|
|
1716
|
+
circle.setAttribute('cx',''+(n-1)*w);
|
|
1717
|
+
circle.setAttribute('cy',''+(40-lastVal*0.4));
|
|
1718
|
+
circle.setAttribute('r','3');
|
|
1719
|
+
circle.setAttribute('fill',dotColor);
|
|
1720
|
+
svg.appendChild(circle);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
return svg;
|
|
688
1724
|
}
|
|
689
|
-
$('#projectSelect').addEventListener('change',function(){
|
|
690
|
-
S.project=this.value?parseInt(this.value,10):null;
|
|
691
|
-
S.selectedRun=null;
|
|
692
|
-
refreshRuns();refreshSuites();refreshScreenshots();
|
|
693
|
-
});
|
|
694
1725
|
|
|
695
|
-
|
|
1726
|
+
function applyWatchJobData(jobs){
|
|
1727
|
+
if(!jobs||!jobs.length)return;
|
|
1728
|
+
jobs.forEach(function(j){
|
|
1729
|
+
// Find matching card by project name
|
|
1730
|
+
if(!_watchData)return;
|
|
1731
|
+
var match=_watchData.find(function(p){return p.name===j.name||p.cwd===j.cwd});
|
|
1732
|
+
if(!match)return;
|
|
1733
|
+
var cdEl=$('#watch-countdown-'+match.id);
|
|
1734
|
+
if(cdEl&&j.nextRunAt){
|
|
1735
|
+
cdEl.dataset.nextRunAt=j.nextRunAt;
|
|
1736
|
+
updateCountdown(cdEl);
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
startCountdownTimer();
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
function startCountdownTimer(){
|
|
1743
|
+
if(_countdownInterval)return;
|
|
1744
|
+
_countdownInterval=setInterval(function(){
|
|
1745
|
+
$$('.watch-card-countdown[data-next-run-at]').forEach(updateCountdown);
|
|
1746
|
+
},1000);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function updateCountdown(cdEl){
|
|
1750
|
+
var next=cdEl.dataset.nextRunAt;
|
|
1751
|
+
if(!next){cdEl.textContent='';return}
|
|
1752
|
+
var diff=new Date(next)-Date.now();
|
|
1753
|
+
if(diff<=0){cdEl.textContent='\u23F1 Running...';return}
|
|
1754
|
+
var m=Math.floor(diff/60000);
|
|
1755
|
+
var s=Math.floor((diff%60000)/1000);
|
|
1756
|
+
cdEl.textContent='\u23F1 Next: '+m+'m '+String(s).padStart(2,'0')+'s';
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function renderEventLog(runs){
|
|
1760
|
+
var container=$('#watchEventLog');
|
|
1761
|
+
if(!container)return;
|
|
1762
|
+
container.textContent='';
|
|
1763
|
+
|
|
1764
|
+
if(!Array.isArray(runs)||!runs.length){
|
|
1765
|
+
container.appendChild(el('div',{style:'padding:16px;text-align:center;color:var(--text3);font-size:11px'},'No runs recorded yet.'));
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Column header row
|
|
1770
|
+
container.appendChild(el('div',{className:'watch-event-row we-header'},[
|
|
1771
|
+
el('span',null,'Time'),
|
|
1772
|
+
el('span',null,'Project'),
|
|
1773
|
+
el('span',null,'Suite'),
|
|
1774
|
+
el('span',{style:'justify-self:center'},'Status'),
|
|
1775
|
+
el('span',{style:'text-align:center'},'Tests'),
|
|
1776
|
+
el('span',{style:'text-align:right'},'Rate'),
|
|
1777
|
+
el('span',{style:'text-align:right'},'Duration'),
|
|
1778
|
+
el('span',{style:'text-align:right'},'Source')
|
|
1779
|
+
]));
|
|
1780
|
+
|
|
1781
|
+
var recent=runs.slice(0,30);
|
|
1782
|
+
recent.forEach(function(r){
|
|
1783
|
+
var rate=parseFloat(r.pass_rate)||0;
|
|
1784
|
+
var badgeCls=r.failed>0?'fail':'pass';
|
|
1785
|
+
var badgeText=r.failed>0?'FAIL':'PASS';
|
|
1786
|
+
|
|
1787
|
+
// Test counts: "5/5" or "3/5 (2 fail)"
|
|
1788
|
+
var countsText=r.passed+'/'+r.total;
|
|
1789
|
+
var countsParts=[el('span',{className:'we-counts-ok'},String(r.passed))];
|
|
1790
|
+
countsParts.push(document.createTextNode('/'+r.total));
|
|
1791
|
+
if(r.failed>0){
|
|
1792
|
+
countsParts.push(document.createTextNode(' ('));
|
|
1793
|
+
countsParts.push(el('span',{style:'color:var(--red)'},r.failed+' fail'));
|
|
1794
|
+
countsParts.push(document.createTextNode(')'));
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// Trigger badge
|
|
1798
|
+
var triggerIcon={'cli':'\u2318','dashboard':'\uD83D\uDCBB','mcp':'\u2699','watch':'\u23F1','api':'\u26A1'};
|
|
1799
|
+
var trigSrc=r.triggered_by||'cli';
|
|
1800
|
+
var trigEl=el('span',{className:'we-trigger',title:'Triggered by: '+trigSrc},(triggerIcon[trigSrc]||'\u2318')+' '+trigSrc);
|
|
1801
|
+
|
|
1802
|
+
var row=el('div',{className:'watch-event-row',style:'cursor:pointer'},[
|
|
1803
|
+
el('span',{className:'watch-event-time'},fdate(r.generated_at)),
|
|
1804
|
+
el('span',{className:'watch-event-project'},r.project_name||'—'),
|
|
1805
|
+
el('span',{className:'watch-event-suite'},r.suite_name||'all'),
|
|
1806
|
+
el('span',{className:'watch-event-result'},[el('span',{className:'badge '+badgeCls},badgeText)]),
|
|
1807
|
+
el('span',{className:'watch-event-counts'},countsParts),
|
|
1808
|
+
el('span',{className:'watch-event-rate'},rate>0?rate.toFixed(0)+'%':'—'),
|
|
1809
|
+
el('span',{className:'watch-event-duration'},r.duration?dur(r.duration):'—'),
|
|
1810
|
+
trigEl
|
|
1811
|
+
]);
|
|
1812
|
+
|
|
1813
|
+
// Click to navigate to run detail
|
|
1814
|
+
(function(run){
|
|
1815
|
+
row.addEventListener('click',function(){
|
|
1816
|
+
S.project=run.project_id;$('#projectSelect').value=run.project_id;
|
|
1817
|
+
showView('runs');
|
|
1818
|
+
refreshRuns();
|
|
1819
|
+
});
|
|
1820
|
+
})(r);
|
|
1821
|
+
|
|
1822
|
+
container.appendChild(row);
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function startWatchPolling(){
|
|
1827
|
+
if(_watchInterval)return;
|
|
1828
|
+
refreshWatch();
|
|
1829
|
+
_watchInterval=setInterval(refreshWatch,10000);
|
|
1830
|
+
}
|
|
1831
|
+
function stopWatchPolling(){
|
|
1832
|
+
if(_watchInterval){clearInterval(_watchInterval);_watchInterval=null}
|
|
1833
|
+
if(_countdownInterval){clearInterval(_countdownInterval);_countdownInterval=null}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
|
|
1837
|
+
/* ── view-tests.js ── */
|
|
1838
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1839
|
+
Tests View — Suites + Modules + Variables (inner tabs)
|
|
1840
|
+
══════════════════════════════════════════════════════════════════ */
|
|
696
1841
|
function refreshSuites(){
|
|
697
|
-
var grid=$('#suiteGrid'),empty=$('#suitesEmpty');
|
|
1842
|
+
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
698
1843
|
grid.textContent='';
|
|
1844
|
+
var moduleSection=$('#moduleSection');
|
|
1845
|
+
moduleSection.textContent='';
|
|
699
1846
|
|
|
700
1847
|
if(S.project){
|
|
701
|
-
// Single project — fetch its suites
|
|
702
1848
|
api('/api/db/projects/'+S.project+'/suites').then(function(suites){
|
|
703
1849
|
if(!Array.isArray(suites)||suites.length===0){empty.style.display='block';empty.querySelector('p').textContent='No test suites found for this project.';return}
|
|
704
1850
|
empty.style.display='none';
|
|
1851
|
+
$('#badgeSuites').textContent=suites.length;
|
|
705
1852
|
renderSuiteCards(grid,suites,S.project);
|
|
706
1853
|
}).catch(function(){});
|
|
1854
|
+
api('/api/db/projects/'+S.project+'/modules').then(function(modules){
|
|
1855
|
+
renderModules(moduleSection,modules);
|
|
1856
|
+
}).catch(function(){});
|
|
707
1857
|
} else {
|
|
708
|
-
// All projects — fetch each project's suites
|
|
709
1858
|
api('/api/db/projects').then(function(projects){
|
|
710
1859
|
if(!Array.isArray(projects)||projects.length===0){empty.style.display='block';empty.querySelector('p').textContent='No projects registered yet.';return}
|
|
711
|
-
var loaded=0,hasAny=false;
|
|
1860
|
+
var loaded=0,hasAny=false,totalSuites=0;
|
|
712
1861
|
projects.forEach(function(p){
|
|
713
1862
|
api('/api/db/projects/'+p.id+'/suites').then(function(suites){
|
|
714
1863
|
loaded++;
|
|
715
1864
|
if(Array.isArray(suites)&&suites.length>0){
|
|
716
|
-
hasAny=true;
|
|
1865
|
+
hasAny=true;totalSuites+=suites.length;
|
|
717
1866
|
var label=el('div',{style:'grid-column:1/-1;font-family:var(--sans);font-size:13px;font-weight:600;margin-top:'+(grid.children.length?'16':'0')+'px;padding-bottom:6px;border-bottom:1px solid var(--border);color:var(--text2)'},p.name);
|
|
718
1867
|
grid.appendChild(label);
|
|
719
1868
|
renderSuiteCards(grid,suites,p.id);
|
|
720
1869
|
}
|
|
721
|
-
if(loaded===projects.length
|
|
1870
|
+
if(loaded===projects.length){
|
|
1871
|
+
$('#badgeSuites').textContent=totalSuites;
|
|
1872
|
+
if(!hasAny){empty.style.display='block';empty.querySelector('p').textContent='No test suites found.'}
|
|
1873
|
+
}
|
|
722
1874
|
}).catch(function(){loaded++;});
|
|
723
1875
|
});
|
|
724
1876
|
}).catch(function(){});
|
|
725
1877
|
}
|
|
726
1878
|
}
|
|
1879
|
+
|
|
1880
|
+
function renderProjectAccordion(container,project,suites){
|
|
1881
|
+
var totalTests=suites.reduce(function(sum,s){return sum+(s.testCount||0)},0);
|
|
1882
|
+
var body=el('div',{className:'project-accordion-body'});
|
|
1883
|
+
var innerGrid=el('div',{className:'suite-grid'});
|
|
1884
|
+
renderSuiteCards(innerGrid,suites,project.id);
|
|
1885
|
+
body.appendChild(innerGrid);
|
|
1886
|
+
|
|
1887
|
+
var header=el('div',{className:'project-accordion-header'},[
|
|
1888
|
+
el('span',{className:'project-accordion-chevron'},'\u25B6'),
|
|
1889
|
+
el('span',{className:'project-accordion-name'},project.name),
|
|
1890
|
+
el('div',{className:'project-accordion-meta'},[
|
|
1891
|
+
el('span',{className:'project-accordion-badge'},suites.length+(suites.length===1?' suite':' suites')),
|
|
1892
|
+
el('span',{className:'project-accordion-badge'},totalTests+(totalTests===1?' test':' tests'))
|
|
1893
|
+
])
|
|
1894
|
+
]);
|
|
1895
|
+
|
|
1896
|
+
var wrapper=el('div',{className:'project-accordion'},[header,body]);
|
|
1897
|
+
header.addEventListener('click',function(){wrapper.classList.toggle('open')});
|
|
1898
|
+
container.appendChild(wrapper);
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/* ── Suite Modal ── */
|
|
1902
|
+
var _suiteCache={};
|
|
1903
|
+
|
|
1904
|
+
function openSuiteModal(suiteName,projectId){
|
|
1905
|
+
var overlay=$('#suiteModalOverlay');
|
|
1906
|
+
var body=$('#suiteModalBody');
|
|
1907
|
+
$('#suiteModalName').textContent=suiteName;
|
|
1908
|
+
$('#suiteModalFile').textContent=suiteName+'.json';
|
|
1909
|
+
body.textContent='';
|
|
1910
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'Loading\u2026'));
|
|
1911
|
+
overlay.classList.add('open');
|
|
1912
|
+
|
|
1913
|
+
$('#suiteModalRun').onclick=function(){triggerRun(suiteName,projectId)};
|
|
1914
|
+
$('#suiteModalClose').onclick=function(){overlay.classList.remove('open')};
|
|
1915
|
+
overlay.addEventListener('click',function(e){if(e.target===overlay)overlay.classList.remove('open')});
|
|
1916
|
+
|
|
1917
|
+
var cacheKey=projectId+'::'+suiteName;
|
|
1918
|
+
var p=_suiteCache[cacheKey]||api('/api/db/projects/'+projectId+'/suites/'+encodeURIComponent(suiteName));
|
|
1919
|
+
_suiteCache[cacheKey]=p;
|
|
1920
|
+
p.then(function(data){
|
|
1921
|
+
body.textContent='';
|
|
1922
|
+
if(!data||!data.tests||!data.tests.length){
|
|
1923
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'No tests found'));
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
data.tests.forEach(function(test){
|
|
1927
|
+
var actionsDiv=el('div',{className:'suite-modal-test-actions'});
|
|
1928
|
+
(test.actions||[]).forEach(function(a,i){
|
|
1929
|
+
var detailContent;
|
|
1930
|
+
if(a.selector&&(a.value||a.text)){
|
|
1931
|
+
detailContent=[el('span',{className:'step-sel'},a.selector),el('span',{className:'step-arrow'},'\u2192'),el('span',{className:'step-val'},a.text||a.value)];
|
|
1932
|
+
} else {
|
|
1933
|
+
detailContent=a.selector||a.value||a.text||'';
|
|
1934
|
+
}
|
|
1935
|
+
actionsDiv.appendChild(el('div',{className:'suite-modal-step'},[
|
|
1936
|
+
el('span',{className:'suite-modal-step-num'},String(i+1)),
|
|
1937
|
+
el('span',{className:'suite-modal-step-type'},a.type),
|
|
1938
|
+
el('span',{className:'suite-modal-step-detail'},detailContent)
|
|
1939
|
+
]));
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
var header=el('div',{className:'suite-modal-test-header'},[
|
|
1943
|
+
el('span',{className:'suite-modal-test-chevron'},'\u25B6'),
|
|
1944
|
+
el('span',{className:'suite-modal-test-name'},test.name),
|
|
1945
|
+
el('span',{className:'suite-modal-test-badge'},(test.actions||[]).length+' actions')
|
|
1946
|
+
]);
|
|
1947
|
+
|
|
1948
|
+
var testEl=el('div',{className:'suite-modal-test'},[header,actionsDiv]);
|
|
1949
|
+
if(test.expect){
|
|
1950
|
+
var expectText=Array.isArray(test.expect)?test.expect.join(', '):test.expect;
|
|
1951
|
+
var expectEl=el('div',{className:'suite-modal-expect'},[
|
|
1952
|
+
el('span',{className:'suite-modal-expect-label'},'Expect:'),
|
|
1953
|
+
document.createTextNode(expectText)
|
|
1954
|
+
]);
|
|
1955
|
+
testEl.insertBefore(expectEl,actionsDiv);
|
|
1956
|
+
}
|
|
1957
|
+
header.addEventListener('click',function(){testEl.classList.toggle('open')});
|
|
1958
|
+
body.appendChild(testEl);
|
|
1959
|
+
});
|
|
1960
|
+
}).catch(function(){
|
|
1961
|
+
body.textContent='';
|
|
1962
|
+
body.appendChild(el('div',{className:'suite-modal-loading',style:'color:var(--red)'},'Failed to load suite'));
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
|
|
727
1966
|
function renderSuiteCards(container,suites,projectId){
|
|
728
1967
|
suites.forEach(function(s){
|
|
729
1968
|
var tests=el('ul',{className:'suite-card-tests'});
|
|
730
|
-
(s.tests||[]).forEach(function(t){tests.appendChild(el('li',null,t))});
|
|
731
1969
|
var pid=projectId;
|
|
732
|
-
|
|
733
|
-
el('
|
|
1970
|
+
(s.tests||[]).forEach(function(t){
|
|
1971
|
+
var li=el('li',null,t);
|
|
1972
|
+
li.addEventListener('click',function(e){
|
|
1973
|
+
e.stopPropagation();
|
|
1974
|
+
var existing=li.querySelector('.suite-test-steps');
|
|
1975
|
+
if(existing){existing.remove();li.classList.remove('expanded');return}
|
|
1976
|
+
tests.querySelectorAll('.suite-test-steps').forEach(function(d){d.remove()});
|
|
1977
|
+
tests.querySelectorAll('li.expanded').forEach(function(l){l.classList.remove('expanded')});
|
|
1978
|
+
var stepsDiv=el('div',{className:'suite-test-steps'});
|
|
1979
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--text3);font-size:10px'},'Loading...'));
|
|
1980
|
+
li.appendChild(stepsDiv);
|
|
1981
|
+
li.classList.add('expanded');
|
|
1982
|
+
var cacheKey=pid+'::'+s.name;
|
|
1983
|
+
var p=_suiteCache[cacheKey]||api('/api/db/projects/'+pid+'/suites/'+encodeURIComponent(s.name));
|
|
1984
|
+
_suiteCache[cacheKey]=p;
|
|
1985
|
+
p.then(function(data){
|
|
1986
|
+
stepsDiv.textContent='';
|
|
1987
|
+
var test=(data.tests||[]).find(function(x){return x.name===t});
|
|
1988
|
+
if(!test||!test.actions||!test.actions.length){
|
|
1989
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--text3);font-size:10px'},'No actions'));
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
if(test.serial){
|
|
1993
|
+
var sb=el('span',{className:'serial-badge'},'Serial');
|
|
1994
|
+
li.insertBefore(sb,li.querySelector('.suite-test-steps'));
|
|
1995
|
+
}
|
|
1996
|
+
test.actions.forEach(function(a,i){
|
|
1997
|
+
var detail=a.selector||a.value||a.text||'';
|
|
1998
|
+
if(a.selector&&(a.value||a.text))detail=a.selector+' \u2192 '+(a.text||a.value);
|
|
1999
|
+
stepsDiv.appendChild(el('div',{className:'lt-step'},[
|
|
2000
|
+
el('span',{className:'step-icon',style:'color:var(--text3)'},String(i+1)),
|
|
2001
|
+
el('span',{className:'step-type'},a.type),
|
|
2002
|
+
el('span',{className:'step-detail'},detail)
|
|
2003
|
+
]));
|
|
2004
|
+
});
|
|
2005
|
+
}).catch(function(){
|
|
2006
|
+
stepsDiv.textContent='';
|
|
2007
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--red);font-size:10px'},'Failed to load'));
|
|
2008
|
+
});
|
|
2009
|
+
});
|
|
2010
|
+
tests.appendChild(li);
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
var cardHead=el('div',{className:'suite-card-head',style:'cursor:pointer'},[
|
|
2014
|
+
el('div',{className:'suite-card-icon'},'\u25B6'),
|
|
2015
|
+
el('div',{className:'suite-card-info'},[
|
|
734
2016
|
el('div',{className:'suite-card-name'},s.name),
|
|
735
|
-
el('
|
|
2017
|
+
el('div',{className:'suite-card-file'},s.file||s.name+'.json')
|
|
736
2018
|
]),
|
|
737
|
-
|
|
738
|
-
|
|
2019
|
+
el('div',{className:'suite-card-count'},[
|
|
2020
|
+
el('div',{className:'suite-card-count-num'},String(s.testCount||0)),
|
|
2021
|
+
el('div',{className:'suite-card-count-lbl'},'tests')
|
|
2022
|
+
])
|
|
2023
|
+
]);
|
|
2024
|
+
(function(name,projId){
|
|
2025
|
+
cardHead.addEventListener('click',function(){openSuiteModal(name,projId)});
|
|
2026
|
+
})(s.name,pid);
|
|
2027
|
+
|
|
2028
|
+
var card=el('div',{className:'suite-card'},[
|
|
2029
|
+
cardHead,
|
|
2030
|
+
el('div',{className:'suite-card-body'},[tests]),
|
|
2031
|
+
el('div',{className:'suite-card-footer'},[
|
|
2032
|
+
el('button',{className:'btn sm primary',onclick:function(){triggerRun(s.name,pid)}},'Run Suite')
|
|
2033
|
+
])
|
|
739
2034
|
]);
|
|
740
2035
|
container.appendChild(card);
|
|
741
2036
|
});
|
|
742
2037
|
}
|
|
743
2038
|
|
|
744
|
-
|
|
2039
|
+
function renderModules(container,modules){
|
|
2040
|
+
if(!Array.isArray(modules)||modules.length===0)return;
|
|
2041
|
+
var title=el('div',{className:'module-section-title'},[
|
|
2042
|
+
el('span',{className:'mod-icon'},'\u{1F9E9}'),
|
|
2043
|
+
document.createTextNode(' Reusable Modules ('+modules.length+')')
|
|
2044
|
+
]);
|
|
2045
|
+
container.appendChild(title);
|
|
2046
|
+
var grid=el('div',{className:'module-grid'});
|
|
2047
|
+
modules.forEach(function(m){
|
|
2048
|
+
var paramsEl=null;
|
|
2049
|
+
if(m.params&&m.params.length){
|
|
2050
|
+
var items=m.params.map(function(p){return el('li',null,typeof p==='string'?p:(p.name||String(p)))});
|
|
2051
|
+
paramsEl=el('ul',{className:'module-card-params'},items);
|
|
2052
|
+
}
|
|
2053
|
+
var card=el('div',{className:'module-card'},[
|
|
2054
|
+
el('div',{className:'module-card-name'},m.name),
|
|
2055
|
+
m.description?el('div',{className:'module-card-desc'},m.description):null,
|
|
2056
|
+
el('div',{className:'module-card-meta'},[
|
|
2057
|
+
el('span',null,m.actionCount+' actions'),
|
|
2058
|
+
m.params&&m.params.length?el('span',null,m.params.length+' params'):null
|
|
2059
|
+
]),
|
|
2060
|
+
paramsEl
|
|
2061
|
+
]);
|
|
2062
|
+
grid.appendChild(card);
|
|
2063
|
+
});
|
|
2064
|
+
container.appendChild(grid);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/* ── Variables ── */
|
|
2068
|
+
function refreshVariables(){
|
|
2069
|
+
var container=$('#variablesContainer'),empty=$('#variablesEmpty');
|
|
2070
|
+
container.textContent='';
|
|
2071
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to manage variables.';return}
|
|
2072
|
+
api('/api/db/projects/'+S.project+'/variables').then(function(vars){
|
|
2073
|
+
if(!Array.isArray(vars)||!vars.length){empty.style.display='block';empty.querySelector('p').textContent='No variables set. Add variables to use {{var.KEY}} in your tests.';return}
|
|
2074
|
+
empty.style.display='none';
|
|
2075
|
+
renderVariables(vars);
|
|
2076
|
+
}).catch(function(){empty.style.display='block'});
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function renderVariables(vars){
|
|
2080
|
+
var container=$('#variablesContainer');
|
|
2081
|
+
var tbl=el('table',{className:'var-table'});
|
|
2082
|
+
var thead=document.createElement('thead');
|
|
2083
|
+
var hr=document.createElement('tr');
|
|
2084
|
+
['Key','Value','Scope','Actions'].forEach(function(h){hr.appendChild(el('th',null,h))});
|
|
2085
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
2086
|
+
var tbody=document.createElement('tbody');
|
|
2087
|
+
vars.forEach(function(v){
|
|
2088
|
+
var tr=document.createElement('tr');
|
|
2089
|
+
tr.appendChild(el('td',null,[el('code',null,v.key)]));
|
|
2090
|
+
tr.appendChild(el('td',{style:'max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},v.is_secret?'\u2022\u2022\u2022\u2022\u2022\u2022':v.value));
|
|
2091
|
+
tr.appendChild(el('td',{style:'color:var(--text3)'},v.scope||'project'));
|
|
2092
|
+
var delBtn=el('button',{className:'btn sm danger',onclick:function(){
|
|
2093
|
+
if(!confirm('Delete variable "'+v.key+'"?'))return;
|
|
2094
|
+
fetch('/api/db/projects/'+S.project+'/variables/'+encodeURIComponent(v.key),{method:'DELETE'}).then(function(){refreshVariables();showToast('Variable deleted','success')}).catch(function(){showToast('Delete failed','error')});
|
|
2095
|
+
}},'\u2715');
|
|
2096
|
+
tr.appendChild(el('td',null,[delBtn]));
|
|
2097
|
+
tbody.appendChild(tr);
|
|
2098
|
+
});
|
|
2099
|
+
tbl.appendChild(tbody);
|
|
2100
|
+
container.appendChild(tbl);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/* ── Variable Add Form ── */
|
|
2104
|
+
$('#btnAddVar').addEventListener('click',function(){
|
|
2105
|
+
var form=$('#varAddForm');
|
|
2106
|
+
if(form.style.display==='none'){
|
|
2107
|
+
form.style.display='';
|
|
2108
|
+
form.textContent='';
|
|
2109
|
+
var keyInput=el('input',{type:'text',placeholder:'KEY',style:'margin-right:8px;width:120px'});
|
|
2110
|
+
var valInput=el('input',{type:'text',placeholder:'Value',style:'margin-right:8px;width:200px'});
|
|
2111
|
+
var secretCheck=el('input',{type:'checkbox',style:'margin-right:4px'});
|
|
2112
|
+
var saveBtn=el('button',{className:'btn sm primary',onclick:function(){
|
|
2113
|
+
var k=keyInput.value.trim(),v=valInput.value;
|
|
2114
|
+
if(!k){showToast('Key is required','error');return}
|
|
2115
|
+
if(!S.project){showToast('Select a project first','error');return}
|
|
2116
|
+
fetch('/api/db/projects/'+S.project+'/variables',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:k,value:v,is_secret:secretCheck.checked})}).then(function(r){return r.json()}).then(function(){
|
|
2117
|
+
form.style.display='none';refreshVariables();showToast('Variable saved','success');
|
|
2118
|
+
}).catch(function(){showToast('Save failed','error')});
|
|
2119
|
+
}},'Save');
|
|
2120
|
+
var cancelBtn=el('button',{className:'btn sm',onclick:function(){form.style.display='none'}},'Cancel');
|
|
2121
|
+
form.appendChild(el('div',{className:'var-add-form',style:'display:flex;align-items:center;gap:8px;flex-wrap:wrap'},[
|
|
2122
|
+
keyInput,valInput,
|
|
2123
|
+
el('label',{style:'font-size:11px;color:var(--text2);display:flex;align-items:center;gap:4px'},[secretCheck,document.createTextNode('Secret')]),
|
|
2124
|
+
saveBtn,cancelBtn
|
|
2125
|
+
]));
|
|
2126
|
+
} else {
|
|
2127
|
+
form.style.display='none';
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
2132
|
+
|
|
2133
|
+
|
|
2134
|
+
/* ── view-runs.js ── */
|
|
2135
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2136
|
+
Runs View — History + Screenshots + Learnings (inner tabs)
|
|
2137
|
+
══════════════════════════════════════════════════════════════════ */
|
|
2138
|
+
|
|
2139
|
+
/* ── Filters ── */
|
|
2140
|
+
$$('.filter-btn').forEach(function(btn){
|
|
2141
|
+
btn.addEventListener('click',function(){
|
|
2142
|
+
$$('.filter-btn').forEach(function(b){b.classList.remove('active')});
|
|
2143
|
+
btn.classList.add('active');
|
|
2144
|
+
S.runFilter.status=btn.dataset.filter;
|
|
2145
|
+
applyRunFilters();
|
|
2146
|
+
});
|
|
2147
|
+
});
|
|
2148
|
+
$('#runSearchInput').addEventListener('input',function(){
|
|
2149
|
+
S.runFilter.search=this.value.trim().toLowerCase();
|
|
2150
|
+
applyRunFilters();
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
var _allRunRows=[];
|
|
2154
|
+
function applyRunFilters(){
|
|
2155
|
+
_allRunRows.forEach(function(item){
|
|
2156
|
+
var show=true;
|
|
2157
|
+
var r=item.data;
|
|
2158
|
+
if(S.runFilter.status!=='all'){
|
|
2159
|
+
var total=r.total||0;var passed=r.passed||0;var failed=r.failed||0;
|
|
2160
|
+
if(S.runFilter.status==='pass'&&(failed>0||total===0))show=false;
|
|
2161
|
+
if(S.runFilter.status==='fail'&&failed===0)show=false;
|
|
2162
|
+
if(S.runFilter.status==='mixed'&&(failed===0||passed===0))show=false;
|
|
2163
|
+
}
|
|
2164
|
+
if(show&&S.runFilter.search){
|
|
2165
|
+
var suite=(r.suite_name||'all').toLowerCase();
|
|
2166
|
+
var proj=(r.project_name||'').toLowerCase();
|
|
2167
|
+
if(suite.indexOf(S.runFilter.search)===-1&&proj.indexOf(S.runFilter.search)===-1)show=false;
|
|
2168
|
+
}
|
|
2169
|
+
item.tr.style.display=show?'':'none';
|
|
2170
|
+
if(item.detailTr)item.detailTr.style.display=show?'':'none';
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
function renderRunsHealthBanner(){
|
|
2175
|
+
var banner=$('#runsHealthBanner');
|
|
2176
|
+
banner.textContent='';
|
|
2177
|
+
var url=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
2178
|
+
fetch(url).then(function(r){return r.json()}).then(function(h){
|
|
2179
|
+
if(!h||!h.passRate)return;
|
|
2180
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2181
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2182
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'dim';
|
|
2183
|
+
var deltaStr=h.trendDelta!==0?(h.trendDelta>0?'+':'')+h.trendDelta+'%':'';
|
|
2184
|
+
|
|
2185
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2186
|
+
el('div',{className:'hb-val '+rateColor},h.passRate+'%'),
|
|
2187
|
+
el('div',{className:'hb-lbl'},'Pass Rate'),
|
|
2188
|
+
el('div',{className:'hb-trend '+trendCls},trendIcon+' '+h.passRateTrend+(deltaStr?' ('+deltaStr+')':''))
|
|
2189
|
+
]));
|
|
2190
|
+
if(h.flakyCount>0){
|
|
2191
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2192
|
+
el('div',{className:'hb-val amber'},String(h.flakyCount)),
|
|
2193
|
+
el('div',{className:'hb-lbl'},'Flaky Tests')
|
|
2194
|
+
]));
|
|
2195
|
+
}
|
|
2196
|
+
if(h.topErrorPattern){
|
|
2197
|
+
var cat=h.topErrorPattern.category||h.topErrorPattern.pattern||'unknown';
|
|
2198
|
+
var pat=cat.replace(/-/g,' ').replace(/\b\w/g,function(c){return c.toUpperCase()});
|
|
2199
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2200
|
+
el('div',{className:'hb-val red',style:'font-size:13px'},pat),
|
|
2201
|
+
el('div',{className:'hb-lbl'},'Top Error ('+h.topErrorPattern.count+'x)')
|
|
2202
|
+
]));
|
|
2203
|
+
}
|
|
2204
|
+
banner.appendChild(el('div',{className:'hb-link',onclick:function(){var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
2205
|
+
el('span',null,'\u2192 View Learnings')
|
|
2206
|
+
]));
|
|
2207
|
+
}).catch(function(){});
|
|
2208
|
+
}
|
|
2209
|
+
|
|
745
2210
|
function refreshRuns(){
|
|
2211
|
+
renderRunsHealthBanner();
|
|
746
2212
|
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
747
2213
|
api(url).then(function(rows){
|
|
748
2214
|
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
749
2215
|
chart.textContent='';body.textContent='';
|
|
750
|
-
|
|
2216
|
+
_allRunRows=[];
|
|
2217
|
+
S.highlightedRunIdx=-1;
|
|
2218
|
+
if(!Array.isArray(rows)||rows.length===0){empty.style.display='block';head.parentNode.parentNode.style.display='none';$('#badgeRuns').textContent='0';return}
|
|
751
2219
|
empty.style.display='none';head.parentNode.parentNode.style.display='';
|
|
2220
|
+
$('#badgeRuns').textContent=rows.length;
|
|
752
2221
|
|
|
753
|
-
// Thead
|
|
754
2222
|
var htr=document.createElement('tr');
|
|
755
2223
|
var cols=[];
|
|
756
2224
|
if(!S.project)cols.push('Project');
|
|
@@ -759,7 +2227,6 @@ function refreshRuns(){
|
|
|
759
2227
|
head.textContent='';head.appendChild(htr);
|
|
760
2228
|
var colSpan=cols.length;
|
|
761
2229
|
|
|
762
|
-
// Chart
|
|
763
2230
|
rows.slice(0,40).slice().reverse().forEach(function(r){
|
|
764
2231
|
var rate=parseFloat(r.pass_rate)||0;
|
|
765
2232
|
var color=rate>=90?'var(--green)':rate>=70?'var(--amber)':'var(--red)';
|
|
@@ -768,7 +2235,6 @@ function refreshRuns(){
|
|
|
768
2235
|
chart.appendChild(bar);
|
|
769
2236
|
});
|
|
770
2237
|
|
|
771
|
-
// Rows
|
|
772
2238
|
rows.forEach(function(r){
|
|
773
2239
|
var tr=document.createElement('tr');
|
|
774
2240
|
tr.dataset.runId=r.id;
|
|
@@ -786,12 +2252,14 @@ function refreshRuns(){
|
|
|
786
2252
|
tr.addEventListener('click',function(){toggleDetail(r.id,tr,colSpan)});
|
|
787
2253
|
body.appendChild(tr);
|
|
788
2254
|
|
|
789
|
-
|
|
2255
|
+
var item={tr:tr,data:r,detailTr:null};
|
|
790
2256
|
if(r.id===S.selectedRun){
|
|
791
2257
|
var detailTr=createDetailRow(colSpan);
|
|
792
2258
|
body.appendChild(detailTr);
|
|
793
2259
|
loadDetailInline(r.id,detailTr);
|
|
2260
|
+
item.detailTr=detailTr;
|
|
794
2261
|
}
|
|
2262
|
+
_allRunRows.push(item);
|
|
795
2263
|
});
|
|
796
2264
|
}).catch(function(){});
|
|
797
2265
|
}
|
|
@@ -802,8 +2270,12 @@ function createDetailRow(colSpan){
|
|
|
802
2270
|
var td=document.createElement('td');
|
|
803
2271
|
td.setAttribute('colspan',colSpan);
|
|
804
2272
|
var wrap=el('div',{className:'rd-wrap'});
|
|
805
|
-
var inner=el('div',{className:'rd-inner'}
|
|
806
|
-
|
|
2273
|
+
var inner=el('div',{className:'rd-inner'},[
|
|
2274
|
+
el('div',{style:'color:var(--text3);font-size:11px'},[
|
|
2275
|
+
el('span',{className:'spinner-small'}),
|
|
2276
|
+
document.createTextNode(' Loading...')
|
|
2277
|
+
])
|
|
2278
|
+
]);
|
|
807
2279
|
wrap.appendChild(inner);
|
|
808
2280
|
td.appendChild(wrap);
|
|
809
2281
|
detailTr.appendChild(td);
|
|
@@ -811,7 +2283,6 @@ function createDetailRow(colSpan){
|
|
|
811
2283
|
}
|
|
812
2284
|
|
|
813
2285
|
function toggleDetail(id,clickedTr,colSpan){
|
|
814
|
-
// If already expanded, collapse
|
|
815
2286
|
if(S.selectedRun===id){
|
|
816
2287
|
var existing=clickedTr.nextElementSibling;
|
|
817
2288
|
if(existing&&existing.classList.contains('run-detail-row')){
|
|
@@ -824,7 +2295,6 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
824
2295
|
return;
|
|
825
2296
|
}
|
|
826
2297
|
|
|
827
|
-
// Collapse any other open detail
|
|
828
2298
|
var prevTr=document.querySelector('#runsBody tr.expanded');
|
|
829
2299
|
if(prevTr){
|
|
830
2300
|
prevTr.classList.remove('expanded');
|
|
@@ -836,83 +2306,142 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
836
2306
|
}
|
|
837
2307
|
}
|
|
838
2308
|
|
|
839
|
-
// Expand new
|
|
840
2309
|
S.selectedRun=id;
|
|
841
2310
|
clickedTr.classList.add('expanded');
|
|
842
2311
|
var detailTr=createDetailRow(colSpan);
|
|
843
2312
|
clickedTr.parentNode.insertBefore(detailTr,clickedTr.nextSibling);
|
|
844
|
-
|
|
845
|
-
// Animate open
|
|
846
2313
|
requestAnimationFrame(function(){
|
|
847
2314
|
requestAnimationFrame(function(){
|
|
848
2315
|
var w2=detailTr.querySelector('.rd-wrap');
|
|
849
2316
|
if(w2)w2.classList.add('open');
|
|
850
2317
|
});
|
|
851
2318
|
});
|
|
852
|
-
|
|
853
2319
|
loadDetailInline(id,detailTr);
|
|
854
2320
|
}
|
|
855
2321
|
|
|
2322
|
+
/* ── Run Detail ── */
|
|
856
2323
|
function loadDetailInline(id,detailTr){
|
|
857
2324
|
api('/api/db/runs/'+id).then(function(d){
|
|
858
2325
|
if(d.error)return;
|
|
859
2326
|
var inner=detailTr.querySelector('.rd-inner');
|
|
860
2327
|
inner.textContent='';
|
|
861
|
-
|
|
862
2328
|
var results=d.results||[];
|
|
863
2329
|
|
|
864
|
-
|
|
2330
|
+
var exportBtn=el('div',null,[
|
|
2331
|
+
el('div',{className:'rd-s-label'},'Export'),
|
|
2332
|
+
el('div',{style:'margin-top:4px'},[
|
|
2333
|
+
el('button',{className:'btn sm',onclick:function(e){
|
|
2334
|
+
e.stopPropagation();
|
|
2335
|
+
downloadFile('run-'+id+'.json',JSON.stringify(d,null,2),'application/json');
|
|
2336
|
+
}},'JSON')
|
|
2337
|
+
])
|
|
2338
|
+
]);
|
|
865
2339
|
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
866
2340
|
var summ=el('div',{className:'rd-summary'},[
|
|
867
|
-
el('div',null,[el('div',{className:'rd-s-label'},'Suite'),el('div',{className:'rd-s-val',style:'font-size:
|
|
2341
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Suite'),el('div',{className:'rd-s-val',style:'font-size:14px;color:var(--accent)'},d.suiteName||'all')]),
|
|
868
2342
|
srcBlock,
|
|
869
2343
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
870
2344
|
el('div',null,[el('div',{className:'rd-s-label'},'Passed'),el('div',{className:'rd-s-val',style:'color:var(--green)'},String(d.summary.passed))]),
|
|
871
|
-
el('div',null,[el('div',{className:'rd-s-label'},'Failed'),el('div',{className:'rd-s-val',style:'color:var(--red)'},String(d.summary.failed))]),
|
|
872
|
-
el('div',null,[el('div',{className:'rd-s-label'},'Duration'),el('div',{className:'rd-s-val',style:'font-size:
|
|
2345
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Failed'),el('div',{className:'rd-s-val',style:'color:'+(d.summary.failed>0?'var(--red)':'var(--text3)')},String(d.summary.failed))]),
|
|
2346
|
+
el('div',null,[el('div',{className:'rd-s-label'},'Duration'),el('div',{className:'rd-s-val',style:'font-size:14px;color:var(--text2)'},d.summary.duration||'-')]),
|
|
2347
|
+
exportBtn
|
|
873
2348
|
]);
|
|
874
2349
|
inner.appendChild(summ);
|
|
875
2350
|
|
|
876
|
-
//
|
|
2351
|
+
// Insights
|
|
2352
|
+
var insightsContainer=el('div',{className:'rd-insights'});
|
|
2353
|
+
inner.appendChild(insightsContainer);
|
|
2354
|
+
fetch('/api/db/runs/'+id+'/insights').then(function(r){return r.json()}).then(function(ins){
|
|
2355
|
+
if(!ins||ins.error)return;
|
|
2356
|
+
var items=[];
|
|
2357
|
+
var h=ins.health;
|
|
2358
|
+
if(h){
|
|
2359
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2360
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2361
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'';
|
|
2362
|
+
items.push(el('div',{className:'rd-ins-health'},[
|
|
2363
|
+
el('span',{className:'rd-ins-rate '+rateColor},h.passRate+'%'),
|
|
2364
|
+
el('span',{className:'rd-ins-trend '+trendCls},trendIcon+' '+h.passRateTrend),
|
|
2365
|
+
h.flakyCount>0?el('span',{className:'rd-ins-tag amber'},h.flakyCount+' flaky'):null,
|
|
2366
|
+
h.unstableSelectorCount>0?el('span',{className:'rd-ins-tag red'},h.unstableSelectorCount+' unstable sel.'):null
|
|
2367
|
+
]));
|
|
2368
|
+
}
|
|
2369
|
+
var insights=ins.insights||[];
|
|
2370
|
+
insights.forEach(function(i){
|
|
2371
|
+
var icon=i.type==='new-failure'?'\u2718':i.type==='recovered'?'\u2714':i.type==='flaky'?'\u223C':'!';
|
|
2372
|
+
var cls=i.type==='new-failure'?'red':i.type==='recovered'?'green':i.type==='flaky'?'amber':'';
|
|
2373
|
+
items.push(el('div',{className:'rd-ins-item '+cls},[
|
|
2374
|
+
el('span',{className:'rd-ins-icon'},icon),
|
|
2375
|
+
el('span',null,i.message)
|
|
2376
|
+
]));
|
|
2377
|
+
});
|
|
2378
|
+
if(items.length>0){items.forEach(function(it){insightsContainer.appendChild(it)})}
|
|
2379
|
+
else{insightsContainer.style.display='none'}
|
|
2380
|
+
}).catch(function(){insightsContainer.style.display='none'});
|
|
2381
|
+
|
|
2382
|
+
// Pool distribution bar
|
|
2383
|
+
var histPoolTests={};
|
|
2384
|
+
results.forEach(function(r){if(!r.poolUrl)return;histPoolTests[r.name]={poolUrl:r.poolUrl,success:r.success}});
|
|
2385
|
+
var histPoolDist=buildPoolDistribution(histPoolTests);
|
|
2386
|
+
if(histPoolDist)inner.appendChild(histPoolDist);
|
|
2387
|
+
|
|
877
2388
|
results.forEach(function(r){
|
|
878
2389
|
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
879
2390
|
var flaky=r.success&&r.attempt>1;
|
|
2391
|
+
var state=flaky?'flaky':(r.success?'pass':'fail');
|
|
880
2392
|
|
|
881
|
-
// Header: badge + name + duration
|
|
882
2393
|
var badges=el('div',{style:'display:flex;gap:6px;align-items:center;flex-shrink:0'});
|
|
883
2394
|
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
884
2395
|
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
885
2396
|
|
|
886
|
-
var
|
|
887
|
-
|
|
888
|
-
el('div',{className:'rd-test-name'},r.name),
|
|
889
|
-
el('div',{className:'rd-test-dur'},d2)
|
|
890
|
-
]);
|
|
891
|
-
|
|
892
|
-
// Body content
|
|
2397
|
+
var poolEl=r.poolUrl?el('span',{className:'pool-badge'},r.poolUrl.replace('ws://','').replace('wss://','')) :null;
|
|
2398
|
+
var head=el('div',{className:'rd-test-head'},[badges,el('div',{className:'rd-test-name'},[document.createTextNode(r.name),poolEl]),el('div',{className:'rd-test-dur'},d2)]);
|
|
893
2399
|
var body=el('div',{className:'rd-test-body'});
|
|
894
2400
|
|
|
895
|
-
|
|
896
|
-
if(r.
|
|
897
|
-
|
|
2401
|
+
if(r.maxAttempts>1){body.appendChild(el('div',{className:'rd-retries'},'Attempt '+r.attempt+' of '+r.maxAttempts))}
|
|
2402
|
+
if(r.error){
|
|
2403
|
+
var errDiv=el('div',{className:'rd-error-msg'});
|
|
2404
|
+
errDiv.appendChild(document.createTextNode(r.error));
|
|
2405
|
+
errDiv.appendChild(makeCopyBtn(r.error));
|
|
2406
|
+
body.appendChild(errDiv);
|
|
898
2407
|
}
|
|
899
2408
|
|
|
900
|
-
//
|
|
901
|
-
if(r.
|
|
902
|
-
|
|
2409
|
+
// Actions panel
|
|
2410
|
+
if(r.actions&&r.actions.length){
|
|
2411
|
+
var passCount=r.actions.filter(function(a){return a.success}).length;
|
|
2412
|
+
var failCount=r.actions.length-passCount;
|
|
2413
|
+
var actHead=el('div',{className:'rd-net-head'},[
|
|
2414
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
2415
|
+
el('span',{className:'net-title'},'Actions'),
|
|
2416
|
+
el('div',{className:'net-stats'},[
|
|
2417
|
+
el('span',{className:'net-stat'},[document.createTextNode('Steps: '),el('strong',null,String(r.actions.length))]),
|
|
2418
|
+
failCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Failed: '),el('strong',null,String(failCount))]):null
|
|
2419
|
+
])
|
|
2420
|
+
]);
|
|
2421
|
+
var actBody=el('div',{className:'rd-net-body',style:'padding:8px 14px'});
|
|
2422
|
+
r.actions.forEach(function(a){
|
|
2423
|
+
var label=a.narrative||a.type;
|
|
2424
|
+
var durText=a.duration!=null?dur(a.duration):'';
|
|
2425
|
+
var retryBadge=null;
|
|
2426
|
+
if(a.actionRetries&&a.actionRetries>0){
|
|
2427
|
+
retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px'},'\u21BB x'+a.actionRetries);
|
|
2428
|
+
}
|
|
2429
|
+
actBody.appendChild(el('div',{className:'lt-step'},[
|
|
2430
|
+
el('span',{className:'step-icon '+(a.success?'ok':'fail')},a.success?'\u2714':'\u2718'),
|
|
2431
|
+
el('span',{className:'step-detail',style:'flex:1'},label),
|
|
2432
|
+
retryBadge,
|
|
2433
|
+
el('span',{className:'step-dur'},durText)
|
|
2434
|
+
]));
|
|
2435
|
+
});
|
|
2436
|
+
actHead.addEventListener('click',function(){actHead.classList.toggle('open')});
|
|
2437
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[actHead,actBody]));
|
|
903
2438
|
}
|
|
904
2439
|
|
|
905
|
-
// Screenshots
|
|
2440
|
+
// Screenshots
|
|
906
2441
|
var shots=[];
|
|
907
2442
|
var hashes=r.screenshotHashes||{};
|
|
908
|
-
(r.screenshots||[]).forEach(function(p){
|
|
909
|
-
|
|
910
|
-
shots.push({path:p,label:fname,type:'screenshot',hash:hashes[p]||null});
|
|
911
|
-
});
|
|
912
|
-
if(r.errorScreenshot){
|
|
913
|
-
var ename=r.errorScreenshot.split('/').pop();
|
|
914
|
-
shots.push({path:r.errorScreenshot,label:ename,type:'error',hash:hashes[r.errorScreenshot]||null});
|
|
915
|
-
}
|
|
2443
|
+
(r.screenshots||[]).forEach(function(p){shots.push({path:p,label:p.split('/').pop(),type:'screenshot',hash:hashes[p]||null})});
|
|
2444
|
+
if(r.errorScreenshot){shots.push({path:r.errorScreenshot,label:r.errorScreenshot.split('/').pop(),type:'error',hash:hashes[r.errorScreenshot]||null})}
|
|
916
2445
|
if(shots.length){
|
|
917
2446
|
var shotsWrap=el('div',{className:'rd-shots'});
|
|
918
2447
|
shots.forEach(function(s){
|
|
@@ -921,66 +2450,69 @@ function loadDetailInline(id,detailTr){
|
|
|
921
2450
|
var capEl=el('div',{className:'rd-shot-cap'},[el('span',{className:'cap-name'},s.label)]);
|
|
922
2451
|
if(s.hash){capEl.appendChild(createHashBadge(s.hash))}
|
|
923
2452
|
else{(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,s.path)}
|
|
924
|
-
|
|
925
|
-
img,capEl
|
|
926
|
-
]);
|
|
927
|
-
shotsWrap.appendChild(shotEl);
|
|
2453
|
+
shotsWrap.appendChild(el('div',{className:'rd-shot'+(s.type==='error'?' err-shot':''),onclick:function(e){e.stopPropagation();openModal(src)}},[img,capEl]));
|
|
928
2454
|
});
|
|
929
2455
|
body.appendChild(shotsWrap);
|
|
930
2456
|
}
|
|
931
2457
|
|
|
932
|
-
// Console logs
|
|
2458
|
+
// Console logs
|
|
933
2459
|
var cIssues=(r.consoleLogs||[]).filter(function(l){return l.type==='error'||l.type==='warn'||l.type==='warning'});
|
|
934
2460
|
if(cIssues.length){
|
|
935
|
-
var
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
2461
|
+
var cErrors=cIssues.filter(function(l){return l.type==='error'}).length;
|
|
2462
|
+
var cWarns=cIssues.length-cErrors;
|
|
2463
|
+
var conHead=el('div',{className:'rd-net-head'},[
|
|
2464
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
2465
|
+
el('span',{className:'net-title'},'Console'),
|
|
2466
|
+
el('div',{className:'net-stats'},[
|
|
2467
|
+
cErrors?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(cErrors))]):null,
|
|
2468
|
+
cWarns?el('span',{className:'net-stat'},[document.createTextNode('Warnings: '),el('strong',null,String(cWarns))]):null
|
|
2469
|
+
]),
|
|
2470
|
+
makeCopyBtn(function(){return cIssues.map(function(l){return '['+l.type+'] '+l.text}).join('\n')})
|
|
2471
|
+
]);
|
|
2472
|
+
var conBody=el('div',{className:'rd-net-body'});
|
|
2473
|
+
cIssues.forEach(function(l){conBody.appendChild(el('div',{className:'rd-log-item '+l.type},'['+l.type+'] '+l.text))});
|
|
2474
|
+
conHead.addEventListener('click',function(){conHead.classList.toggle('open')});
|
|
2475
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[conHead,conBody]));
|
|
941
2476
|
}
|
|
942
2477
|
|
|
943
2478
|
// Network errors
|
|
944
2479
|
if(r.networkErrors&&r.networkErrors.length){
|
|
945
|
-
var
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
2480
|
+
var neHead=el('div',{className:'rd-net-head'},[
|
|
2481
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
2482
|
+
el('span',{className:'net-title'},'Network Errors'),
|
|
2483
|
+
el('div',{className:'net-stats'},[el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(r.networkErrors.length))])]),
|
|
2484
|
+
makeCopyBtn(function(){return r.networkErrors.map(function(ne){return '['+ne.error+'] '+ne.url}).join('\n')})
|
|
2485
|
+
]);
|
|
2486
|
+
var neBody=el('div',{className:'rd-net-body'});
|
|
2487
|
+
r.networkErrors.forEach(function(ne){neBody.appendChild(el('div',{className:'rd-log-item error'},'['+ne.error+'] '+ne.url))});
|
|
2488
|
+
neHead.addEventListener('click',function(){neHead.classList.toggle('open')});
|
|
2489
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[neHead,neBody]));
|
|
951
2490
|
}
|
|
952
2491
|
|
|
953
|
-
// Network
|
|
2492
|
+
// Network panel
|
|
954
2493
|
if(r.networkLogs&&r.networkLogs.length){
|
|
955
2494
|
var errCount=r.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
956
|
-
var
|
|
957
|
-
var netToggle=el('div',{className:'rd-net-toggle'},[
|
|
2495
|
+
var netHead=el('div',{className:'rd-net-head'},[
|
|
958
2496
|
el('span',{className:'net-arrow'},'\u25B6'),
|
|
959
|
-
el('span',{},
|
|
2497
|
+
el('span',{className:'net-title'},'Network Requests'),
|
|
2498
|
+
el('div',{className:'net-stats'},[
|
|
2499
|
+
el('span',{className:'net-stat'},[document.createTextNode('Total: '),el('strong',null,String(r.networkLogs.length))]),
|
|
2500
|
+
errCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(errCount))]):null
|
|
2501
|
+
])
|
|
960
2502
|
]);
|
|
961
|
-
var
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
});
|
|
967
|
-
netToggle.addEventListener('click',function(){
|
|
968
|
-
netToggle.classList.toggle('open');
|
|
969
|
-
});
|
|
970
|
-
body.appendChild(netToggle);
|
|
971
|
-
body.appendChild(netList);
|
|
2503
|
+
var netCols=el('div',{className:'rd-net-cols'},[el('span',{className:'col-e'},''),el('span',{className:'col-m'},'Method'),el('span',{className:'col-s'},'Status'),el('span',{className:'col-u'},'URL'),el('span',{className:'col-d'},'Time')]);
|
|
2504
|
+
var netBody=el('div',{className:'rd-net-body'},[netCols]);
|
|
2505
|
+
r.networkLogs.forEach(function(n){var built=buildNetRow(n);netBody.appendChild(built.row);if(built.detail)netBody.appendChild(built.detail)});
|
|
2506
|
+
netHead.addEventListener('click',function(){netHead.classList.toggle('open')});
|
|
2507
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[netHead,netBody]));
|
|
972
2508
|
}
|
|
973
2509
|
|
|
974
|
-
|
|
975
|
-
inner.appendChild(testCard);
|
|
2510
|
+
inner.appendChild(el('div',{className:'rd-test '+state},[head,body]));
|
|
976
2511
|
});
|
|
977
2512
|
|
|
978
|
-
// Re-trigger open animation if not yet open
|
|
979
2513
|
var w=detailTr.querySelector('.rd-wrap');
|
|
980
|
-
if(w&&!w.classList.contains('open')){
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
}).catch(function(err){
|
|
2514
|
+
if(w&&!w.classList.contains('open')){requestAnimationFrame(function(){w.classList.add('open')})}
|
|
2515
|
+
}).catch(function(){
|
|
984
2516
|
var inner=detailTr.querySelector('.rd-inner');
|
|
985
2517
|
if(inner)inner.textContent='Failed to load run detail';
|
|
986
2518
|
});
|
|
@@ -990,25 +2522,21 @@ function loadDetailInline(id,detailTr){
|
|
|
990
2522
|
function refreshScreenshots(){
|
|
991
2523
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
992
2524
|
gal.textContent='';
|
|
993
|
-
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to view screenshots.';return}
|
|
2525
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to view screenshots.';$('#badgeScreenshots').textContent='-';return}
|
|
994
2526
|
api('/api/db/projects/'+S.project+'/screenshots').then(function(files){
|
|
995
|
-
if(!Array.isArray(files)||!files.length){empty.style.display='block';empty.querySelector('p').textContent='No screenshots for this project.';return}
|
|
2527
|
+
if(!Array.isArray(files)||!files.length){empty.style.display='block';empty.querySelector('p').textContent='No screenshots for this project.';$('#badgeScreenshots').textContent='0';return}
|
|
996
2528
|
empty.style.display='none';
|
|
2529
|
+
$('#badgeScreenshots').textContent=files.length;
|
|
997
2530
|
files.forEach(function(f){
|
|
998
2531
|
var src='/api/image?path='+encodeURIComponent(f.path);
|
|
999
2532
|
var img=document.createElement('img');img.src=src;img.alt=f.name;img.loading='lazy';
|
|
1000
2533
|
var capEl=el('div',{className:'cap'},[el('span',{className:'cap-name'},f.name)]);
|
|
1001
|
-
(function(c,fp){
|
|
1002
|
-
|
|
1003
|
-
})(capEl,f.path);
|
|
1004
|
-
var item=el('div',{className:'gallery-item',onclick:function(){openModal(src)}},[
|
|
1005
|
-
img,capEl
|
|
1006
|
-
]);gal.appendChild(item);
|
|
2534
|
+
(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,f.path);
|
|
2535
|
+
gal.appendChild(el('div',{className:'gallery-item',onclick:function(){openModal(src)}},[img,capEl]));
|
|
1007
2536
|
});
|
|
1008
2537
|
}).catch(function(){});
|
|
1009
2538
|
}
|
|
1010
2539
|
|
|
1011
|
-
/* ── Screenshot Hash Search ── */
|
|
1012
2540
|
function searchByHash(){
|
|
1013
2541
|
var container=$('#ssSearchResult');
|
|
1014
2542
|
container.textContent='';
|
|
@@ -1020,67 +2548,182 @@ function searchByHash(){
|
|
|
1020
2548
|
return;
|
|
1021
2549
|
}
|
|
1022
2550
|
fetch('/api/screenshot-hash/'+hash).then(function(res){
|
|
1023
|
-
if(!res.ok){
|
|
1024
|
-
container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
2551
|
+
if(!res.ok){container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));return}
|
|
1027
2552
|
return res.blob();
|
|
1028
2553
|
}).then(function(blob){
|
|
1029
2554
|
if(!blob)return;
|
|
1030
2555
|
var url=URL.createObjectURL(blob);
|
|
1031
|
-
var wrap=el('div',{className:'ss-search-result'},[
|
|
1032
|
-
|
|
1033
|
-
createHashBadge(hash),
|
|
1034
|
-
el('span',{},'Found')
|
|
1035
|
-
])
|
|
1036
|
-
]);
|
|
1037
|
-
var img=document.createElement('img');
|
|
1038
|
-
img.src=url;
|
|
1039
|
-
img.alt='ss:'+hash;
|
|
2556
|
+
var wrap=el('div',{className:'ss-search-result'},[el('div',{className:'ss-result-label'},[createHashBadge(hash),el('span',{},'Found')])]);
|
|
2557
|
+
var img=document.createElement('img');img.src=url;img.alt='ss:'+hash;
|
|
1040
2558
|
img.addEventListener('click',function(){openModal(url)});
|
|
1041
2559
|
wrap.appendChild(img);
|
|
1042
2560
|
container.appendChild(wrap);
|
|
1043
|
-
}).catch(function(){
|
|
1044
|
-
container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'));
|
|
1045
|
-
});
|
|
2561
|
+
}).catch(function(){container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'))});
|
|
1046
2562
|
}
|
|
1047
2563
|
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1048
2564
|
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1049
2565
|
|
|
1050
|
-
/* ──
|
|
1051
|
-
function
|
|
1052
|
-
|
|
1053
|
-
|
|
2566
|
+
/* ── Learnings ── */
|
|
2567
|
+
function refreshLearnings(){
|
|
2568
|
+
var days=$('#learningsDays').value||30;
|
|
2569
|
+
var url=S.project?'/api/db/projects/'+S.project+'/learnings?days='+days:'/api/db/learnings?days='+days;
|
|
2570
|
+
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
2571
|
+
if(!data||data.totalRuns===0){
|
|
2572
|
+
$('#learningsEmpty').style.display='block';
|
|
2573
|
+
$('#learningsOverview').textContent='';$('#learningsTrend').textContent='';
|
|
2574
|
+
$('#learningsFlaky').textContent='';$('#learningsSelectors').textContent='';
|
|
2575
|
+
$('#learningsPages').textContent='';$('#learningsApis').textContent='';
|
|
2576
|
+
$('#learningsErrors').textContent='';
|
|
2577
|
+
$('#badgeLearnings').textContent='-';
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
$('#learningsEmpty').style.display='none';
|
|
2581
|
+
S.lastLearningsData=data;
|
|
2582
|
+
var flakyCount=data.flakyTests?data.flakyTests.length:0;
|
|
2583
|
+
var passRate=data.overallPassRate||0;
|
|
2584
|
+
var declining=data.recentTrend&&Array.isArray(data.recentTrend.data||data.recentTrend)&&(function(){
|
|
2585
|
+
var td=data.recentTrend.data||data.recentTrend;
|
|
2586
|
+
if(td.length<2)return false;
|
|
2587
|
+
var last=td[td.length-1].pass_rate;
|
|
2588
|
+
var prior=td.slice(0,-1).reduce(function(s,t){return s+t.pass_rate},0)/(td.length-1);
|
|
2589
|
+
return last-prior<-2;
|
|
2590
|
+
})();
|
|
2591
|
+
if(passRate<70){
|
|
2592
|
+
$('#badgeLearnings').textContent='\u26A0';
|
|
2593
|
+
$('#badgeLearnings').style.background='var(--red-dim)';$('#badgeLearnings').style.color='var(--red)';
|
|
2594
|
+
} else if(flakyCount>0||declining){
|
|
2595
|
+
$('#badgeLearnings').textContent=flakyCount>0?flakyCount:(declining?'\u25BC':'\u2714');
|
|
2596
|
+
$('#badgeLearnings').style.background='var(--amber-dim)';$('#badgeLearnings').style.color='var(--amber)';
|
|
2597
|
+
} else {
|
|
2598
|
+
$('#badgeLearnings').textContent='\u2714';
|
|
2599
|
+
$('#badgeLearnings').style.background='var(--green-dim)';$('#badgeLearnings').style.color='var(--green)';
|
|
2600
|
+
}
|
|
2601
|
+
renderLearnOverview(data);
|
|
2602
|
+
renderLearnTrend(data.recentTrend||[]);
|
|
2603
|
+
renderLearnFlaky(data.flakyTests||[]);
|
|
2604
|
+
renderLearnSelectors(data.unstableSelectors||[]);
|
|
2605
|
+
renderLearnPages(data.failingPages||[]);
|
|
2606
|
+
renderLearnApis(data.apiIssues||[]);
|
|
2607
|
+
renderLearnErrors(data.topErrors||[]);
|
|
2608
|
+
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
function renderLearnOverview(d){
|
|
2612
|
+
var container=$('#learningsOverview');container.textContent='';
|
|
2613
|
+
var grid=document.createElement('div');grid.className='learn-grid';
|
|
2614
|
+
[{val:d.totalRuns,lbl:'Runs',cls:'accent'},{val:d.totalTests,lbl:'Tests',cls:'accent'},
|
|
2615
|
+
{val:d.overallPassRate+'%',lbl:'Pass Rate',cls:d.overallPassRate>=90?'green':d.overallPassRate>=70?'':'red'},
|
|
2616
|
+
{val:d.avgDurationMs<1000?d.avgDurationMs+'ms':(d.avgDurationMs/1000).toFixed(1)+'s',lbl:'Avg Duration',cls:'purple'},
|
|
2617
|
+
{val:(d.flakyTests?d.flakyTests.length:0),lbl:'Flaky Tests',cls:d.flakyTests&&d.flakyTests.length>0?'red':'green'},
|
|
2618
|
+
{val:(d.unstableSelectors?d.unstableSelectors.length:0),lbl:'Unstable Selectors',cls:d.unstableSelectors&&d.unstableSelectors.length>0?'red':'green'}
|
|
2619
|
+
].forEach(function(item){
|
|
2620
|
+
var stat=document.createElement('div');stat.className='learn-stat';
|
|
2621
|
+
var valEl=document.createElement('div');valEl.className='learn-stat-val '+item.cls;valEl.textContent=item.val;
|
|
2622
|
+
var lblEl=document.createElement('div');lblEl.className='learn-stat-lbl';lblEl.textContent=item.lbl;
|
|
2623
|
+
stat.appendChild(valEl);stat.appendChild(lblEl);grid.appendChild(stat);
|
|
2624
|
+
});
|
|
2625
|
+
container.appendChild(grid);
|
|
1054
2626
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
2627
|
+
|
|
2628
|
+
function renderLearnTrend(trend){
|
|
2629
|
+
var container=$('#learningsTrend');container.textContent='';
|
|
2630
|
+
if(!trend.length)return;
|
|
2631
|
+
var card=document.createElement('div');card.className='card';
|
|
2632
|
+
var label=document.createElement('div');label.className='card-label';label.textContent='Pass Rate Trend (7 days)';card.appendChild(label);
|
|
2633
|
+
var chartDiv=document.createElement('div');chartDiv.className='learn-trend-chart';
|
|
2634
|
+
var w=100/trend.length;var ns='http://www.w3.org/2000/svg';
|
|
2635
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');
|
|
2636
|
+
var bg=document.createElementNS(ns,'rect');bg.setAttribute('x','0');bg.setAttribute('y','0');bg.setAttribute('width','100');bg.setAttribute('height','100');bg.setAttribute('fill','var(--surface2)');bg.setAttribute('rx','2');svg.appendChild(bg);
|
|
2637
|
+
var gridLine=document.createElementNS(ns,'line');gridLine.setAttribute('x1','0');gridLine.setAttribute('y1','50');gridLine.setAttribute('x2','100');gridLine.setAttribute('y2','50');gridLine.setAttribute('stroke','var(--border)');gridLine.setAttribute('stroke-width','0.3');gridLine.setAttribute('stroke-dasharray','2,2');svg.appendChild(gridLine);
|
|
2638
|
+
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
2639
|
+
var poly=document.createElementNS(ns,'polygon');poly.setAttribute('points',(0*w+w/2)+',100 '+pts+' '+((trend.length-1)*w+w/2)+',100');poly.setAttribute('fill','var(--accent-dim)');svg.appendChild(poly);
|
|
2640
|
+
var pl=document.createElementNS(ns,'polyline');pl.setAttribute('points',pts);pl.setAttribute('fill','none');pl.setAttribute('stroke','var(--accent)');pl.setAttribute('stroke-width','1.5');svg.appendChild(pl);
|
|
2641
|
+
trend.forEach(function(t,i){
|
|
2642
|
+
var circle=document.createElementNS(ns,'circle');circle.setAttribute('cx',''+(i*w+w/2));circle.setAttribute('cy',''+(100-t.pass_rate));circle.setAttribute('r','2');circle.setAttribute('fill','var(--accent)');
|
|
2643
|
+
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
2644
|
+
});
|
|
2645
|
+
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
2646
|
+
var dates=document.createElement('div');dates.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:4px';
|
|
2647
|
+
dates.appendChild(el('span',null,trend[0].date));dates.appendChild(el('span',null,trend[trend.length-1].date));
|
|
2648
|
+
card.appendChild(dates);container.appendChild(card);
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function buildLearnTable(title,headers,rows){
|
|
2652
|
+
var card=document.createElement('div');card.className='card learn-section';
|
|
2653
|
+
var h=document.createElement('div');h.className='learn-section-title';h.textContent=title;card.appendChild(h);
|
|
2654
|
+
var wrap=document.createElement('div');wrap.className='tbl-wrap';
|
|
2655
|
+
var tbl=document.createElement('table');tbl.className='learn-table';
|
|
2656
|
+
var thead=document.createElement('thead');var hr=document.createElement('tr');
|
|
2657
|
+
headers.forEach(function(hdr){var th=document.createElement('th');th.textContent=hdr;hr.appendChild(th)});
|
|
2658
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
2659
|
+
var tbody=document.createElement('tbody');
|
|
2660
|
+
rows.forEach(function(cells){
|
|
2661
|
+
var tr=document.createElement('tr');
|
|
2662
|
+
cells.forEach(function(cell){
|
|
2663
|
+
var td=document.createElement('td');
|
|
2664
|
+
if(cell.code){var code=document.createElement('code');code.textContent=cell.code;td.appendChild(code)}
|
|
2665
|
+
else if(cell.badge){var span=document.createElement('span');span.className='badge '+cell.cls;span.textContent=cell.badge;td.appendChild(span)}
|
|
2666
|
+
else{td.textContent=cell.text!==undefined&&cell.text!==null?cell.text:(typeof cell==='object'?'-':cell)}
|
|
2667
|
+
tr.appendChild(td);
|
|
2668
|
+
});
|
|
2669
|
+
tbody.appendChild(tr);
|
|
2670
|
+
});
|
|
2671
|
+
tbl.appendChild(tbody);wrap.appendChild(tbl);card.appendChild(wrap);return card;
|
|
1058
2672
|
}
|
|
2673
|
+
|
|
2674
|
+
function renderLearnFlaky(flaky){var c=$('#learningsFlaky');c.textContent='';if(!flaky.length)return;c.appendChild(buildLearnTable('Flaky Tests',['Test','Flaky Rate','Occurrences','Total Runs','Last Flaky','Avg Attempts'],flaky.map(function(f){return[{code:f.test_name},{badge:f.flaky_rate+'%',cls:f.flaky_rate>30?'fail':'flaky'},{text:f.flaky_count},{text:f.total_runs},{text:(f.last_flaky||'-').split('T')[0]},{text:f.avg_attempts}]})))}
|
|
2675
|
+
function renderLearnSelectors(sels){var c=$('#learningsSelectors');c.textContent='';if(!sels.length)return;c.appendChild(buildLearnTable('Unstable Selectors',['Selector','Action','Fail Rate','Uses','Tests','Page'],sels.map(function(s){var sel=s.selector.length>45?s.selector.slice(0,42)+'...':s.selector;return[{code:sel},{text:s.action_type},{badge:s.fail_rate+'%',cls:s.fail_rate>30?'fail':'flaky'},{text:s.total_uses},{text:s.used_by_tests},{text:s.page_url||'-'}]})))}
|
|
2676
|
+
function renderLearnPages(pages){var c=$('#learningsPages');c.textContent='';if(!pages.length)return;c.appendChild(buildLearnTable('Failing Pages',['Page','Fail Rate','Visits','Console Errors','Network Errors'],pages.map(function(p){return[{code:p.url_path},{badge:p.fail_rate+'%',cls:p.fail_rate>30?'fail':'flaky'},{text:p.total_visits},{text:p.console_errors},{text:p.network_errors}]})))}
|
|
2677
|
+
function renderLearnApis(apis){var c=$('#learningsApis');c.textContent='';if(!apis.length)return;c.appendChild(buildLearnTable('API Issues',['Endpoint','Error Rate','Calls','Avg Duration','Status Codes'],apis.map(function(a){var ep=a.endpoint.length>45?a.endpoint.slice(0,42)+'...':a.endpoint;var d=a.avg_duration_ms<1000?Math.round(a.avg_duration_ms)+'ms':(a.avg_duration_ms/1000).toFixed(1)+'s';return[{code:ep},{badge:a.error_rate+'%',cls:a.error_rate>20?'fail':'flaky'},{text:a.total_calls},{text:d},{text:a.status_codes||'-'}]})))}
|
|
2678
|
+
function renderLearnErrors(errors){var c=$('#learningsErrors');c.textContent='';if(!errors.length)return;c.appendChild(buildLearnTable('Error Patterns',['Pattern','Category','Count','First Seen','Last Seen','Example Test'],errors.map(function(e){var pat=e.pattern.length>50?e.pattern.slice(0,47)+'...':e.pattern;return[{text:pat},{badge:e.category,cls:'run'},{text:e.occurrence_count},{text:(e.first_seen||'-').split('T')[0]},{text:(e.last_seen||'-').split('T')[0]},{code:e.example_test||'-'}]})))}
|
|
2679
|
+
|
|
2680
|
+
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
2681
|
+
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
2682
|
+
|
|
2683
|
+
$('#btnExportLearnings').addEventListener('click',function(){
|
|
2684
|
+
var data=S.lastLearningsData;
|
|
2685
|
+
if(!data){showToast('No learnings data to export','error');return}
|
|
2686
|
+
var md='# E2E Learnings Report\n\n';
|
|
2687
|
+
md+='| Metric | Value |\n|--------|-------|\n';
|
|
2688
|
+
md+='| Total Runs | '+data.totalRuns+' |\n';
|
|
2689
|
+
md+='| Total Tests | '+data.totalTests+' |\n';
|
|
2690
|
+
md+='| Pass Rate | '+data.overallPassRate+'% |\n';
|
|
2691
|
+
md+='| Avg Duration | '+dur(data.avgDurationMs)+' |\n\n';
|
|
2692
|
+
if(data.flakyTests&&data.flakyTests.length){
|
|
2693
|
+
md+='## Flaky Tests\n\n| Test | Flaky Rate | Occurrences |\n|------|-----------|-------------|\n';
|
|
2694
|
+
data.flakyTests.forEach(function(f){md+='| '+f.test_name+' | '+f.flaky_rate+'% | '+f.flaky_count+' |\n'});md+='\n';
|
|
2695
|
+
}
|
|
2696
|
+
if(data.unstableSelectors&&data.unstableSelectors.length){
|
|
2697
|
+
md+='## Unstable Selectors\n\n| Selector | Action | Fail Rate |\n|----------|--------|-----------|\n';
|
|
2698
|
+
data.unstableSelectors.forEach(function(s){md+='| `'+s.selector+'` | '+s.action_type+' | '+s.fail_rate+'% |\n'});md+='\n';
|
|
2699
|
+
}
|
|
2700
|
+
downloadFile('learnings-report.md',md,'text/markdown');
|
|
2701
|
+
showToast('Learnings exported','success');
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
/* ── Modal ── */
|
|
2705
|
+
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
2706
|
+
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
2707
|
+
|
|
2708
|
+
|
|
2709
|
+
/* ── view-live.js ── */
|
|
2710
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2711
|
+
Live Execution View
|
|
2712
|
+
══════════════════════════════════════════════════════════════════ */
|
|
2713
|
+
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}renderLive()}
|
|
2714
|
+
function dismissLiveRun(rid){delete S.liveRuns[rid];renderLive()}
|
|
1059
2715
|
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
1060
2716
|
|
|
1061
2717
|
function renderLive(){
|
|
1062
2718
|
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
1063
|
-
var runs=S.liveRuns;
|
|
1064
|
-
var runIds=Object.keys(runs);
|
|
1065
|
-
|
|
1066
|
-
if(runIds.length===0){
|
|
1067
|
-
panel.classList.remove('active');
|
|
1068
|
-
navLive.style.display='none';
|
|
1069
|
-
liveEmpty.style.display='block';
|
|
1070
|
-
$('#liveClearBtn').style.display='none';
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
2719
|
+
var runs=S.liveRuns;var runIds=Object.keys(runs);
|
|
1073
2720
|
|
|
1074
|
-
navLive.style.display='';
|
|
1075
|
-
|
|
1076
|
-
panel.classList.add('active');
|
|
2721
|
+
if(runIds.length===0){panel.classList.remove('active');navLive.style.display='none';liveEmpty.style.display='block';$('#liveClearBtn').style.display='none';return}
|
|
2722
|
+
|
|
2723
|
+
navLive.style.display='';liveEmpty.style.display='none';panel.classList.add('active');
|
|
1077
2724
|
|
|
1078
|
-
// Aggregate stats across all runs
|
|
1079
2725
|
var gTotal=0,gCompleted=0,gPassed=0,gFailed=0,gActive=0,gRunning=false,gDone=true;
|
|
1080
|
-
runIds.forEach(function(rid){
|
|
1081
|
-
var r=runs[rid];gTotal+=r.total;gCompleted+=r.completed;gPassed+=r.passed;gFailed+=r.failed;gActive+=r.active;
|
|
1082
|
-
if(r.on)gRunning=true;if(!r.done)gDone=false;
|
|
1083
|
-
});
|
|
2726
|
+
runIds.forEach(function(rid){var r=runs[rid];gTotal+=r.total;gCompleted+=r.completed;gPassed+=r.passed;gFailed+=r.failed;gActive+=r.active;if(r.on)gRunning=true;if(!r.done)gDone=false});
|
|
1084
2727
|
|
|
1085
2728
|
var badgeActive=0;
|
|
1086
2729
|
runIds.forEach(function(rid){var r=runs[rid];Object.keys(r.tests).forEach(function(n){if(n!=='__error'&&r.tests[n].status==='running')badgeActive++})});
|
|
@@ -1088,21 +2731,13 @@ function renderLive(){
|
|
|
1088
2731
|
$('#liveBadge').style.background=gRunning?'var(--purple-dim)':gFailed>0?'var(--red-dim)':'var(--green-dim)';
|
|
1089
2732
|
$('#liveBadge').style.color=gRunning?'var(--purple)':gFailed>0?'var(--red)':'var(--green)';
|
|
1090
2733
|
|
|
1091
|
-
$('#liveTotal').textContent=gTotal;
|
|
1092
|
-
$('#
|
|
1093
|
-
$('#liveFail').textContent=gFailed;
|
|
1094
|
-
$('#liveActive').textContent=gActive;
|
|
1095
|
-
var pct=gTotal>0?gCompleted/gTotal*100:0;
|
|
1096
|
-
$('#liveProgressFill').style.width=pct+'%';
|
|
1097
|
-
|
|
1098
|
-
// Hide single-project info (now shown per-section)
|
|
2734
|
+
$('#liveTotal').textContent=gTotal;$('#livePass').textContent=gPassed;$('#liveFail').textContent=gFailed;$('#liveActive').textContent=gActive;
|
|
2735
|
+
$('#liveProgressFill').style.width=(gTotal>0?gCompleted/gTotal*100:0)+'%';
|
|
1099
2736
|
$('#liveProject').style.display='none';
|
|
1100
2737
|
|
|
1101
|
-
// Show "Clear All" when there are finished/stale runs
|
|
1102
2738
|
var hasFinished=runIds.some(function(rid){return runs[rid].done||!runs[rid].on});
|
|
1103
2739
|
$('#liveClearBtn').style.display=hasFinished?'inline-block':'none';
|
|
1104
2740
|
|
|
1105
|
-
// Header state
|
|
1106
2741
|
var lbl=panel.querySelector('.live-header .label');
|
|
1107
2742
|
var anyStale=runIds.some(function(rid){return runs[rid].stale});
|
|
1108
2743
|
if(!gRunning&&gDone){
|
|
@@ -1111,148 +2746,104 @@ function renderLive(){
|
|
|
1111
2746
|
var dot=lbl.querySelector('.dot');if(dot)dot.remove();
|
|
1112
2747
|
$('#liveProgressFill').style.background=anyStale?'var(--yellow)':gFailed>0?'var(--red)':'var(--green)';
|
|
1113
2748
|
} else {
|
|
1114
|
-
if(!lbl.querySelector('.dot')){
|
|
1115
|
-
|
|
1116
|
-
var d=el('span',{className:'dot'});lbl.appendChild(d);lbl.appendChild(document.createTextNode(' RUNNING'));
|
|
1117
|
-
}
|
|
1118
|
-
lbl.style.color='var(--purple)';
|
|
1119
|
-
$('#liveProgressFill').style.background='var(--purple)';
|
|
2749
|
+
if(!lbl.querySelector('.dot')){lbl.textContent='';var d=el('span',{className:'dot'});lbl.appendChild(d);lbl.appendChild(document.createTextNode(' RUNNING'))}
|
|
2750
|
+
lbl.style.color='var(--purple)';$('#liveProgressFill').style.background='var(--purple)';
|
|
1120
2751
|
}
|
|
1121
2752
|
|
|
1122
|
-
// Render per-run sections
|
|
1123
2753
|
grid.textContent='';
|
|
1124
2754
|
runIds.forEach(function(rid){
|
|
1125
2755
|
var L=runs[rid];
|
|
1126
|
-
// Project section header
|
|
1127
2756
|
var projLabel=L.project||(L.cwd?L.cwd.split('/').pop():'Run');
|
|
1128
2757
|
var runStatus=L.done?(L.failed>0?'fail':'pass'):'running';
|
|
1129
2758
|
var dismissBtn=null;
|
|
1130
|
-
if(L.done||!L.on){
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
el('span',{className:'lr-project-name'},projLabel),
|
|
1135
|
-
createTriggerBadge(L.triggeredBy),
|
|
1136
|
-
el('span',{className:'lr-section-stats'},[
|
|
1137
|
-
el('span',{},L.completed+'/'+L.total),
|
|
1138
|
-
L.failed>0?el('span',{style:'color:var(--red);margin-left:6px'},L.failed+' failed'):null,
|
|
1139
|
-
L.on?el('span',{className:'spinner-small',style:'margin-left:6px'}):null
|
|
1140
|
-
]),
|
|
2759
|
+
if(L.done||!L.on){dismissBtn=el('button',{className:'lr-dismiss',onclick:function(e){e.stopPropagation();dismissLiveRun(rid)}},'\u2715')}
|
|
2760
|
+
grid.appendChild(el('div',{className:'lr-section-header '+runStatus},[
|
|
2761
|
+
el('span',{className:'lr-project-name'},projLabel),createTriggerBadge(L.triggeredBy),
|
|
2762
|
+
el('span',{className:'lr-section-stats'},[el('span',{},L.completed+'/'+L.total),L.failed>0?el('span',{style:'color:var(--red);margin-left:6px'},L.failed+' failed'):null,L.on?el('span',{className:'spinner-small',style:'margin-left:6px'}):null]),
|
|
1141
2763
|
dismissBtn
|
|
1142
|
-
]);
|
|
1143
|
-
|
|
2764
|
+
]));
|
|
2765
|
+
|
|
2766
|
+
var poolDist=buildPoolDistribution(L.tests);
|
|
2767
|
+
if(poolDist)grid.appendChild(poolDist);
|
|
1144
2768
|
|
|
1145
2769
|
var testGrid=el('div',{className:'lr-test-grid'});
|
|
1146
|
-
|
|
1147
|
-
names.forEach(function(name){
|
|
2770
|
+
Object.keys(L.tests).forEach(function(name){
|
|
1148
2771
|
if(name==='__error')return;
|
|
1149
|
-
var t=L.tests[name];
|
|
1150
|
-
var testKey=rid+'::'+name;
|
|
2772
|
+
var t=L.tests[name];var testKey=rid+'::'+name;
|
|
1151
2773
|
var iconText=t.status==='passed'?'\u2714':t.status==='failed'?'\u2718':'\u25CF';
|
|
1152
2774
|
var iconColor=t.status==='passed'?'color:var(--green)':t.status==='failed'?'color:var(--red)':'color:var(--purple)';
|
|
1153
2775
|
var meta='';
|
|
1154
|
-
if(t.status==='running'){
|
|
1155
|
-
|
|
1156
|
-
if(t.retry)meta='Retry '+t.retry;
|
|
1157
|
-
} else if(t.status==='passed'){meta=t.duration||'done'}
|
|
2776
|
+
if(t.status==='running'){meta=t.actionType?('Step '+(t.actions||0)+'/'+(t.totalActions||'?')):'starting...';if(t.retry)meta='Retry '+t.retry}
|
|
2777
|
+
else if(t.status==='passed'){meta=t.duration||'done'}
|
|
1158
2778
|
else if(t.status==='failed'){meta=t.error||'failed'}
|
|
1159
|
-
|
|
2779
|
+
|
|
1160
2780
|
var stepsEl=el('div',{className:'lt-actions'});
|
|
1161
2781
|
if(t.actionLog&&t.actionLog.length>0){
|
|
1162
2782
|
t.actionLog.forEach(function(a){
|
|
1163
|
-
var detail=a.selector||a.value||a.text||'';
|
|
2783
|
+
var detail=a.narrative||a.selector||a.value||a.text||'';
|
|
1164
2784
|
var durText=a.duration!=null?(a.duration<1000?a.duration+'ms':(a.duration/1000).toFixed(1)+'s'):'';
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
el('span',{className:'step-
|
|
2785
|
+
var retryBadge=null;
|
|
2786
|
+
if(a.actionRetries&&a.actionRetries>0){retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px;margin-left:4px'},'\u21BB x'+a.actionRetries)}
|
|
2787
|
+
var stepCls='lt-step'+(a.isPoolLog?' pool-log':'');
|
|
2788
|
+
stepsEl.appendChild(el('div',{className:stepCls},[
|
|
2789
|
+
el('span',{className:'step-icon '+(a.isPoolLog?'':a.success?'ok':'fail')},a.isPoolLog?'\uD83D\uDD17':a.success?'\u2714':'\u2718'),
|
|
2790
|
+
el('span',{className:'step-type'},a.isPoolLog?'pool':a.type),
|
|
2791
|
+
el('span',{className:'step-detail'},a.isPoolLog?a.narrative:detail),
|
|
2792
|
+
retryBadge,
|
|
2793
|
+
a.isPoolLog?null:el('span',{className:'step-dur'},durText)
|
|
1170
2794
|
]));
|
|
1171
2795
|
});
|
|
1172
|
-
if(t.status==='running'&&t.actions<t.totalActions){
|
|
1173
|
-
stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'waiting...')]));
|
|
1174
|
-
}
|
|
2796
|
+
if(t.status==='running'&&t.actions<t.totalActions){stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'waiting...')]))}
|
|
1175
2797
|
} else if(t.status==='running'){
|
|
1176
2798
|
stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'connecting...')]));
|
|
1177
2799
|
}
|
|
1178
2800
|
var isFinished=t.status==='passed'||t.status==='failed';
|
|
1179
|
-
var isCollapsed=isFinished
|
|
1180
|
-
var summaryEl=el('div',{className:'lt-summary'},[
|
|
1181
|
-
|
|
1182
|
-
el('span',{className:'lt-expand'},isCollapsed?'\u25BC':'\u25B2')
|
|
1183
|
-
]);
|
|
1184
|
-
// Screenshots section
|
|
2801
|
+
var isCollapsed=isFinished&&S.liveCollapsed.has(testKey);
|
|
2802
|
+
var summaryEl=el('div',{className:'lt-summary'},[el('span',{className:'lt-dur'},t.duration||''),el('span',{className:'lt-expand'},isCollapsed?'\u25BC':'\u25B2')]);
|
|
2803
|
+
|
|
1185
2804
|
var ssEl=null;
|
|
1186
2805
|
var allSS=(t.screenshots||[]).slice();
|
|
1187
2806
|
if(t.errorScreenshot)allSS.push(t.errorScreenshot);
|
|
1188
2807
|
if(allSS.length>0){
|
|
1189
2808
|
var ssOpen=S.liveSSOpen&&S.liveSSOpen.has(testKey);
|
|
1190
|
-
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[
|
|
1191
|
-
el('span',{className:'ss-arrow'},'\u25B6'),
|
|
1192
|
-
el('span',{},'Screenshots ('+allSS.length+')')
|
|
1193
|
-
]);
|
|
2809
|
+
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[el('span',{className:'ss-arrow'},'\u25B6'),el('span',{},'Screenshots ('+allSS.length+')')]);
|
|
1194
2810
|
var ssGridEl=el('div',{className:'lt-screenshots-grid'});
|
|
1195
2811
|
allSS.forEach(function(ssPath){
|
|
1196
|
-
var fname=ssPath.split('/').pop();
|
|
1197
|
-
var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
2812
|
+
var fname=ssPath.split('/').pop();var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
1198
2813
|
var thumb=el('div',{className:'lt-ss-thumb'});
|
|
1199
|
-
var img=document.createElement('img');
|
|
1200
|
-
img.src='/api/image?path='+encodeURIComponent(ssPath);
|
|
1201
|
-
img.alt=fname;img.loading='lazy';
|
|
2814
|
+
var img=document.createElement('img');img.src='/api/image?path='+encodeURIComponent(ssPath);img.alt=fname;img.loading='lazy';
|
|
1202
2815
|
if(isErr)thumb.style.borderColor='var(--red)';
|
|
1203
2816
|
thumb.appendChild(img);
|
|
1204
2817
|
thumb.addEventListener('click',function(e){e.stopPropagation();openModal('/api/image?path='+encodeURIComponent(ssPath),fname)});
|
|
1205
2818
|
var labelEl=el('div',{className:'lt-ss-label'},[el('span',{style:'overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},fname)]);
|
|
1206
|
-
|
|
1207
|
-
(function(lbl,sp){
|
|
1208
|
-
ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))});
|
|
1209
|
-
})(labelEl,ssPath);
|
|
2819
|
+
(function(lbl,sp){ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))})})(labelEl,ssPath);
|
|
1210
2820
|
ssGridEl.appendChild(el('div',{},[thumb,labelEl]));
|
|
1211
2821
|
});
|
|
1212
|
-
toggle.addEventListener('click',function(e){
|
|
1213
|
-
e.stopPropagation();
|
|
1214
|
-
if(S.liveSSOpen.has(testKey))S.liveSSOpen.delete(testKey);else S.liveSSOpen.add(testKey);
|
|
1215
|
-
toggle.classList.toggle('open');
|
|
1216
|
-
ssGridEl.style.display=ssGridEl.style.display==='grid'?'none':'grid';
|
|
1217
|
-
});
|
|
2822
|
+
toggle.addEventListener('click',function(e){e.stopPropagation();if(S.liveSSOpen.has(testKey))S.liveSSOpen.delete(testKey);else S.liveSSOpen.add(testKey);toggle.classList.toggle('open');ssGridEl.style.display=ssGridEl.style.display==='grid'?'none':'grid'});
|
|
1218
2823
|
if(ssOpen)ssGridEl.style.display='grid';
|
|
1219
2824
|
ssEl=el('div',{className:'lt-screenshots'},[toggle,ssGridEl]);
|
|
1220
2825
|
}
|
|
2826
|
+
|
|
2827
|
+
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
2828
|
+
var poolBadge=t.poolUrl?el('span',{className:'pool-badge'},t.poolUrl.replace('ws://','').replace('wss://','')):null;
|
|
1221
2829
|
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
1222
2830
|
el('div',{className:'lt-name'},[
|
|
1223
2831
|
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
1224
|
-
document.createTextNode(' '+name),
|
|
1225
|
-
summaryEl
|
|
2832
|
+
document.createTextNode(' '+name),serialBadge,poolBadge,summaryEl
|
|
1226
2833
|
]),
|
|
1227
|
-
el('div',{className:'lt-meta'},meta),
|
|
1228
|
-
stepsEl
|
|
2834
|
+
el('div',{className:'lt-meta'},meta),stepsEl
|
|
1229
2835
|
]);
|
|
1230
2836
|
if(ssEl)card.appendChild(ssEl);
|
|
1231
|
-
// Network API logs in live view (clickable rows)
|
|
1232
2837
|
if(t.networkLogs&&t.networkLogs.length&&!isCollapsed){
|
|
1233
2838
|
var liveErrCount=t.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
1234
|
-
var
|
|
1235
|
-
var
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
t.networkLogs.forEach(function(n){
|
|
1241
|
-
var built=buildNetRow(n);
|
|
1242
|
-
liveNetList.appendChild(built.row);
|
|
1243
|
-
if(built.detail)liveNetList.appendChild(built.detail);
|
|
1244
|
-
});
|
|
1245
|
-
liveNetToggle.addEventListener('click',function(e){e.stopPropagation();liveNetToggle.classList.toggle('open')});
|
|
1246
|
-
card.appendChild(liveNetToggle);
|
|
1247
|
-
card.appendChild(liveNetList);
|
|
1248
|
-
}
|
|
1249
|
-
if(isFinished){
|
|
1250
|
-
card.addEventListener('click',function(){
|
|
1251
|
-
if(S.liveExpanded.has(testKey))S.liveExpanded.delete(testKey);
|
|
1252
|
-
else S.liveExpanded.add(testKey);
|
|
1253
|
-
renderLive();
|
|
1254
|
-
});
|
|
2839
|
+
var liveNetHead=el('div',{className:'rd-net-head'},[el('span',{className:'net-arrow'},'\u25B6'),el('span',{className:'net-title'},'Network Requests'),el('div',{className:'net-stats'},[el('span',{className:'net-stat'},[document.createTextNode('Total: '),el('strong',null,String(t.networkLogs.length))]),liveErrCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(liveErrCount))]):null])]);
|
|
2840
|
+
var liveNetCols=el('div',{className:'rd-net-cols'},[el('span',{className:'col-e'},''),el('span',{className:'col-m'},'Method'),el('span',{className:'col-s'},'Status'),el('span',{className:'col-u'},'URL'),el('span',{className:'col-d'},'Time')]);
|
|
2841
|
+
var liveNetBody=el('div',{className:'rd-net-body'},[liveNetCols]);
|
|
2842
|
+
t.networkLogs.forEach(function(n){var built=buildNetRow(n);liveNetBody.appendChild(built.row);if(built.detail)liveNetBody.appendChild(built.detail)});
|
|
2843
|
+
liveNetHead.addEventListener('click',function(e){e.stopPropagation();liveNetHead.classList.toggle('open')});
|
|
2844
|
+
card.appendChild(el('div',{className:'rd-net-panel',style:'margin-top:6px'},[liveNetHead,liveNetBody]));
|
|
1255
2845
|
}
|
|
2846
|
+
if(isFinished){card.addEventListener('click',function(e){if(window.getSelection().toString())return;if(S.liveCollapsed.has(testKey))S.liveCollapsed.delete(testKey);else S.liveCollapsed.add(testKey);renderLive()})}
|
|
1256
2847
|
testGrid.appendChild(card);
|
|
1257
2848
|
if(!isCollapsed)stepsEl.scrollTop=stepsEl.scrollHeight;
|
|
1258
2849
|
});
|
|
@@ -1260,21 +2851,71 @@ function renderLive(){
|
|
|
1260
2851
|
});
|
|
1261
2852
|
}
|
|
1262
2853
|
|
|
1263
|
-
/* ── Actions ── */
|
|
1264
|
-
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
1265
2854
|
|
|
1266
|
-
/* ──
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
2855
|
+
/* ── keyboard.js ── */
|
|
2856
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2857
|
+
Keyboard Shortcuts (Updated: 1=Watch, 2=Tests, 3=Runs, 4=Live)
|
|
2858
|
+
══════════════════════════════════════════════════════════════════ */
|
|
2859
|
+
document.addEventListener('keydown',function(e){
|
|
2860
|
+
var tag=document.activeElement.tagName;
|
|
2861
|
+
if(tag==='INPUT'||tag==='SELECT'||tag==='TEXTAREA')return;
|
|
2862
|
+
if(e.key==='Escape'){
|
|
2863
|
+
if($('#kbModal').classList.contains('open')){$('#kbModal').classList.remove('open');return}
|
|
2864
|
+
if($('#modal').classList.contains('open')){$('#modal').classList.remove('open');return}
|
|
2865
|
+
if($('#suiteModalOverlay').classList.contains('open')){$('#suiteModalOverlay').classList.remove('open');return}
|
|
2866
|
+
if(S.selectedRun!==null){
|
|
2867
|
+
var expanded=document.querySelector('#runsBody tr.expanded');
|
|
2868
|
+
if(expanded){
|
|
2869
|
+
var next=expanded.nextElementSibling;
|
|
2870
|
+
if(next&&next.classList.contains('run-detail-row')){var w=next.querySelector('.rd-wrap');if(w)w.classList.remove('open');expanded.classList.remove('expanded');setTimeout(function(){if(next.parentNode)next.parentNode.removeChild(next)},350)}
|
|
2871
|
+
S.selectedRun=null;
|
|
2872
|
+
}
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
if(e.key==='?'){$('#kbModal').classList.toggle('open');return}
|
|
2878
|
+
var viewMap={'1':'watch','2':'tests','3':'runs','4':'live','5':'instances'};
|
|
2879
|
+
if(viewMap[e.key]){showView(viewMap[e.key]);return}
|
|
2880
|
+
if(e.key==='r'){
|
|
2881
|
+
if(S.view==='watch')refreshWatch();
|
|
2882
|
+
else if(S.view==='tests'){refreshSuites();refreshVariables()}
|
|
2883
|
+
else if(S.view==='runs'){refreshRuns();refreshScreenshots();refreshLearnings()}
|
|
2884
|
+
else if(S.view==='live')renderLive();
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if(S.view==='runs'&&(e.key==='j'||e.key==='k')){
|
|
2888
|
+
var visible=_allRunRows.filter(function(item){return item.tr.style.display!=='none'});
|
|
2889
|
+
if(!visible.length)return;
|
|
2890
|
+
if(e.key==='j')S.highlightedRunIdx=Math.min(S.highlightedRunIdx+1,visible.length-1);
|
|
2891
|
+
if(e.key==='k')S.highlightedRunIdx=Math.max(S.highlightedRunIdx-1,0);
|
|
2892
|
+
visible.forEach(function(item,i){if(i===S.highlightedRunIdx){item.tr.classList.add('selected');item.tr.scrollIntoView({block:'nearest'})}else item.tr.classList.remove('selected')});
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
if(S.view==='runs'&&e.key==='Enter'){
|
|
2896
|
+
var visible2=_allRunRows.filter(function(item){return item.tr.style.display!=='none'});
|
|
2897
|
+
if(S.highlightedRunIdx>=0&&S.highlightedRunIdx<visible2.length){visible2[S.highlightedRunIdx].tr.click()}
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
$('#kbModal').addEventListener('click',function(e){if(e.target===$('#kbModal'))$('#kbModal').classList.remove('open')});
|
|
1270
2902
|
|
|
1271
|
-
|
|
2903
|
+
|
|
2904
|
+
/* ── init.js ── */
|
|
2905
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
2906
|
+
Init — startup sequence
|
|
2907
|
+
══════════════════════════════════════════════════════════════════ */
|
|
2908
|
+
initTabs();
|
|
1272
2909
|
connectWS();
|
|
1273
2910
|
refreshStatus();
|
|
1274
2911
|
refreshProjects();
|
|
1275
2912
|
refreshSuites();
|
|
1276
2913
|
refreshRuns();
|
|
1277
2914
|
refreshScreenshots();
|
|
2915
|
+
refreshLearnings();
|
|
2916
|
+
refreshVariables();
|
|
2917
|
+
startWatchPolling();
|
|
2918
|
+
|
|
1278
2919
|
})();
|
|
1279
2920
|
</script>
|
|
1280
2921
|
</body>
|