@matware/e2e-runner 1.1.1 → 1.2.1
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/plugin.json +9 -0
- package/.mcp.json +9 -0
- package/README.md +475 -307
- package/agents/test-analyzer.md +81 -0
- package/agents/test-creator.md +102 -0
- package/agents/test-improver.md +140 -0
- package/bin/cli.js +194 -6
- package/commands/create-test.md +50 -0
- package/commands/run.md +49 -0
- package/commands/verify-issue.md +63 -0
- package/package.json +10 -2
- package/skills/e2e-testing/SKILL.md +166 -0
- package/skills/e2e-testing/references/action-types.md +100 -0
- package/skills/e2e-testing/references/test-json-format.md +159 -0
- package/skills/e2e-testing/references/troubleshooting.md +182 -0
- package/src/actions.js +273 -18
- package/src/ai-generate.js +87 -7
- package/src/config.js +28 -0
- package/src/dashboard.js +156 -6
- package/src/db.js +207 -13
- package/src/index.js +9 -3
- package/src/learner-markdown.js +177 -0
- package/src/learner-neo4j.js +255 -0
- package/src/learner-sqlite.js +354 -0
- package/src/learner.js +413 -0
- package/src/mcp-tools.js +448 -18
- package/src/module-resolver.js +273 -0
- package/src/narrate.js +225 -0
- package/src/neo4j-pool.js +124 -0
- package/src/reporter.js +35 -2
- package/src/runner.js +120 -46
- package/src/verify.js +5 -3
- package/templates/build-dashboard.js +28 -0
- package/templates/dashboard/app.js +1152 -0
- package/templates/dashboard/styles.css +413 -0
- package/templates/dashboard/template.html +201 -0
- package/templates/dashboard.html +964 -378
- package/templates/docker-compose-neo4j.yml +19 -0
- package/templates/e2e.config.js +3 -0
package/templates/dashboard.html
CHANGED
|
@@ -84,6 +84,24 @@ a{color:var(--accent);text-decoration:none}
|
|
|
84
84
|
.stat-val.accent{color:var(--accent)}
|
|
85
85
|
.stat-val.purple{color:var(--purple)}
|
|
86
86
|
|
|
87
|
+
/* ── Learnings ── */
|
|
88
|
+
.learn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;margin-bottom:20px}
|
|
89
|
+
.learn-stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px;text-align:center}
|
|
90
|
+
.learn-stat-val{font-size:24px;font-weight:700;margin-bottom:2px}
|
|
91
|
+
.learn-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
92
|
+
.learn-section{margin-bottom:20px}
|
|
93
|
+
.learn-section-title{font-family:var(--sans);font-size:13px;font-weight:600;margin-bottom:10px;color:var(--text)}
|
|
94
|
+
.learn-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
95
|
+
.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}
|
|
96
|
+
.learn-table th:hover{color:var(--text2)}
|
|
97
|
+
.learn-table th.sorted::after{content:' \\25B2';font-size:8px}
|
|
98
|
+
.learn-table th.sorted.desc::after{content:' \\25BC'}
|
|
99
|
+
.learn-table td{padding:6px 10px;border-bottom:1px solid var(--border);color:var(--text2)}
|
|
100
|
+
.learn-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:10px;color:var(--text)}
|
|
101
|
+
.learn-table tbody tr:hover td{background:var(--surface2);color:var(--text)}
|
|
102
|
+
.learn-trend-chart{width:100%;height:100px;margin-bottom:20px}
|
|
103
|
+
.learn-trend-chart svg{width:100%;height:100%}
|
|
104
|
+
|
|
87
105
|
/* ── Tables ── */
|
|
88
106
|
.tbl-wrap{overflow-x:auto}
|
|
89
107
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
@@ -105,16 +123,80 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
105
123
|
.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
124
|
.chart-bar:hover .tip{display:block}
|
|
107
125
|
|
|
126
|
+
/* ── Project Accordion ── */
|
|
127
|
+
.project-accordion{margin-bottom:2px}
|
|
128
|
+
.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}
|
|
129
|
+
.project-accordion-header:hover{background:var(--surface2);border-color:var(--border-hi)}
|
|
130
|
+
.project-accordion.open>.project-accordion-header{border-radius:var(--r) var(--r) 0 0;border-bottom-color:transparent;background:var(--surface2)}
|
|
131
|
+
.project-accordion-chevron{font-size:10px;color:var(--text3);transition:transform .2s ease;flex-shrink:0;width:16px;text-align:center}
|
|
132
|
+
.project-accordion.open>.project-accordion-header .project-accordion-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
133
|
+
.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}
|
|
134
|
+
.project-accordion-meta{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
135
|
+
.project-accordion-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--surface3);color:var(--text2)}
|
|
136
|
+
.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)}
|
|
137
|
+
.project-accordion.open>.project-accordion-body{max-height:5000px}
|
|
138
|
+
|
|
108
139
|
/* ── Suite Cards ── */
|
|
109
|
-
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(
|
|
110
|
-
.suite-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
|
|
111
|
-
.suite-card:hover{border-color:var(--border-hi)}
|
|
112
|
-
.suite-card-head{display:flex;align-items:center;
|
|
113
|
-
.suite-card-
|
|
114
|
-
.suite-card-
|
|
115
|
-
.suite-card-
|
|
116
|
-
.suite-card-
|
|
117
|
-
.suite-card-
|
|
140
|
+
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;padding:16px}
|
|
141
|
+
.suite-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;transition:border-color .2s,box-shadow .2s}
|
|
142
|
+
.suite-card:hover{border-color:var(--border-hi);box-shadow:0 2px 12px rgba(0,0,0,.25)}
|
|
143
|
+
.suite-card-head{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
144
|
+
.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}
|
|
145
|
+
.suite-card-info{flex:1;min-width:0}
|
|
146
|
+
.suite-card-name{font-family:var(--sans);font-weight:600;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
147
|
+
.suite-card-file{font-size:10px;color:var(--text3);margin-top:1px;font-family:var(--mono)}
|
|
148
|
+
.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)}
|
|
149
|
+
.suite-card-count-num{font-size:16px;font-weight:700;color:var(--text);line-height:1}
|
|
150
|
+
.suite-card-count-lbl{font-size:8px;color:var(--text3);text-transform:uppercase;letter-spacing:.06em;line-height:1;margin-top:2px}
|
|
151
|
+
.suite-card-body{padding:0}
|
|
152
|
+
.suite-card-tests{list-style:none;max-height:180px;overflow-y:auto}
|
|
153
|
+
.suite-card-tests::-webkit-scrollbar{width:4px}
|
|
154
|
+
.suite-card-tests::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
|
|
155
|
+
.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)}
|
|
156
|
+
.suite-card-tests li:last-child{border-bottom:none}
|
|
157
|
+
.suite-card-tests li:hover{color:var(--text);background:var(--surface2)}
|
|
158
|
+
.suite-card-tests li::before{content:"\25B8";position:absolute;left:14px;color:var(--text3);font-size:9px;top:7px;transition:transform .15s}
|
|
159
|
+
.suite-card-tests li.expanded{color:var(--accent);background:var(--accent-dim)}
|
|
160
|
+
.suite-card-tests li.expanded::before{content:"\25BE";color:var(--accent)}
|
|
161
|
+
.suite-test-steps{padding:8px 0 8px 14px;border-left:2px solid var(--accent-dim);margin:6px 0 4px 6px}
|
|
162
|
+
.suite-card-footer{padding:10px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;background:var(--surface)}
|
|
163
|
+
|
|
164
|
+
/* ── Suite Detail Modal ── */
|
|
165
|
+
.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)}
|
|
166
|
+
.suite-modal-overlay.open{display:flex}
|
|
167
|
+
.suite-modal{width:100%;max-width:960px;background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden;animation:suiteModalIn .2s ease}
|
|
168
|
+
@keyframes suiteModalIn{from{opacity:0;transform:translateY(-12px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
|
|
169
|
+
.suite-modal-header{display:flex;align-items:center;gap:14px;padding:18px 24px;background:var(--surface2);border-bottom:1px solid var(--border)}
|
|
170
|
+
.suite-modal-header .suite-card-icon{width:36px;height:36px;font-size:16px}
|
|
171
|
+
.suite-modal-title{flex:1;min-width:0}
|
|
172
|
+
.suite-modal-title h2{font-family:var(--sans);font-size:16px;font-weight:700;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
173
|
+
.suite-modal-title span{font-size:10px;color:var(--text3);font-family:var(--mono)}
|
|
174
|
+
.suite-modal-actions{display:flex;gap:8px;align-items:center;flex-shrink:0}
|
|
175
|
+
.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}
|
|
176
|
+
.suite-modal-close:hover{background:var(--red-dim);border-color:rgba(239,68,68,.3);color:var(--red)}
|
|
177
|
+
.suite-modal-body{padding:0}
|
|
178
|
+
.suite-modal-loading{padding:32px;text-align:center;color:var(--text3);font-size:12px}
|
|
179
|
+
.suite-modal-test{border-bottom:1px solid var(--border)}
|
|
180
|
+
.suite-modal-test:last-child{border-bottom:none}
|
|
181
|
+
.suite-modal-test-header{display:flex;align-items:center;gap:10px;padding:12px 24px;cursor:pointer;transition:background .15s;user-select:none}
|
|
182
|
+
.suite-modal-test-header:hover{background:var(--surface2)}
|
|
183
|
+
.suite-modal-test.open>.suite-modal-test-header{background:var(--surface2)}
|
|
184
|
+
.suite-modal-test-chevron{font-size:9px;color:var(--text3);transition:transform .15s;width:14px;text-align:center;flex-shrink:0}
|
|
185
|
+
.suite-modal-test.open>.suite-modal-test-header .suite-modal-test-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
186
|
+
.suite-modal-test-name{font-size:12px;font-weight:500;color:var(--text);flex:1}
|
|
187
|
+
.suite-modal-test-badge{font-size:9px;padding:2px 7px;border-radius:8px;background:var(--surface3);color:var(--text3);flex-shrink:0}
|
|
188
|
+
.suite-modal-test-actions{display:none;padding:0 24px 16px 48px}
|
|
189
|
+
.suite-modal-test.open>.suite-modal-test-actions{display:block}
|
|
190
|
+
.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)}
|
|
191
|
+
.suite-modal-step:last-child{border-bottom:none}
|
|
192
|
+
.suite-modal-step-num{width:22px;text-align:right;color:var(--text3);flex-shrink:0;font-size:10px;padding-top:1px}
|
|
193
|
+
.suite-modal-step-type{color:var(--purple);font-weight:600;flex-shrink:0;min-width:120px}
|
|
194
|
+
.suite-modal-step-detail{color:var(--text2);flex:1;min-width:0;word-break:break-word}
|
|
195
|
+
.suite-modal-step-detail .step-sel{color:var(--accent)}
|
|
196
|
+
.suite-modal-step-detail .step-arrow{color:var(--text3);margin:0 4px}
|
|
197
|
+
.suite-modal-step-detail .step-val{color:var(--text)}
|
|
198
|
+
.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}
|
|
199
|
+
.suite-modal-expect-label{font-weight:600;flex-shrink:0}
|
|
118
200
|
|
|
119
201
|
/* ── Live Execution ── */
|
|
120
202
|
.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}
|
|
@@ -145,14 +227,14 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
145
227
|
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
146
228
|
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
147
229
|
.live-test .lt-icon{font-size:12px}
|
|
148
|
-
.lt-actions{
|
|
230
|
+
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
149
231
|
.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
232
|
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
151
233
|
.lt-step .step-icon.ok{color:var(--green)}
|
|
152
234
|
.lt-step .step-icon.fail{color:var(--red)}
|
|
153
235
|
.lt-step .step-icon.run{color:var(--purple)}
|
|
154
236
|
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
155
|
-
.lt-step .step-detail{color:var(--text2);
|
|
237
|
+
.lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
|
|
156
238
|
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
157
239
|
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
158
240
|
.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}
|
|
@@ -171,7 +253,7 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
171
253
|
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
172
254
|
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
173
255
|
.lr-project-name{letter-spacing:.5px}
|
|
174
|
-
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-
|
|
256
|
+
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
|
|
175
257
|
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
176
258
|
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
177
259
|
.live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
|
|
@@ -206,77 +288,112 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
206
288
|
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
207
289
|
|
|
208
290
|
/* ── Inline Run Detail ── */
|
|
209
|
-
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--
|
|
291
|
+
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--purple)}
|
|
210
292
|
.run-detail-row:hover td{background:transparent!important}
|
|
211
|
-
.rd-wrap{overflow:hidden;transition:max-height .
|
|
212
|
-
.rd-wrap.open{max-height:
|
|
213
|
-
.rd-inner{padding:
|
|
214
|
-
.rd-
|
|
293
|
+
.rd-wrap{overflow:hidden;transition:max-height .4s cubic-bezier(.4,0,.2,1);max-height:0}
|
|
294
|
+
.rd-wrap.open{max-height:10000px}
|
|
295
|
+
.rd-inner{padding:20px 24px;background:var(--bg);position:relative}
|
|
296
|
+
.rd-inner::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--purple),var(--accent),transparent 80%)}
|
|
297
|
+
.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}
|
|
298
|
+
.rd-summary>div{background:var(--surface);padding:14px 16px;text-align:center;transition:background .15s}
|
|
299
|
+
.rd-summary>div:hover{background:var(--surface2)}
|
|
300
|
+
.rd-s-label{font-size:9px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
301
|
+
.rd-s-val{font-weight:700;font-size:18px;margin-top:4px}
|
|
302
|
+
.rd-test{margin-bottom:12px;background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden;transition:border-color .2s}
|
|
303
|
+
.rd-test:hover{border-color:var(--border-hi)}
|
|
215
304
|
.rd-test:last-child{margin-bottom:0}
|
|
216
|
-
.rd-test-head{display:flex;align-items:center;gap:
|
|
217
|
-
.rd-test-
|
|
218
|
-
.rd-test-
|
|
219
|
-
.rd-test-
|
|
220
|
-
.rd-
|
|
221
|
-
.rd-
|
|
222
|
-
.rd-
|
|
223
|
-
.rd-
|
|
224
|
-
.rd-
|
|
225
|
-
.rd-
|
|
305
|
+
.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}
|
|
306
|
+
.rd-test-head::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px}
|
|
307
|
+
.rd-test.pass .rd-test-head::before{background:var(--green)}
|
|
308
|
+
.rd-test.fail .rd-test-head::before{background:var(--red)}
|
|
309
|
+
.rd-test.flaky .rd-test-head::before{background:var(--amber)}
|
|
310
|
+
.rd-test-name{font-family:var(--sans);font-weight:600;font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
311
|
+
.rd-test-dur{font-size:11px;color:var(--text2);flex-shrink:0;font-variant-numeric:tabular-nums}
|
|
312
|
+
.rd-test-body{padding:16px}
|
|
313
|
+
.rd-retries{font-size:11px;color:var(--amber);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
314
|
+
.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}
|
|
315
|
+
.rd-shots{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
316
|
+
.rd-shot{width:140px;border-radius:6px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--surface2)}
|
|
317
|
+
.rd-shot:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.35)}
|
|
318
|
+
.rd-shot img{width:100%;height:84px;object-fit:cover;display:block}
|
|
319
|
+
.rd-shot .rd-shot-cap{font-size:9px;color:var(--text3);padding:5px 8px;background:var(--surface)}
|
|
226
320
|
.rd-shot .rd-shot-cap .cap-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
227
321
|
.rd-shot .rd-shot-cap .ss-hash{margin-top:3px}
|
|
228
322
|
.rd-shot.err-shot{border-color:rgba(239,68,68,.4)}
|
|
229
323
|
.rd-shot.err-shot .rd-shot-cap{color:var(--red)}
|
|
230
|
-
.rd-logs{margin-
|
|
231
|
-
.rd-log-label{font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.
|
|
232
|
-
.rd-log-item{font-size:
|
|
324
|
+
.rd-logs{margin-bottom:12px}
|
|
325
|
+
.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}
|
|
326
|
+
.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}
|
|
233
327
|
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
234
328
|
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
235
|
-
.rd-net-
|
|
236
|
-
.rd-net-
|
|
237
|
-
.rd-net-
|
|
238
|
-
.rd-net-
|
|
239
|
-
.rd-net-
|
|
240
|
-
.rd-net-
|
|
241
|
-
.rd-net-
|
|
329
|
+
.rd-net-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
330
|
+
.rd-net-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
331
|
+
.rd-net-head:hover{background:var(--surface3)}
|
|
332
|
+
.rd-net-head .net-arrow{font-size:9px;color:var(--text3);transition:transform .2s}
|
|
333
|
+
.rd-net-head.open .net-arrow{transform:rotate(90deg)}
|
|
334
|
+
.rd-net-head .net-title{font-family:var(--sans);font-size:12px;font-weight:600;color:var(--text)}
|
|
335
|
+
.rd-net-head .net-stats{margin-left:auto;display:flex;gap:12px;font-size:10px}
|
|
336
|
+
.rd-net-head .net-stat{color:var(--text3)}
|
|
337
|
+
.rd-net-head .net-stat strong{color:var(--text2)}
|
|
338
|
+
.rd-net-head .net-stat.has-err strong{color:var(--red)}
|
|
339
|
+
.rd-net-body{display:none;max-height:600px;overflow-y:auto}
|
|
340
|
+
.rd-net-head.open~.rd-net-body{display:block}
|
|
341
|
+
.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}
|
|
342
|
+
.rd-net-cols .col-e{width:16px;flex-shrink:0}
|
|
343
|
+
.rd-net-cols .col-m{width:50px;flex-shrink:0}
|
|
344
|
+
.rd-net-cols .col-s{width:60px;flex-shrink:0}
|
|
345
|
+
.rd-net-cols .col-u{flex:1;min-width:0}
|
|
346
|
+
.rd-net-cols .col-d{width:60px;flex-shrink:0;text-align:right}
|
|
347
|
+
.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}
|
|
242
348
|
.rd-net-row:hover{background:var(--surface2)}
|
|
243
|
-
.rd-net-row.has-error{
|
|
244
|
-
.rd-net-
|
|
349
|
+
.rd-net-row.has-error{background:rgba(239,68,68,.03)}
|
|
350
|
+
.rd-net-row.has-error:hover{background:rgba(239,68,68,.06)}
|
|
351
|
+
.rd-net-expand{width:16px;flex-shrink:0;font-size:8px;color:var(--text3);transition:transform .2s;text-align:center}
|
|
352
|
+
.rd-net-row.open .rd-net-expand{transform:rotate(90deg);color:var(--accent)}
|
|
353
|
+
.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}
|
|
245
354
|
.rd-net-method.get{background:var(--accent-dim);color:var(--accent)}
|
|
246
355
|
.rd-net-method.post{background:var(--green-dim);color:var(--green)}
|
|
247
356
|
.rd-net-method.put,.rd-net-method.patch{background:var(--amber-dim);color:var(--amber)}
|
|
248
357
|
.rd-net-method.delete{background:var(--red-dim);color:var(--red)}
|
|
249
|
-
.rd-net-status{
|
|
250
|
-
.rd-net-status.s2xx{
|
|
251
|
-
.rd-net-status.s3xx{
|
|
252
|
-
.rd-net-status.s4xx,.rd-net-status.s5xx{
|
|
253
|
-
.rd-net-url{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap
|
|
254
|
-
.rd-net-dur{color:var(--text3);
|
|
255
|
-
.rd-net-
|
|
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}
|
|
358
|
+
.rd-net-status{width:60px;flex-shrink:0;font-size:10px;font-weight:600}
|
|
359
|
+
.rd-net-status.s2xx{color:var(--green)}
|
|
360
|
+
.rd-net-status.s3xx{color:var(--amber)}
|
|
361
|
+
.rd-net-status.s4xx,.rd-net-status.s5xx{color:var(--red)}
|
|
362
|
+
.rd-net-url{flex:1;min-width:0;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
363
|
+
.rd-net-dur{width:60px;flex-shrink:0;text-align:right;color:var(--text3);font-variant-numeric:tabular-nums}
|
|
364
|
+
.rd-net-detail{display:none;border-bottom:1px solid var(--border);background:var(--bg);overflow:hidden}
|
|
258
365
|
.rd-net-row.open+.rd-net-detail{display:block}
|
|
259
|
-
.rd-
|
|
260
|
-
.rd-
|
|
261
|
-
.rd-
|
|
262
|
-
.rd-
|
|
263
|
-
.rd-
|
|
264
|
-
.rd-
|
|
265
|
-
.rd-
|
|
266
|
-
.rd-
|
|
366
|
+
.rd-nd-section{border-bottom:1px solid var(--border)}
|
|
367
|
+
.rd-nd-section:last-child{border-bottom:none}
|
|
368
|
+
.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}
|
|
369
|
+
.rd-nd-toggle:hover{background:var(--surface2);color:var(--text2)}
|
|
370
|
+
.rd-nd-toggle .nd-arrow{font-size:8px;transition:transform .2s}
|
|
371
|
+
.rd-nd-toggle.open .nd-arrow{transform:rotate(90deg)}
|
|
372
|
+
.rd-nd-toggle .nd-count{margin-left:auto;font-size:9px;font-weight:400;letter-spacing:0;text-transform:none}
|
|
373
|
+
.rd-nd-content{display:none;padding:0 16px 10px;max-height:300px;overflow-y:auto}
|
|
374
|
+
.rd-nd-toggle.open+.rd-nd-content{display:block}
|
|
375
|
+
.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)}
|
|
376
|
+
.rd-hdr-table{display:grid;grid-template-columns:minmax(120px,auto) 1fr;gap:0;font-size:11px}
|
|
377
|
+
.rd-hdr-row{display:contents}
|
|
378
|
+
.rd-hdr-row:hover .rd-hdr-key,.rd-hdr-row:hover .rd-hdr-val{background:var(--surface2)}
|
|
379
|
+
.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}
|
|
380
|
+
.rd-hdr-val{padding:3px 0;color:var(--text2);border-bottom:1px solid var(--border);word-break:break-all}
|
|
381
|
+
.rd-nd-empty{color:var(--text3);font-size:11px;padding:4px 0;font-style:italic}
|
|
382
|
+
.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}
|
|
383
|
+
.copy-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-dim)}
|
|
384
|
+
.copy-btn.copied{color:var(--green);border-color:var(--green);background:var(--green-dim)}
|
|
385
|
+
.rd-error-msg .copy-btn{position:absolute;top:8px;right:8px;opacity:0}
|
|
386
|
+
.rd-error-msg:hover .copy-btn{opacity:1}
|
|
387
|
+
.rd-net-row .copy-btn{opacity:0;padding:1px 6px}
|
|
388
|
+
.rd-net-row:hover .copy-btn{opacity:1}
|
|
389
|
+
.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}
|
|
390
|
+
.rd-net-body .rd-log-item:last-child{border-bottom:none}
|
|
391
|
+
.rd-net-body .rd-log-item.error{border-left-color:var(--red)}
|
|
392
|
+
.rd-net-body .rd-log-item.warn,.rd-net-body .rd-log-item.warning{border-left-color:var(--amber)}
|
|
267
393
|
tr.expanded td{background:var(--surface2)!important}
|
|
268
394
|
tr.expanded td:first-child{position:relative}
|
|
269
395
|
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
270
396
|
|
|
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
397
|
/* ── Screenshot Hash Badge ── */
|
|
281
398
|
.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
399
|
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
@@ -291,14 +408,79 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
291
408
|
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
292
409
|
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
293
410
|
|
|
411
|
+
/* ── Empty ── */
|
|
412
|
+
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
413
|
+
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
414
|
+
|
|
415
|
+
/* ── Modal ── */
|
|
416
|
+
.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}
|
|
417
|
+
.modal.open{display:flex}
|
|
418
|
+
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
419
|
+
|
|
420
|
+
/* ── Toast Notifications (NEW) ── */
|
|
421
|
+
.toast-container{position:fixed;bottom:24px;right:24px;z-index:300;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}
|
|
422
|
+
.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}
|
|
423
|
+
.toast.success{background:var(--green);border:1px solid rgba(255,255,255,.15)}
|
|
424
|
+
.toast.error{background:var(--red);border:1px solid rgba(255,255,255,.15)}
|
|
425
|
+
.toast.info{background:var(--accent);border:1px solid rgba(255,255,255,.15)}
|
|
426
|
+
.toast.fade-out{animation:toastOut .3s ease forwards}
|
|
427
|
+
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}
|
|
428
|
+
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(24px)}}
|
|
429
|
+
|
|
430
|
+
/* ── Filter Bar (NEW) ── */
|
|
431
|
+
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
432
|
+
.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}
|
|
433
|
+
.filter-btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
434
|
+
.filter-btn.active{background:var(--accent-dim);border-color:var(--accent);color:var(--accent)}
|
|
435
|
+
.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}
|
|
436
|
+
.filter-bar input:focus{outline:none;border-color:var(--accent)}
|
|
437
|
+
.filter-bar input::placeholder{color:var(--text3)}
|
|
438
|
+
|
|
439
|
+
/* ── Module Cards (NEW) ── */
|
|
440
|
+
.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}
|
|
441
|
+
.module-section-title .mod-icon{color:var(--purple)}
|
|
442
|
+
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
|
443
|
+
.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}
|
|
444
|
+
.module-card:hover{border-color:var(--border-hi);border-left-color:var(--purple)}
|
|
445
|
+
.module-card-name{font-weight:600;font-size:13px;color:var(--purple);margin-bottom:4px}
|
|
446
|
+
.module-card-desc{font-size:11px;color:var(--text2);margin-bottom:8px}
|
|
447
|
+
.module-card-meta{font-size:10px;color:var(--text3);display:flex;gap:12px}
|
|
448
|
+
.module-card-params{list-style:none;font-size:10px;color:var(--text2);margin-top:6px}
|
|
449
|
+
.module-card-params li{padding:2px 0}
|
|
450
|
+
.module-card-params li::before{content:'$';color:var(--purple);margin-right:4px}
|
|
451
|
+
|
|
452
|
+
/* ── Keyboard Shortcuts Modal (NEW) ── */
|
|
453
|
+
.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}
|
|
454
|
+
.kb-modal.open{display:flex}
|
|
455
|
+
.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}
|
|
456
|
+
.kb-modal-content h2{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
|
457
|
+
.kb-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
458
|
+
.kb-row:last-child{border-bottom:none}
|
|
459
|
+
.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)}
|
|
460
|
+
.kb-desc{font-size:12px;color:var(--text2)}
|
|
461
|
+
|
|
462
|
+
/* ── Actions Panel in Run Detail (NEW) ── */
|
|
463
|
+
.rd-actions-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
464
|
+
.rd-actions-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
465
|
+
.rd-actions-head:hover{background:var(--surface3)}
|
|
466
|
+
.rd-actions-head .act-arrow{font-size:9px;color:var(--text3);transition:transform .2s}
|
|
467
|
+
.rd-actions-head.open .act-arrow{transform:rotate(90deg)}
|
|
468
|
+
.rd-actions-body{display:none;max-height:500px;overflow-y:auto;padding:8px 14px}
|
|
469
|
+
.rd-actions-head.open~.rd-actions-body{display:block}
|
|
470
|
+
|
|
471
|
+
/* ── Serial Badge (NEW) ── */
|
|
472
|
+
.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}
|
|
473
|
+
|
|
294
474
|
/* ── Responsive ── */
|
|
295
475
|
@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}
|
|
476
|
+
.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}
|
|
297
477
|
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
298
478
|
.main{margin-left:60px}
|
|
299
|
-
.suite-grid,.gallery{grid-template-columns:1fr}
|
|
479
|
+
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
300
480
|
.lr-test-grid{grid-template-columns:1fr}
|
|
481
|
+
.toast-container{right:12px;bottom:12px}
|
|
301
482
|
}
|
|
483
|
+
|
|
302
484
|
</style>
|
|
303
485
|
</head>
|
|
304
486
|
<body>
|
|
@@ -323,13 +505,16 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
323
505
|
<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>
|
|
324
506
|
</div>
|
|
325
507
|
<div class="nav-item active" data-view="suites">
|
|
326
|
-
<i class="icon">▷</i><span>Suites</span>
|
|
508
|
+
<i class="icon">▷</i><span>Suites</span><span class="badge" id="badgeSuites">-</span>
|
|
327
509
|
</div>
|
|
328
510
|
<div class="nav-item" data-view="runs">
|
|
329
|
-
<i class="icon">☰</i><span>Runs</span>
|
|
511
|
+
<i class="icon">☰</i><span>Runs</span><span class="badge" id="badgeRuns">-</span>
|
|
330
512
|
</div>
|
|
331
513
|
<div class="nav-item" data-view="screenshots">
|
|
332
|
-
<i class="icon">▣</i><span>Screenshots</span>
|
|
514
|
+
<i class="icon">▣</i><span>Screenshots</span><span class="badge" id="badgeScreenshots">-</span>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="nav-item" data-view="learnings">
|
|
517
|
+
<i class="icon">★</i><span>Learnings</span><span class="badge" id="badgeLearnings">-</span>
|
|
333
518
|
</div>
|
|
334
519
|
|
|
335
520
|
<div class="pool-status" id="poolStatus">
|
|
@@ -379,9 +564,10 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
379
564
|
<div class="view active" id="view-suites">
|
|
380
565
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
381
566
|
<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
567
|
</div>
|
|
568
|
+
<div id="suiteAccordionContainer"></div>
|
|
384
569
|
<div class="suite-grid" id="suiteGrid"></div>
|
|
570
|
+
<div id="moduleSection"></div>
|
|
385
571
|
<div class="empty" id="suitesEmpty" style="display:none">
|
|
386
572
|
<div class="empty-icon">▷</div>
|
|
387
573
|
<p>No test suites found.</p>
|
|
@@ -390,11 +576,20 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
390
576
|
|
|
391
577
|
<!-- Runs View -->
|
|
392
578
|
<div class="view" id="view-runs">
|
|
393
|
-
<div style="
|
|
579
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
580
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Run History</div>
|
|
581
|
+
</div>
|
|
394
582
|
<div class="card">
|
|
395
583
|
<div class="card-label">Pass Rate Trend</div>
|
|
396
584
|
<div class="chart" id="trendChart"></div>
|
|
397
585
|
</div>
|
|
586
|
+
<div class="filter-bar" id="filterBar">
|
|
587
|
+
<button class="filter-btn active" data-filter="all">All</button>
|
|
588
|
+
<button class="filter-btn" data-filter="pass">Pass</button>
|
|
589
|
+
<button class="filter-btn" data-filter="fail">Fail</button>
|
|
590
|
+
<button class="filter-btn" data-filter="mixed">Mixed</button>
|
|
591
|
+
<input type="text" id="runSearchInput" placeholder="Search suite..." spellcheck="false">
|
|
592
|
+
</div>
|
|
398
593
|
<div class="card" style="padding:0">
|
|
399
594
|
<div class="tbl-wrap">
|
|
400
595
|
<table>
|
|
@@ -409,6 +604,34 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
409
604
|
</div>
|
|
410
605
|
</div>
|
|
411
606
|
|
|
607
|
+
<!-- Learnings View -->
|
|
608
|
+
<div class="view" id="view-learnings">
|
|
609
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
610
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Learnings</div>
|
|
611
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
612
|
+
<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">
|
|
613
|
+
<option value="7">7 days</option>
|
|
614
|
+
<option value="14">14 days</option>
|
|
615
|
+
<option value="30" selected>30 days</option>
|
|
616
|
+
<option value="90">90 days</option>
|
|
617
|
+
</select>
|
|
618
|
+
<button class="btn sm" id="btnExportLearnings">Export MD</button>
|
|
619
|
+
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
<div id="learningsOverview"></div>
|
|
623
|
+
<div id="learningsTrend"></div>
|
|
624
|
+
<div id="learningsFlaky"></div>
|
|
625
|
+
<div id="learningsSelectors"></div>
|
|
626
|
+
<div id="learningsPages"></div>
|
|
627
|
+
<div id="learningsApis"></div>
|
|
628
|
+
<div id="learningsErrors"></div>
|
|
629
|
+
<div class="empty" id="learningsEmpty" style="display:none">
|
|
630
|
+
<div class="empty-icon">★</div>
|
|
631
|
+
<p>No learnings data yet. Run some tests to start building knowledge.</p>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
|
|
412
635
|
<!-- Screenshots View -->
|
|
413
636
|
<div class="view" id="view-screenshots">
|
|
414
637
|
<div style="font-family:var(--sans);font-size:16px;font-weight:600;margin-bottom:20px">Screenshots</div>
|
|
@@ -427,6 +650,22 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
427
650
|
</div>
|
|
428
651
|
|
|
429
652
|
<div class="modal" id="modal"><img id="modalImg" src="" alt=""></div>
|
|
653
|
+
<div class="toast-container" id="toastContainer"></div>
|
|
654
|
+
<div class="kb-modal" id="kbModal">
|
|
655
|
+
<div class="kb-modal-content">
|
|
656
|
+
<h2>Keyboard Shortcuts</h2>
|
|
657
|
+
<div class="kb-row"><span class="kb-key">1</span><span class="kb-desc">Suites view</span></div>
|
|
658
|
+
<div class="kb-row"><span class="kb-key">2</span><span class="kb-desc">Runs view</span></div>
|
|
659
|
+
<div class="kb-row"><span class="kb-key">3</span><span class="kb-desc">Screenshots view</span></div>
|
|
660
|
+
<div class="kb-row"><span class="kb-key">4</span><span class="kb-desc">Learnings view</span></div>
|
|
661
|
+
<div class="kb-row"><span class="kb-key">5</span><span class="kb-desc">Live view</span></div>
|
|
662
|
+
<div class="kb-row"><span class="kb-key">j / k</span><span class="kb-desc">Navigate runs (next / previous)</span></div>
|
|
663
|
+
<div class="kb-row"><span class="kb-key">Enter</span><span class="kb-desc">Expand / collapse selected run</span></div>
|
|
664
|
+
<div class="kb-row"><span class="kb-key">Esc</span><span class="kb-desc">Close modal / collapse run</span></div>
|
|
665
|
+
<div class="kb-row"><span class="kb-key">r</span><span class="kb-desc">Refresh current view</span></div>
|
|
666
|
+
<div class="kb-row"><span class="kb-key">?</span><span class="kb-desc">Show this help</span></div>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
430
669
|
|
|
431
670
|
<script>
|
|
432
671
|
(function(){
|
|
@@ -450,53 +689,89 @@ function css(n){return n.replace(/[^a-zA-Z0-9\-_]/g,'_')}
|
|
|
450
689
|
function dur(ms){return ms>=1000?(ms/1000).toFixed(1)+'s':ms+'ms'}
|
|
451
690
|
function fdate(iso){return iso?new Date(iso).toLocaleString():'--'}
|
|
452
691
|
|
|
453
|
-
/** Pretty-print a string if it's JSON, otherwise return as-is */
|
|
454
692
|
function prettyJson(str){
|
|
455
693
|
if(!str)return '';
|
|
456
694
|
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
457
695
|
}
|
|
458
696
|
|
|
459
|
-
/** Format headers object as readable string */
|
|
460
697
|
function fmtHeaders(h){
|
|
461
698
|
if(!h||typeof h!=='object')return '';
|
|
462
699
|
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
463
700
|
}
|
|
464
701
|
|
|
465
|
-
|
|
702
|
+
function buildHeaderKV(h){
|
|
703
|
+
if(!h||typeof h!=='object') return el('div',{className:'rd-nd-empty'},'No data');
|
|
704
|
+
var table=el('div',{className:'rd-hdr-table'});
|
|
705
|
+
Object.keys(h).forEach(function(k){
|
|
706
|
+
var row=el('div',{className:'rd-hdr-row'});
|
|
707
|
+
row.appendChild(el('span',{className:'rd-hdr-key'},k));
|
|
708
|
+
row.appendChild(el('span',{className:'rd-hdr-val'},String(h[k])));
|
|
709
|
+
table.appendChild(row);
|
|
710
|
+
});
|
|
711
|
+
return table;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function makeCopyBtn(getTextFn){
|
|
715
|
+
var btn=el('span',{className:'copy-btn',onclick:function(e){
|
|
716
|
+
e.stopPropagation();
|
|
717
|
+
var text=typeof getTextFn==='function'?getTextFn():String(getTextFn);
|
|
718
|
+
navigator.clipboard.writeText(text).then(function(){
|
|
719
|
+
btn.textContent='\u2713 Copied';
|
|
720
|
+
btn.classList.add('copied');
|
|
721
|
+
setTimeout(function(){btn.textContent='\u2398 Copy';btn.classList.remove('copied')},1200);
|
|
722
|
+
});
|
|
723
|
+
}},'\u2398 Copy');
|
|
724
|
+
return btn;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function buildNdSection(title,contentEl,count,copyText){
|
|
728
|
+
var toggle=el('div',{className:'rd-nd-toggle'},[
|
|
729
|
+
el('span',{className:'nd-arrow'},'\u25B6'),
|
|
730
|
+
el('span',null,title),
|
|
731
|
+
count?el('span',{className:'nd-count'},count+' entries'):null,
|
|
732
|
+
makeCopyBtn(copyText||function(){return contentWrap.textContent})
|
|
733
|
+
]);
|
|
734
|
+
var contentWrap=el('div',{className:'rd-nd-content'});
|
|
735
|
+
contentWrap.appendChild(contentEl);
|
|
736
|
+
toggle.addEventListener('click',function(e){
|
|
737
|
+
e.stopPropagation();
|
|
738
|
+
toggle.classList.toggle('open');
|
|
739
|
+
});
|
|
740
|
+
return el('div',{className:'rd-nd-section'},[toggle,contentWrap]);
|
|
741
|
+
}
|
|
742
|
+
|
|
466
743
|
function buildNetRow(n){
|
|
467
|
-
var mCls='rd-net-method '+n.method.toLowerCase();
|
|
468
|
-
var
|
|
744
|
+
var mCls='rd-net-method '+(n.method||'GET').toLowerCase();
|
|
745
|
+
var sCode=n.status||0;
|
|
746
|
+
var sCls='rd-net-status '+(sCode<300?'s2xx':sCode<400?'s3xx':sCode<500?'s4xx':'s5xx');
|
|
469
747
|
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
470
|
-
var rowCls='rd-net-row'+(
|
|
748
|
+
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
471
749
|
var row=el('div',{className:rowCls},[
|
|
472
|
-
|
|
473
|
-
el('span',{className:mCls},n.method),
|
|
474
|
-
el('span',{className:sCls},String(
|
|
475
|
-
el('span',{className:'rd-net-url'},n.url),
|
|
750
|
+
el('span',{className:'rd-net-expand'},hasDetail?'\u25B6':''),
|
|
751
|
+
el('span',{className:mCls},n.method||'GET'),
|
|
752
|
+
el('span',{className:sCls},String(sCode)),
|
|
753
|
+
el('span',{className:'rd-net-url'},n.url||''),
|
|
754
|
+
makeCopyBtn(n.url||''),
|
|
476
755
|
el('span',{className:'rd-net-dur'},dur(n.duration))
|
|
477
756
|
]);
|
|
478
757
|
var detail=null;
|
|
479
758
|
if(hasDetail){
|
|
480
759
|
var sections=[];
|
|
481
760
|
if(n.requestHeaders){
|
|
482
|
-
var
|
|
483
|
-
|
|
484
|
-
sections.push(s);
|
|
761
|
+
var hCount=Object.keys(n.requestHeaders).length;
|
|
762
|
+
sections.push(buildNdSection('Request Headers',buildHeaderKV(n.requestHeaders),hCount,fmtHeaders(n.requestHeaders)));
|
|
485
763
|
}
|
|
486
764
|
if(n.requestBody){
|
|
487
|
-
var
|
|
488
|
-
|
|
489
|
-
sections.push(s2);
|
|
765
|
+
var rbText=prettyJson(n.requestBody);
|
|
766
|
+
sections.push(buildNdSection('Request Body',el('pre',null,rbText),null,rbText));
|
|
490
767
|
}
|
|
491
768
|
if(n.responseHeaders){
|
|
492
|
-
var
|
|
493
|
-
|
|
494
|
-
sections.push(s3);
|
|
769
|
+
var rhCount=Object.keys(n.responseHeaders).length;
|
|
770
|
+
sections.push(buildNdSection('Response Headers',buildHeaderKV(n.responseHeaders),rhCount,fmtHeaders(n.responseHeaders)));
|
|
495
771
|
}
|
|
496
772
|
if(n.responseBody){
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
sections.push(s4);
|
|
773
|
+
var respText=prettyJson(n.responseBody);
|
|
774
|
+
sections.push(buildNdSection('Response Body',el('pre',null,respText),null,respText));
|
|
500
775
|
}
|
|
501
776
|
detail=el('div',{className:'rd-net-detail'},sections);
|
|
502
777
|
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
@@ -530,7 +805,6 @@ function createHashBadge(hash){
|
|
|
530
805
|
return badge;
|
|
531
806
|
}
|
|
532
807
|
|
|
533
|
-
/* ── Trigger source badge helper ── */
|
|
534
808
|
function createTriggerBadge(source){
|
|
535
809
|
var s=source||'unknown';
|
|
536
810
|
var labels={dashboard:'Dashboard',mcp:'MCP',cli:'CLI',unknown:'--'};
|
|
@@ -542,10 +816,44 @@ function createTriggerBadge(source){
|
|
|
542
816
|
return badge;
|
|
543
817
|
}
|
|
544
818
|
|
|
819
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
820
|
+
Toast Notifications (Improvement 4)
|
|
821
|
+
══════════════════════════════════════════════════════════════════ */
|
|
822
|
+
function showToast(message,type){
|
|
823
|
+
type=type||'info';
|
|
824
|
+
var container=$('#toastContainer');
|
|
825
|
+
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
826
|
+
var t=el('div',{className:'toast '+type},[
|
|
827
|
+
el('span',null,icons[type]||''),
|
|
828
|
+
el('span',null,message)
|
|
829
|
+
]);
|
|
830
|
+
container.appendChild(t);
|
|
831
|
+
setTimeout(function(){
|
|
832
|
+
t.classList.add('fade-out');
|
|
833
|
+
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
834
|
+
},5000);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
838
|
+
Download helper (Improvement 8)
|
|
839
|
+
══════════════════════════════════════════════════════════════════ */
|
|
840
|
+
function downloadFile(filename,content,mimeType){
|
|
841
|
+
var blob=new Blob([content],{type:mimeType||'text/plain'});
|
|
842
|
+
var url=URL.createObjectURL(blob);
|
|
843
|
+
var a=document.createElement('a');
|
|
844
|
+
a.href=url;a.download=filename;
|
|
845
|
+
document.body.appendChild(a);a.click();
|
|
846
|
+
document.body.removeChild(a);
|
|
847
|
+
URL.revokeObjectURL(url);
|
|
848
|
+
}
|
|
849
|
+
|
|
545
850
|
/* ── State ── */
|
|
546
851
|
var S={
|
|
547
852
|
ws:null,project:null,view:'suites',selectedRun:null,
|
|
548
|
-
liveRuns:{},
|
|
853
|
+
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
854
|
+
runFilter:{status:'all',search:''},
|
|
855
|
+
lastLearningsData:null,
|
|
856
|
+
highlightedRunIdx:-1
|
|
549
857
|
};
|
|
550
858
|
|
|
551
859
|
/* ── Navigation ── */
|
|
@@ -565,12 +873,20 @@ function showView(v){
|
|
|
565
873
|
$('#view-'+v).classList.add('active');
|
|
566
874
|
}
|
|
567
875
|
|
|
568
|
-
/*
|
|
876
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
877
|
+
WebSocket
|
|
878
|
+
══════════════════════════════════════════════════════════════════ */
|
|
569
879
|
function connectWS(){
|
|
570
880
|
var proto=location.protocol==='https:'?'wss:':'ws:';
|
|
571
881
|
S.ws=new WebSocket(proto+'//'+location.host);
|
|
572
|
-
S.ws.onopen=function(){
|
|
573
|
-
|
|
882
|
+
S.ws.onopen=function(){
|
|
883
|
+
$('#wsDot').style.background='var(--green)';$('#wsLabel').textContent='ws: connected';$('#wsLabel').style.color='var(--green)';
|
|
884
|
+
showToast('WebSocket connected','info');
|
|
885
|
+
};
|
|
886
|
+
S.ws.onclose=function(){
|
|
887
|
+
$('#wsDot').style.background='var(--red)';$('#wsLabel').textContent='ws: disconnected';$('#wsLabel').style.color='var(--text3)';
|
|
888
|
+
setTimeout(connectWS,3000);
|
|
889
|
+
};
|
|
574
890
|
S.ws.onerror=function(){};
|
|
575
891
|
S.ws.onmessage=function(e){try{handleWS(JSON.parse(e.data))}catch(x){}};
|
|
576
892
|
}
|
|
@@ -583,22 +899,16 @@ function getLiveRun(m){
|
|
|
583
899
|
}
|
|
584
900
|
function anyLiveRunning(){for(var k in S.liveRuns)if(S.liveRuns[k].on)return true;return false}
|
|
585
901
|
|
|
586
|
-
/* Staleness guard: auto-finish stuck runs, garbage-collect old finished runs */
|
|
587
902
|
setInterval(function(){
|
|
588
903
|
var changed=false;
|
|
589
904
|
for(var k in S.liveRuns){
|
|
590
905
|
var r=S.liveRuns[k];
|
|
591
906
|
var age=Date.now()-r._lastEvent;
|
|
592
|
-
/* Mark stuck runs as done */
|
|
593
907
|
if(r.on&&!r.done){
|
|
594
|
-
/* 0/0 runs (never received any tests) — mark stale after 10s */
|
|
595
908
|
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
909
|
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
910
|
else if(age>30000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
600
911
|
}
|
|
601
|
-
/* Auto-remove stale 0/0 runs after 15s, finished runs after 120s */
|
|
602
912
|
if(r.done&&r.stale&&r.total===0&&age>15000){delete S.liveRuns[k];changed=true}
|
|
603
913
|
else if(r.done&&age>120000){delete S.liveRuns[k];changed=true}
|
|
604
914
|
}
|
|
@@ -609,53 +919,58 @@ function handleWS(m){
|
|
|
609
919
|
switch(m.event){
|
|
610
920
|
case 'pool:status':renderPool(m.data);break;
|
|
611
921
|
case 'run:start':
|
|
612
|
-
/* Clear all finished/stale runs when a new one starts */
|
|
613
922
|
for(var dk in S.liveRuns){if(S.liveRuns[dk].done)delete S.liveRuns[dk]}
|
|
614
923
|
var r=getLiveRun(m);
|
|
615
924
|
r.total=m.total;r.on=true;r.done=false;
|
|
616
|
-
S.
|
|
925
|
+
S.liveCollapsed=new Set();S.liveSSOpen=new Set();
|
|
617
926
|
showView('live');renderLive();break;
|
|
618
927
|
case 'test:start':
|
|
619
|
-
var
|
|
620
|
-
|
|
621
|
-
|
|
928
|
+
var r2=getLiveRun(m);if(!r2)break;
|
|
929
|
+
r2.active=m.activeCount;
|
|
930
|
+
r2.tests[m.name]={status:'running',actions:0,totalActions:0,error:null,actionLog:[],screenshots:[],serial:m.serial||false};
|
|
622
931
|
renderLive();break;
|
|
623
932
|
case 'test:action':
|
|
624
|
-
var
|
|
625
|
-
var t=
|
|
933
|
+
var r3=getLiveRun(m);if(!r3||!r3.tests[m.name])break;
|
|
934
|
+
var t=r3.tests[m.name];
|
|
626
935
|
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});
|
|
936
|
+
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
937
|
if(m.screenshotPath)t.screenshots.push(m.screenshotPath);
|
|
629
938
|
renderLive();break;
|
|
630
939
|
case 'test:retry':
|
|
631
|
-
var
|
|
632
|
-
|
|
940
|
+
var r4=getLiveRun(m);if(!r4||!r4.tests[m.name])break;
|
|
941
|
+
r4.tests[m.name].retry=m.attempt+'/'+m.maxAttempts;
|
|
633
942
|
renderLive();break;
|
|
634
943
|
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)
|
|
944
|
+
var r5=getLiveRun(m);if(!r5)break;
|
|
945
|
+
r5.completed++;
|
|
946
|
+
if(m.success){r5.passed++;if(r5.tests[m.name])r5.tests[m.name].status='passed'}
|
|
947
|
+
else{r5.failed++;if(r5.tests[m.name]){r5.tests[m.name].status='failed';r5.tests[m.name].error=m.error}}
|
|
948
|
+
if(r5.tests[m.name]){
|
|
949
|
+
r5.tests[m.name].duration=m.duration;
|
|
950
|
+
if(m.screenshots&&m.screenshots.length)r5.tests[m.name].screenshots=m.screenshots;
|
|
951
|
+
if(m.errorScreenshot)r5.tests[m.name].errorScreenshot=m.errorScreenshot;
|
|
952
|
+
if(m.networkLogs&&m.networkLogs.length)r5.tests[m.name].networkLogs=m.networkLogs;
|
|
644
953
|
}
|
|
645
|
-
|
|
954
|
+
r5.active=Math.max(0,r5.active-1);
|
|
646
955
|
renderLive();break;
|
|
647
956
|
case 'run:complete':
|
|
648
|
-
var
|
|
957
|
+
var r6=getLiveRun(m);if(r6){r6.on=false;r6.done=true;r6.active=0}
|
|
958
|
+
var summary=m.summary||{};
|
|
959
|
+
if(summary.failed>0){showToast('Run complete: '+summary.failed+' failed','error')}
|
|
960
|
+
else{showToast('Run complete: all '+(summary.total||0)+' passed','success')}
|
|
649
961
|
renderLive();refreshRuns();refreshProjects();break;
|
|
650
962
|
case 'run:error':
|
|
651
|
-
var
|
|
963
|
+
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
964
|
+
showToast('Run error: '+m.error,'error');
|
|
652
965
|
renderLive();break;
|
|
653
966
|
case 'db:updated':
|
|
654
|
-
refreshRuns();refreshProjects();refreshScreenshots();break;
|
|
967
|
+
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();break;
|
|
655
968
|
}
|
|
656
969
|
}
|
|
657
970
|
|
|
658
|
-
/*
|
|
971
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
972
|
+
API & Pool
|
|
973
|
+
══════════════════════════════════════════════════════════════════ */
|
|
659
974
|
function api(p){return fetch(p).then(function(r){return r.json()})}
|
|
660
975
|
function triggerRun(suite,projectId){
|
|
661
976
|
if(anyLiveRunning())return;
|
|
@@ -666,7 +981,6 @@ function triggerRun(suite,projectId){
|
|
|
666
981
|
fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
667
982
|
}
|
|
668
983
|
|
|
669
|
-
/* ── Pool ── */
|
|
670
984
|
function renderPool(d){
|
|
671
985
|
if(!d)return;
|
|
672
986
|
$('#poolDot').className='pool-dot '+(d.error||!d.available?'off':'on');
|
|
@@ -675,7 +989,9 @@ function renderPool(d){
|
|
|
675
989
|
}
|
|
676
990
|
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
677
991
|
|
|
678
|
-
/*
|
|
992
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
993
|
+
Projects
|
|
994
|
+
══════════════════════════════════════════════════════════════════ */
|
|
679
995
|
function refreshProjects(){
|
|
680
996
|
api('/api/db/projects').then(function(projects){
|
|
681
997
|
var sel=$('#projectSelect'),prev=sel.value;
|
|
@@ -689,68 +1005,226 @@ function refreshProjects(){
|
|
|
689
1005
|
$('#projectSelect').addEventListener('change',function(){
|
|
690
1006
|
S.project=this.value?parseInt(this.value,10):null;
|
|
691
1007
|
S.selectedRun=null;
|
|
692
|
-
refreshRuns();refreshSuites();refreshScreenshots();
|
|
1008
|
+
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();
|
|
693
1009
|
});
|
|
694
1010
|
|
|
695
|
-
/*
|
|
1011
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1012
|
+
Suites (+ Serial badges, Modules)
|
|
1013
|
+
══════════════════════════════════════════════════════════════════ */
|
|
696
1014
|
function refreshSuites(){
|
|
697
|
-
var grid=$('#suiteGrid'),empty=$('#suitesEmpty');
|
|
1015
|
+
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
698
1016
|
grid.textContent='';
|
|
1017
|
+
var moduleSection=$('#moduleSection');
|
|
1018
|
+
moduleSection.textContent='';
|
|
699
1019
|
|
|
700
1020
|
if(S.project){
|
|
701
|
-
// Single project — fetch its suites
|
|
702
1021
|
api('/api/db/projects/'+S.project+'/suites').then(function(suites){
|
|
703
1022
|
if(!Array.isArray(suites)||suites.length===0){empty.style.display='block';empty.querySelector('p').textContent='No test suites found for this project.';return}
|
|
704
1023
|
empty.style.display='none';
|
|
1024
|
+
$('#badgeSuites').textContent=suites.length;
|
|
705
1025
|
renderSuiteCards(grid,suites,S.project);
|
|
706
1026
|
}).catch(function(){});
|
|
1027
|
+
api('/api/db/projects/'+S.project+'/modules').then(function(modules){
|
|
1028
|
+
renderModules(moduleSection,modules);
|
|
1029
|
+
}).catch(function(){});
|
|
707
1030
|
} else {
|
|
708
|
-
// All projects — fetch each project's suites
|
|
709
1031
|
api('/api/db/projects').then(function(projects){
|
|
710
1032
|
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;
|
|
1033
|
+
var loaded=0,hasAny=false,totalSuites=0;
|
|
712
1034
|
projects.forEach(function(p){
|
|
713
1035
|
api('/api/db/projects/'+p.id+'/suites').then(function(suites){
|
|
714
1036
|
loaded++;
|
|
715
1037
|
if(Array.isArray(suites)&&suites.length>0){
|
|
716
|
-
hasAny=true;
|
|
1038
|
+
hasAny=true;totalSuites+=suites.length;
|
|
717
1039
|
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
1040
|
grid.appendChild(label);
|
|
719
1041
|
renderSuiteCards(grid,suites,p.id);
|
|
720
1042
|
}
|
|
721
|
-
if(loaded===projects.length
|
|
1043
|
+
if(loaded===projects.length){
|
|
1044
|
+
$('#badgeSuites').textContent=totalSuites;
|
|
1045
|
+
if(!hasAny){empty.style.display='block';empty.querySelector('p').textContent='No test suites found.'}
|
|
1046
|
+
}
|
|
722
1047
|
}).catch(function(){loaded++;});
|
|
723
1048
|
});
|
|
724
1049
|
}).catch(function(){});
|
|
725
1050
|
}
|
|
726
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
function renderProjectAccordion(container,project,suites){
|
|
1054
|
+
var totalTests=suites.reduce(function(sum,s){return sum+(s.testCount||0)},0);
|
|
1055
|
+
var body=el('div',{className:'project-accordion-body'});
|
|
1056
|
+
var innerGrid=el('div',{className:'suite-grid'});
|
|
1057
|
+
renderSuiteCards(innerGrid,suites,project.id);
|
|
1058
|
+
body.appendChild(innerGrid);
|
|
1059
|
+
|
|
1060
|
+
var header=el('div',{className:'project-accordion-header'},[
|
|
1061
|
+
el('span',{className:'project-accordion-chevron'},'\u25B6'),
|
|
1062
|
+
el('span',{className:'project-accordion-name'},project.name),
|
|
1063
|
+
el('div',{className:'project-accordion-meta'},[
|
|
1064
|
+
el('span',{className:'project-accordion-badge'},suites.length+(suites.length===1?' suite':' suites')),
|
|
1065
|
+
el('span',{className:'project-accordion-badge'},totalTests+(totalTests===1?' test':' tests'))
|
|
1066
|
+
])
|
|
1067
|
+
]);
|
|
1068
|
+
|
|
1069
|
+
var wrapper=el('div',{className:'project-accordion'},[header,body]);
|
|
1070
|
+
|
|
1071
|
+
header.addEventListener('click',function(){
|
|
1072
|
+
wrapper.classList.toggle('open');
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
container.appendChild(wrapper);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
var _suiteCache={};
|
|
727
1079
|
function renderSuiteCards(container,suites,projectId){
|
|
728
1080
|
suites.forEach(function(s){
|
|
729
1081
|
var tests=el('ul',{className:'suite-card-tests'});
|
|
730
|
-
(s.tests||[]).forEach(function(t){tests.appendChild(el('li',null,t))});
|
|
731
1082
|
var pid=projectId;
|
|
732
|
-
|
|
733
|
-
el('
|
|
1083
|
+
(s.tests||[]).forEach(function(t){
|
|
1084
|
+
var li=el('li',null,t);
|
|
1085
|
+
li.addEventListener('click',function(e){
|
|
1086
|
+
e.stopPropagation();
|
|
1087
|
+
var existing=li.querySelector('.suite-test-steps');
|
|
1088
|
+
if(existing){existing.remove();li.classList.remove('expanded');return}
|
|
1089
|
+
tests.querySelectorAll('.suite-test-steps').forEach(function(d){d.remove()});
|
|
1090
|
+
tests.querySelectorAll('li.expanded').forEach(function(l){l.classList.remove('expanded')});
|
|
1091
|
+
var stepsDiv=el('div',{className:'suite-test-steps'});
|
|
1092
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--text3);font-size:10px'},'Loading...'));
|
|
1093
|
+
li.appendChild(stepsDiv);
|
|
1094
|
+
li.classList.add('expanded');
|
|
1095
|
+
var cacheKey=pid+'::'+s.name;
|
|
1096
|
+
var p=_suiteCache[cacheKey]||api('/api/db/projects/'+pid+'/suites/'+encodeURIComponent(s.name));
|
|
1097
|
+
_suiteCache[cacheKey]=p;
|
|
1098
|
+
p.then(function(data){
|
|
1099
|
+
stepsDiv.textContent='';
|
|
1100
|
+
var test=(data.tests||[]).find(function(x){return x.name===t});
|
|
1101
|
+
if(!test||!test.actions||!test.actions.length){
|
|
1102
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--text3);font-size:10px'},'No actions'));
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if(test.serial){
|
|
1106
|
+
var sb=el('span',{className:'serial-badge'},'Serial');
|
|
1107
|
+
li.insertBefore(sb,li.querySelector('.suite-test-steps'));
|
|
1108
|
+
}
|
|
1109
|
+
test.actions.forEach(function(a,i){
|
|
1110
|
+
var detail=a.selector||a.value||a.text||'';
|
|
1111
|
+
if(a.selector&&(a.value||a.text))detail=a.selector+' \u2192 '+(a.text||a.value);
|
|
1112
|
+
stepsDiv.appendChild(el('div',{className:'lt-step'},[
|
|
1113
|
+
el('span',{className:'step-icon',style:'color:var(--text3)'},String(i+1)),
|
|
1114
|
+
el('span',{className:'step-type'},a.type),
|
|
1115
|
+
el('span',{className:'step-detail'},detail)
|
|
1116
|
+
]));
|
|
1117
|
+
});
|
|
1118
|
+
}).catch(function(){
|
|
1119
|
+
stepsDiv.textContent='';
|
|
1120
|
+
stepsDiv.appendChild(el('div',{style:'color:var(--red);font-size:10px'},'Failed to load'));
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
tests.appendChild(li);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
var cardHead=el('div',{className:'suite-card-head',style:'cursor:pointer'},[
|
|
1127
|
+
el('div',{className:'suite-card-icon'},'\u25B6'),
|
|
1128
|
+
el('div',{className:'suite-card-info'},[
|
|
734
1129
|
el('div',{className:'suite-card-name'},s.name),
|
|
735
|
-
el('
|
|
1130
|
+
el('div',{className:'suite-card-file'},s.file||s.name+'.json')
|
|
736
1131
|
]),
|
|
737
|
-
|
|
738
|
-
|
|
1132
|
+
el('div',{className:'suite-card-count'},[
|
|
1133
|
+
el('div',{className:'suite-card-count-num'},String(s.testCount||0)),
|
|
1134
|
+
el('div',{className:'suite-card-count-lbl'},'tests')
|
|
1135
|
+
])
|
|
1136
|
+
]);
|
|
1137
|
+
(function(name,projId){
|
|
1138
|
+
cardHead.addEventListener('click',function(){openSuiteModal(name,projId)});
|
|
1139
|
+
})(s.name,pid);
|
|
1140
|
+
|
|
1141
|
+
var card=el('div',{className:'suite-card'},[
|
|
1142
|
+
cardHead,
|
|
1143
|
+
el('div',{className:'suite-card-body'},[tests]),
|
|
1144
|
+
el('div',{className:'suite-card-footer'},[
|
|
1145
|
+
el('button',{className:'btn sm primary',onclick:function(){triggerRun(s.name,pid)}},'Run Suite')
|
|
1146
|
+
])
|
|
739
1147
|
]);
|
|
740
1148
|
container.appendChild(card);
|
|
741
1149
|
});
|
|
742
1150
|
}
|
|
743
1151
|
|
|
744
|
-
|
|
1152
|
+
function renderModules(container,modules){
|
|
1153
|
+
if(!Array.isArray(modules)||modules.length===0)return;
|
|
1154
|
+
var title=el('div',{className:'module-section-title'},[
|
|
1155
|
+
el('span',{className:'mod-icon'},'\u{1F9E9}'),
|
|
1156
|
+
document.createTextNode(' Reusable Modules ('+modules.length+')')
|
|
1157
|
+
]);
|
|
1158
|
+
container.appendChild(title);
|
|
1159
|
+
var grid=el('div',{className:'module-grid'});
|
|
1160
|
+
modules.forEach(function(m){
|
|
1161
|
+
var paramsEl=null;
|
|
1162
|
+
if(m.params&&m.params.length){
|
|
1163
|
+
var items=m.params.map(function(p){return el('li',null,typeof p==='string'?p:(p.name||String(p)))});
|
|
1164
|
+
paramsEl=el('ul',{className:'module-card-params'},items);
|
|
1165
|
+
}
|
|
1166
|
+
var card=el('div',{className:'module-card'},[
|
|
1167
|
+
el('div',{className:'module-card-name'},m.name),
|
|
1168
|
+
m.description?el('div',{className:'module-card-desc'},m.description):null,
|
|
1169
|
+
el('div',{className:'module-card-meta'},[
|
|
1170
|
+
el('span',null,m.actionCount+' actions'),
|
|
1171
|
+
m.params&&m.params.length?el('span',null,m.params.length+' params'):null
|
|
1172
|
+
]),
|
|
1173
|
+
paramsEl
|
|
1174
|
+
]);
|
|
1175
|
+
grid.appendChild(card);
|
|
1176
|
+
});
|
|
1177
|
+
container.appendChild(grid);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1181
|
+
Runs (+ Filters)
|
|
1182
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1183
|
+
$$('.filter-btn').forEach(function(btn){
|
|
1184
|
+
btn.addEventListener('click',function(){
|
|
1185
|
+
$$('.filter-btn').forEach(function(b){b.classList.remove('active')});
|
|
1186
|
+
btn.classList.add('active');
|
|
1187
|
+
S.runFilter.status=btn.dataset.filter;
|
|
1188
|
+
applyRunFilters();
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
$('#runSearchInput').addEventListener('input',function(){
|
|
1192
|
+
S.runFilter.search=this.value.trim().toLowerCase();
|
|
1193
|
+
applyRunFilters();
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
var _allRunRows=[];
|
|
1197
|
+
function applyRunFilters(){
|
|
1198
|
+
_allRunRows.forEach(function(item){
|
|
1199
|
+
var show=true;
|
|
1200
|
+
var r=item.data;
|
|
1201
|
+
if(S.runFilter.status!=='all'){
|
|
1202
|
+
var total=r.total||0;var passed=r.passed||0;var failed=r.failed||0;
|
|
1203
|
+
if(S.runFilter.status==='pass'&&(failed>0||total===0))show=false;
|
|
1204
|
+
if(S.runFilter.status==='fail'&&failed===0)show=false;
|
|
1205
|
+
if(S.runFilter.status==='mixed'&&(failed===0||passed===0))show=false;
|
|
1206
|
+
}
|
|
1207
|
+
if(show&&S.runFilter.search){
|
|
1208
|
+
var suite=(r.suite_name||'all').toLowerCase();
|
|
1209
|
+
var proj=(r.project_name||'').toLowerCase();
|
|
1210
|
+
if(suite.indexOf(S.runFilter.search)===-1&&proj.indexOf(S.runFilter.search)===-1)show=false;
|
|
1211
|
+
}
|
|
1212
|
+
item.tr.style.display=show?'':'none';
|
|
1213
|
+
if(item.detailTr)item.detailTr.style.display=show?'':'none';
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
745
1217
|
function refreshRuns(){
|
|
746
1218
|
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
747
1219
|
api(url).then(function(rows){
|
|
748
1220
|
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
749
1221
|
chart.textContent='';body.textContent='';
|
|
750
|
-
|
|
1222
|
+
_allRunRows=[];
|
|
1223
|
+
S.highlightedRunIdx=-1;
|
|
1224
|
+
if(!Array.isArray(rows)||rows.length===0){empty.style.display='block';head.parentNode.parentNode.style.display='none';$('#badgeRuns').textContent='0';return}
|
|
751
1225
|
empty.style.display='none';head.parentNode.parentNode.style.display='';
|
|
1226
|
+
$('#badgeRuns').textContent=rows.length;
|
|
752
1227
|
|
|
753
|
-
// Thead
|
|
754
1228
|
var htr=document.createElement('tr');
|
|
755
1229
|
var cols=[];
|
|
756
1230
|
if(!S.project)cols.push('Project');
|
|
@@ -759,7 +1233,6 @@ function refreshRuns(){
|
|
|
759
1233
|
head.textContent='';head.appendChild(htr);
|
|
760
1234
|
var colSpan=cols.length;
|
|
761
1235
|
|
|
762
|
-
// Chart
|
|
763
1236
|
rows.slice(0,40).slice().reverse().forEach(function(r){
|
|
764
1237
|
var rate=parseFloat(r.pass_rate)||0;
|
|
765
1238
|
var color=rate>=90?'var(--green)':rate>=70?'var(--amber)':'var(--red)';
|
|
@@ -768,7 +1241,6 @@ function refreshRuns(){
|
|
|
768
1241
|
chart.appendChild(bar);
|
|
769
1242
|
});
|
|
770
1243
|
|
|
771
|
-
// Rows
|
|
772
1244
|
rows.forEach(function(r){
|
|
773
1245
|
var tr=document.createElement('tr');
|
|
774
1246
|
tr.dataset.runId=r.id;
|
|
@@ -786,12 +1258,14 @@ function refreshRuns(){
|
|
|
786
1258
|
tr.addEventListener('click',function(){toggleDetail(r.id,tr,colSpan)});
|
|
787
1259
|
body.appendChild(tr);
|
|
788
1260
|
|
|
789
|
-
|
|
1261
|
+
var item={tr:tr,data:r,detailTr:null};
|
|
790
1262
|
if(r.id===S.selectedRun){
|
|
791
1263
|
var detailTr=createDetailRow(colSpan);
|
|
792
1264
|
body.appendChild(detailTr);
|
|
793
1265
|
loadDetailInline(r.id,detailTr);
|
|
1266
|
+
item.detailTr=detailTr;
|
|
794
1267
|
}
|
|
1268
|
+
_allRunRows.push(item);
|
|
795
1269
|
});
|
|
796
1270
|
}).catch(function(){});
|
|
797
1271
|
}
|
|
@@ -802,8 +1276,12 @@ function createDetailRow(colSpan){
|
|
|
802
1276
|
var td=document.createElement('td');
|
|
803
1277
|
td.setAttribute('colspan',colSpan);
|
|
804
1278
|
var wrap=el('div',{className:'rd-wrap'});
|
|
805
|
-
var inner=el('div',{className:'rd-inner'}
|
|
806
|
-
|
|
1279
|
+
var inner=el('div',{className:'rd-inner'},[
|
|
1280
|
+
el('div',{style:'color:var(--text3);font-size:11px'},[
|
|
1281
|
+
el('span',{className:'spinner-small'}),
|
|
1282
|
+
document.createTextNode(' Loading...')
|
|
1283
|
+
])
|
|
1284
|
+
]);
|
|
807
1285
|
wrap.appendChild(inner);
|
|
808
1286
|
td.appendChild(wrap);
|
|
809
1287
|
detailTr.appendChild(td);
|
|
@@ -811,7 +1289,6 @@ function createDetailRow(colSpan){
|
|
|
811
1289
|
}
|
|
812
1290
|
|
|
813
1291
|
function toggleDetail(id,clickedTr,colSpan){
|
|
814
|
-
// If already expanded, collapse
|
|
815
1292
|
if(S.selectedRun===id){
|
|
816
1293
|
var existing=clickedTr.nextElementSibling;
|
|
817
1294
|
if(existing&&existing.classList.contains('run-detail-row')){
|
|
@@ -824,7 +1301,6 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
824
1301
|
return;
|
|
825
1302
|
}
|
|
826
1303
|
|
|
827
|
-
// Collapse any other open detail
|
|
828
1304
|
var prevTr=document.querySelector('#runsBody tr.expanded');
|
|
829
1305
|
if(prevTr){
|
|
830
1306
|
prevTr.classList.remove('expanded');
|
|
@@ -836,83 +1312,106 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
836
1312
|
}
|
|
837
1313
|
}
|
|
838
1314
|
|
|
839
|
-
// Expand new
|
|
840
1315
|
S.selectedRun=id;
|
|
841
1316
|
clickedTr.classList.add('expanded');
|
|
842
1317
|
var detailTr=createDetailRow(colSpan);
|
|
843
1318
|
clickedTr.parentNode.insertBefore(detailTr,clickedTr.nextSibling);
|
|
844
|
-
|
|
845
|
-
// Animate open
|
|
846
1319
|
requestAnimationFrame(function(){
|
|
847
1320
|
requestAnimationFrame(function(){
|
|
848
1321
|
var w2=detailTr.querySelector('.rd-wrap');
|
|
849
1322
|
if(w2)w2.classList.add('open');
|
|
850
1323
|
});
|
|
851
1324
|
});
|
|
852
|
-
|
|
853
1325
|
loadDetailInline(id,detailTr);
|
|
854
1326
|
}
|
|
855
1327
|
|
|
1328
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1329
|
+
Run Detail (+ Action Narratives, Retry badges, Export)
|
|
1330
|
+
══════════════════════════════════════════════════════════════════ */
|
|
856
1331
|
function loadDetailInline(id,detailTr){
|
|
857
1332
|
api('/api/db/runs/'+id).then(function(d){
|
|
858
1333
|
if(d.error)return;
|
|
859
1334
|
var inner=detailTr.querySelector('.rd-inner');
|
|
860
1335
|
inner.textContent='';
|
|
861
|
-
|
|
862
1336
|
var results=d.results||[];
|
|
863
1337
|
|
|
864
|
-
|
|
1338
|
+
var exportBtn=el('div',null,[
|
|
1339
|
+
el('div',{className:'rd-s-label'},'Export'),
|
|
1340
|
+
el('div',{style:'margin-top:4px'},[
|
|
1341
|
+
el('button',{className:'btn sm',onclick:function(e){
|
|
1342
|
+
e.stopPropagation();
|
|
1343
|
+
downloadFile('run-'+id+'.json',JSON.stringify(d,null,2),'application/json');
|
|
1344
|
+
}},'JSON')
|
|
1345
|
+
])
|
|
1346
|
+
]);
|
|
865
1347
|
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
866
1348
|
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:
|
|
1349
|
+
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
1350
|
srcBlock,
|
|
869
1351
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
870
1352
|
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:
|
|
1353
|
+
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))]),
|
|
1354
|
+
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||'-')]),
|
|
1355
|
+
exportBtn
|
|
873
1356
|
]);
|
|
874
1357
|
inner.appendChild(summ);
|
|
875
1358
|
|
|
876
|
-
// Test result cards
|
|
877
1359
|
results.forEach(function(r){
|
|
878
1360
|
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
879
1361
|
var flaky=r.success&&r.attempt>1;
|
|
1362
|
+
var state=flaky?'flaky':(r.success?'pass':'fail');
|
|
880
1363
|
|
|
881
|
-
// Header: badge + name + duration
|
|
882
1364
|
var badges=el('div',{style:'display:flex;gap:6px;align-items:center;flex-shrink:0'});
|
|
883
1365
|
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
884
1366
|
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
885
1367
|
|
|
886
|
-
var head=el('div',{className:'rd-test-head'},[
|
|
887
|
-
badges,
|
|
888
|
-
el('div',{className:'rd-test-name'},r.name),
|
|
889
|
-
el('div',{className:'rd-test-dur'},d2)
|
|
890
|
-
]);
|
|
891
|
-
|
|
892
|
-
// Body content
|
|
1368
|
+
var head=el('div',{className:'rd-test-head'},[badges,el('div',{className:'rd-test-name'},r.name),el('div',{className:'rd-test-dur'},d2)]);
|
|
893
1369
|
var body=el('div',{className:'rd-test-body'});
|
|
894
1370
|
|
|
895
|
-
|
|
896
|
-
if(r.
|
|
897
|
-
|
|
1371
|
+
if(r.maxAttempts>1){body.appendChild(el('div',{className:'rd-retries'},'Attempt '+r.attempt+' of '+r.maxAttempts))}
|
|
1372
|
+
if(r.error){
|
|
1373
|
+
var errDiv=el('div',{className:'rd-error-msg'});
|
|
1374
|
+
errDiv.appendChild(document.createTextNode(r.error));
|
|
1375
|
+
errDiv.appendChild(makeCopyBtn(r.error));
|
|
1376
|
+
body.appendChild(errDiv);
|
|
898
1377
|
}
|
|
899
1378
|
|
|
900
|
-
//
|
|
901
|
-
if(r.
|
|
902
|
-
|
|
1379
|
+
// Actions panel
|
|
1380
|
+
if(r.actions&&r.actions.length){
|
|
1381
|
+
var passCount=r.actions.filter(function(a){return a.success}).length;
|
|
1382
|
+
var failCount=r.actions.length-passCount;
|
|
1383
|
+
var actHead=el('div',{className:'rd-net-head'},[
|
|
1384
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
1385
|
+
el('span',{className:'net-title'},'Actions'),
|
|
1386
|
+
el('div',{className:'net-stats'},[
|
|
1387
|
+
el('span',{className:'net-stat'},[document.createTextNode('Steps: '),el('strong',null,String(r.actions.length))]),
|
|
1388
|
+
failCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Failed: '),el('strong',null,String(failCount))]):null
|
|
1389
|
+
])
|
|
1390
|
+
]);
|
|
1391
|
+
var actBody=el('div',{className:'rd-net-body',style:'padding:8px 14px'});
|
|
1392
|
+
r.actions.forEach(function(a){
|
|
1393
|
+
var label=a.narrative||a.type;
|
|
1394
|
+
var durText=a.duration!=null?dur(a.duration):'';
|
|
1395
|
+
var retryBadge=null;
|
|
1396
|
+
if(a.actionRetries&&a.actionRetries>0){
|
|
1397
|
+
retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px'},'\u21BB x'+a.actionRetries);
|
|
1398
|
+
}
|
|
1399
|
+
actBody.appendChild(el('div',{className:'lt-step'},[
|
|
1400
|
+
el('span',{className:'step-icon '+(a.success?'ok':'fail')},a.success?'\u2714':'\u2718'),
|
|
1401
|
+
el('span',{className:'step-detail',style:'flex:1'},label),
|
|
1402
|
+
retryBadge,
|
|
1403
|
+
el('span',{className:'step-dur'},durText)
|
|
1404
|
+
]));
|
|
1405
|
+
});
|
|
1406
|
+
actHead.addEventListener('click',function(){actHead.classList.toggle('open')});
|
|
1407
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[actHead,actBody]));
|
|
903
1408
|
}
|
|
904
1409
|
|
|
905
|
-
// Screenshots
|
|
1410
|
+
// Screenshots
|
|
906
1411
|
var shots=[];
|
|
907
1412
|
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
|
-
}
|
|
1413
|
+
(r.screenshots||[]).forEach(function(p){shots.push({path:p,label:p.split('/').pop(),type:'screenshot',hash:hashes[p]||null})});
|
|
1414
|
+
if(r.errorScreenshot){shots.push({path:r.errorScreenshot,label:r.errorScreenshot.split('/').pop(),type:'error',hash:hashes[r.errorScreenshot]||null})}
|
|
916
1415
|
if(shots.length){
|
|
917
1416
|
var shotsWrap=el('div',{className:'rd-shots'});
|
|
918
1417
|
shots.forEach(function(s){
|
|
@@ -921,94 +1420,95 @@ function loadDetailInline(id,detailTr){
|
|
|
921
1420
|
var capEl=el('div',{className:'rd-shot-cap'},[el('span',{className:'cap-name'},s.label)]);
|
|
922
1421
|
if(s.hash){capEl.appendChild(createHashBadge(s.hash))}
|
|
923
1422
|
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);
|
|
1423
|
+
shotsWrap.appendChild(el('div',{className:'rd-shot'+(s.type==='error'?' err-shot':''),onclick:function(e){e.stopPropagation();openModal(src)}},[img,capEl]));
|
|
928
1424
|
});
|
|
929
1425
|
body.appendChild(shotsWrap);
|
|
930
1426
|
}
|
|
931
1427
|
|
|
932
|
-
// Console logs
|
|
1428
|
+
// Console logs
|
|
933
1429
|
var cIssues=(r.consoleLogs||[]).filter(function(l){return l.type==='error'||l.type==='warn'||l.type==='warning'});
|
|
934
1430
|
if(cIssues.length){
|
|
935
|
-
var
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1431
|
+
var cErrors=cIssues.filter(function(l){return l.type==='error'}).length;
|
|
1432
|
+
var cWarns=cIssues.length-cErrors;
|
|
1433
|
+
var conHead=el('div',{className:'rd-net-head'},[
|
|
1434
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
1435
|
+
el('span',{className:'net-title'},'Console'),
|
|
1436
|
+
el('div',{className:'net-stats'},[
|
|
1437
|
+
cErrors?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(cErrors))]):null,
|
|
1438
|
+
cWarns?el('span',{className:'net-stat'},[document.createTextNode('Warnings: '),el('strong',null,String(cWarns))]):null
|
|
1439
|
+
]),
|
|
1440
|
+
makeCopyBtn(function(){return cIssues.map(function(l){return '['+l.type+'] '+l.text}).join('\n')})
|
|
1441
|
+
]);
|
|
1442
|
+
var conBody=el('div',{className:'rd-net-body'});
|
|
1443
|
+
cIssues.forEach(function(l){conBody.appendChild(el('div',{className:'rd-log-item '+l.type},'['+l.type+'] '+l.text))});
|
|
1444
|
+
conHead.addEventListener('click',function(){conHead.classList.toggle('open')});
|
|
1445
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[conHead,conBody]));
|
|
941
1446
|
}
|
|
942
1447
|
|
|
943
1448
|
// Network errors
|
|
944
1449
|
if(r.networkErrors&&r.networkErrors.length){
|
|
945
|
-
var
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1450
|
+
var neHead=el('div',{className:'rd-net-head'},[
|
|
1451
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
1452
|
+
el('span',{className:'net-title'},'Network Errors'),
|
|
1453
|
+
el('div',{className:'net-stats'},[el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(r.networkErrors.length))])]),
|
|
1454
|
+
makeCopyBtn(function(){return r.networkErrors.map(function(ne){return '['+ne.error+'] '+ne.url}).join('\n')})
|
|
1455
|
+
]);
|
|
1456
|
+
var neBody=el('div',{className:'rd-net-body'});
|
|
1457
|
+
r.networkErrors.forEach(function(ne){neBody.appendChild(el('div',{className:'rd-log-item error'},'['+ne.error+'] '+ne.url))});
|
|
1458
|
+
neHead.addEventListener('click',function(){neHead.classList.toggle('open')});
|
|
1459
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[neHead,neBody]));
|
|
951
1460
|
}
|
|
952
1461
|
|
|
953
|
-
// Network
|
|
1462
|
+
// Network panel
|
|
954
1463
|
if(r.networkLogs&&r.networkLogs.length){
|
|
955
1464
|
var errCount=r.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
956
|
-
var
|
|
957
|
-
var netToggle=el('div',{className:'rd-net-toggle'},[
|
|
1465
|
+
var netHead=el('div',{className:'rd-net-head'},[
|
|
958
1466
|
el('span',{className:'net-arrow'},'\u25B6'),
|
|
959
|
-
el('span',{},
|
|
1467
|
+
el('span',{className:'net-title'},'Network Requests'),
|
|
1468
|
+
el('div',{className:'net-stats'},[
|
|
1469
|
+
el('span',{className:'net-stat'},[document.createTextNode('Total: '),el('strong',null,String(r.networkLogs.length))]),
|
|
1470
|
+
errCount?el('span',{className:'net-stat has-err'},[document.createTextNode('Errors: '),el('strong',null,String(errCount))]):null
|
|
1471
|
+
])
|
|
960
1472
|
]);
|
|
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);
|
|
1473
|
+
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')]);
|
|
1474
|
+
var netBody=el('div',{className:'rd-net-body'},[netCols]);
|
|
1475
|
+
r.networkLogs.forEach(function(n){var built=buildNetRow(n);netBody.appendChild(built.row);if(built.detail)netBody.appendChild(built.detail)});
|
|
1476
|
+
netHead.addEventListener('click',function(){netHead.classList.toggle('open')});
|
|
1477
|
+
body.appendChild(el('div',{className:'rd-net-panel'},[netHead,netBody]));
|
|
972
1478
|
}
|
|
973
1479
|
|
|
974
|
-
|
|
975
|
-
inner.appendChild(testCard);
|
|
1480
|
+
inner.appendChild(el('div',{className:'rd-test '+state},[head,body]));
|
|
976
1481
|
});
|
|
977
1482
|
|
|
978
|
-
// Re-trigger open animation if not yet open
|
|
979
1483
|
var w=detailTr.querySelector('.rd-wrap');
|
|
980
|
-
if(w&&!w.classList.contains('open')){
|
|
981
|
-
|
|
982
|
-
}
|
|
983
|
-
}).catch(function(err){
|
|
1484
|
+
if(w&&!w.classList.contains('open')){requestAnimationFrame(function(){w.classList.add('open')})}
|
|
1485
|
+
}).catch(function(){
|
|
984
1486
|
var inner=detailTr.querySelector('.rd-inner');
|
|
985
1487
|
if(inner)inner.textContent='Failed to load run detail';
|
|
986
1488
|
});
|
|
987
1489
|
}
|
|
988
1490
|
|
|
989
|
-
/*
|
|
1491
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1492
|
+
Screenshots
|
|
1493
|
+
══════════════════════════════════════════════════════════════════ */
|
|
990
1494
|
function refreshScreenshots(){
|
|
991
1495
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
992
1496
|
gal.textContent='';
|
|
993
|
-
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to view screenshots.';return}
|
|
1497
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to view screenshots.';$('#badgeScreenshots').textContent='-';return}
|
|
994
1498
|
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}
|
|
1499
|
+
if(!Array.isArray(files)||!files.length){empty.style.display='block';empty.querySelector('p').textContent='No screenshots for this project.';$('#badgeScreenshots').textContent='0';return}
|
|
996
1500
|
empty.style.display='none';
|
|
1501
|
+
$('#badgeScreenshots').textContent=files.length;
|
|
997
1502
|
files.forEach(function(f){
|
|
998
1503
|
var src='/api/image?path='+encodeURIComponent(f.path);
|
|
999
1504
|
var img=document.createElement('img');img.src=src;img.alt=f.name;img.loading='lazy';
|
|
1000
1505
|
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);
|
|
1506
|
+
(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,f.path);
|
|
1507
|
+
gal.appendChild(el('div',{className:'gallery-item',onclick:function(){openModal(src)}},[img,capEl]));
|
|
1007
1508
|
});
|
|
1008
1509
|
}).catch(function(){});
|
|
1009
1510
|
}
|
|
1010
1511
|
|
|
1011
|
-
/* ── Screenshot Hash Search ── */
|
|
1012
1512
|
function searchByHash(){
|
|
1013
1513
|
var container=$('#ssSearchResult');
|
|
1014
1514
|
container.textContent='';
|
|
@@ -1020,67 +1520,38 @@ function searchByHash(){
|
|
|
1020
1520
|
return;
|
|
1021
1521
|
}
|
|
1022
1522
|
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
|
-
}
|
|
1523
|
+
if(!res.ok){container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));return}
|
|
1027
1524
|
return res.blob();
|
|
1028
1525
|
}).then(function(blob){
|
|
1029
1526
|
if(!blob)return;
|
|
1030
1527
|
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;
|
|
1528
|
+
var wrap=el('div',{className:'ss-search-result'},[el('div',{className:'ss-result-label'},[createHashBadge(hash),el('span',{},'Found')])]);
|
|
1529
|
+
var img=document.createElement('img');img.src=url;img.alt='ss:'+hash;
|
|
1040
1530
|
img.addEventListener('click',function(){openModal(url)});
|
|
1041
1531
|
wrap.appendChild(img);
|
|
1042
1532
|
container.appendChild(wrap);
|
|
1043
|
-
}).catch(function(){
|
|
1044
|
-
container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'));
|
|
1045
|
-
});
|
|
1533
|
+
}).catch(function(){container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'))});
|
|
1046
1534
|
}
|
|
1047
1535
|
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1048
1536
|
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1049
1537
|
|
|
1050
|
-
/*
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
}
|
|
1055
|
-
function dismissLiveRun(rid){
|
|
1056
|
-
delete S.liveRuns[rid];
|
|
1057
|
-
renderLive();
|
|
1058
|
-
}
|
|
1538
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1539
|
+
Live Execution (+ Retry badges, Serial badges)
|
|
1540
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1541
|
+
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}renderLive()}
|
|
1542
|
+
function dismissLiveRun(rid){delete S.liveRuns[rid];renderLive()}
|
|
1059
1543
|
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
1060
1544
|
|
|
1061
1545
|
function renderLive(){
|
|
1062
1546
|
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
1063
|
-
var runs=S.liveRuns;
|
|
1064
|
-
|
|
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
|
-
}
|
|
1547
|
+
var runs=S.liveRuns;var runIds=Object.keys(runs);
|
|
1548
|
+
|
|
1549
|
+
if(runIds.length===0){panel.classList.remove('active');navLive.style.display='none';liveEmpty.style.display='block';$('#liveClearBtn').style.display='none';return}
|
|
1073
1550
|
|
|
1074
|
-
navLive.style.display='';
|
|
1075
|
-
liveEmpty.style.display='none';
|
|
1076
|
-
panel.classList.add('active');
|
|
1551
|
+
navLive.style.display='';liveEmpty.style.display='none';panel.classList.add('active');
|
|
1077
1552
|
|
|
1078
|
-
// Aggregate stats across all runs
|
|
1079
1553
|
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
|
-
});
|
|
1554
|
+
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
1555
|
|
|
1085
1556
|
var badgeActive=0;
|
|
1086
1557
|
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 +1559,13 @@ function renderLive(){
|
|
|
1088
1559
|
$('#liveBadge').style.background=gRunning?'var(--purple-dim)':gFailed>0?'var(--red-dim)':'var(--green-dim)';
|
|
1089
1560
|
$('#liveBadge').style.color=gRunning?'var(--purple)':gFailed>0?'var(--red)':'var(--green)';
|
|
1090
1561
|
|
|
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)
|
|
1562
|
+
$('#liveTotal').textContent=gTotal;$('#livePass').textContent=gPassed;$('#liveFail').textContent=gFailed;$('#liveActive').textContent=gActive;
|
|
1563
|
+
$('#liveProgressFill').style.width=(gTotal>0?gCompleted/gTotal*100:0)+'%';
|
|
1099
1564
|
$('#liveProject').style.display='none';
|
|
1100
1565
|
|
|
1101
|
-
// Show "Clear All" when there are finished/stale runs
|
|
1102
1566
|
var hasFinished=runIds.some(function(rid){return runs[rid].done||!runs[rid].on});
|
|
1103
1567
|
$('#liveClearBtn').style.display=hasFinished?'inline-block':'none';
|
|
1104
1568
|
|
|
1105
|
-
// Header state
|
|
1106
1569
|
var lbl=panel.querySelector('.live-header .label');
|
|
1107
1570
|
var anyStale=runIds.some(function(rid){return runs[rid].stale});
|
|
1108
1571
|
if(!gRunning&&gDone){
|
|
@@ -1111,148 +1574,99 @@ function renderLive(){
|
|
|
1111
1574
|
var dot=lbl.querySelector('.dot');if(dot)dot.remove();
|
|
1112
1575
|
$('#liveProgressFill').style.background=anyStale?'var(--yellow)':gFailed>0?'var(--red)':'var(--green)';
|
|
1113
1576
|
} 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)';
|
|
1577
|
+
if(!lbl.querySelector('.dot')){lbl.textContent='';var d=el('span',{className:'dot'});lbl.appendChild(d);lbl.appendChild(document.createTextNode(' RUNNING'))}
|
|
1578
|
+
lbl.style.color='var(--purple)';$('#liveProgressFill').style.background='var(--purple)';
|
|
1120
1579
|
}
|
|
1121
1580
|
|
|
1122
|
-
// Render per-run sections
|
|
1123
1581
|
grid.textContent='';
|
|
1124
1582
|
runIds.forEach(function(rid){
|
|
1125
1583
|
var L=runs[rid];
|
|
1126
|
-
// Project section header
|
|
1127
1584
|
var projLabel=L.project||(L.cwd?L.cwd.split('/').pop():'Run');
|
|
1128
1585
|
var runStatus=L.done?(L.failed>0?'fail':'pass'):'running';
|
|
1129
1586
|
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
|
-
]),
|
|
1587
|
+
if(L.done||!L.on){dismissBtn=el('button',{className:'lr-dismiss',onclick:function(e){e.stopPropagation();dismissLiveRun(rid)}},'\u2715')}
|
|
1588
|
+
grid.appendChild(el('div',{className:'lr-section-header '+runStatus},[
|
|
1589
|
+
el('span',{className:'lr-project-name'},projLabel),createTriggerBadge(L.triggeredBy),
|
|
1590
|
+
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
1591
|
dismissBtn
|
|
1142
|
-
]);
|
|
1143
|
-
grid.appendChild(sectionHeader);
|
|
1592
|
+
]));
|
|
1144
1593
|
|
|
1145
1594
|
var testGrid=el('div',{className:'lr-test-grid'});
|
|
1146
|
-
|
|
1147
|
-
names.forEach(function(name){
|
|
1595
|
+
Object.keys(L.tests).forEach(function(name){
|
|
1148
1596
|
if(name==='__error')return;
|
|
1149
|
-
var t=L.tests[name];
|
|
1150
|
-
var testKey=rid+'::'+name;
|
|
1597
|
+
var t=L.tests[name];var testKey=rid+'::'+name;
|
|
1151
1598
|
var iconText=t.status==='passed'?'\u2714':t.status==='failed'?'\u2718':'\u25CF';
|
|
1152
1599
|
var iconColor=t.status==='passed'?'color:var(--green)':t.status==='failed'?'color:var(--red)':'color:var(--purple)';
|
|
1153
1600
|
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'}
|
|
1601
|
+
if(t.status==='running'){meta=t.actionType?('Step '+(t.actions||0)+'/'+(t.totalActions||'?')):'starting...';if(t.retry)meta='Retry '+t.retry}
|
|
1602
|
+
else if(t.status==='passed'){meta=t.duration||'done'}
|
|
1158
1603
|
else if(t.status==='failed'){meta=t.error||'failed'}
|
|
1159
|
-
|
|
1604
|
+
|
|
1160
1605
|
var stepsEl=el('div',{className:'lt-actions'});
|
|
1161
1606
|
if(t.actionLog&&t.actionLog.length>0){
|
|
1162
1607
|
t.actionLog.forEach(function(a){
|
|
1163
|
-
var detail=a.selector||a.value||a.text||'';
|
|
1608
|
+
var detail=a.narrative||a.selector||a.value||a.text||'';
|
|
1164
1609
|
var durText=a.duration!=null?(a.duration<1000?a.duration+'ms':(a.duration/1000).toFixed(1)+'s'):'';
|
|
1610
|
+
var retryBadge=null;
|
|
1611
|
+
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)}
|
|
1165
1612
|
stepsEl.appendChild(el('div',{className:'lt-step'},[
|
|
1166
1613
|
el('span',{className:'step-icon '+(a.success?'ok':'fail')},a.success?'\u2714':'\u2718'),
|
|
1167
1614
|
el('span',{className:'step-type'},a.type),
|
|
1168
1615
|
el('span',{className:'step-detail'},detail),
|
|
1616
|
+
retryBadge,
|
|
1169
1617
|
el('span',{className:'step-dur'},durText)
|
|
1170
1618
|
]));
|
|
1171
1619
|
});
|
|
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
|
-
}
|
|
1620
|
+
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
1621
|
} else if(t.status==='running'){
|
|
1176
1622
|
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
1623
|
}
|
|
1178
1624
|
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
|
|
1625
|
+
var isCollapsed=isFinished&&S.liveCollapsed.has(testKey);
|
|
1626
|
+
var summaryEl=el('div',{className:'lt-summary'},[el('span',{className:'lt-dur'},t.duration||''),el('span',{className:'lt-expand'},isCollapsed?'\u25BC':'\u25B2')]);
|
|
1627
|
+
|
|
1185
1628
|
var ssEl=null;
|
|
1186
1629
|
var allSS=(t.screenshots||[]).slice();
|
|
1187
1630
|
if(t.errorScreenshot)allSS.push(t.errorScreenshot);
|
|
1188
1631
|
if(allSS.length>0){
|
|
1189
1632
|
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
|
-
]);
|
|
1633
|
+
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[el('span',{className:'ss-arrow'},'\u25B6'),el('span',{},'Screenshots ('+allSS.length+')')]);
|
|
1194
1634
|
var ssGridEl=el('div',{className:'lt-screenshots-grid'});
|
|
1195
1635
|
allSS.forEach(function(ssPath){
|
|
1196
|
-
var fname=ssPath.split('/').pop();
|
|
1197
|
-
var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
1636
|
+
var fname=ssPath.split('/').pop();var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
1198
1637
|
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';
|
|
1638
|
+
var img=document.createElement('img');img.src='/api/image?path='+encodeURIComponent(ssPath);img.alt=fname;img.loading='lazy';
|
|
1202
1639
|
if(isErr)thumb.style.borderColor='var(--red)';
|
|
1203
1640
|
thumb.appendChild(img);
|
|
1204
1641
|
thumb.addEventListener('click',function(e){e.stopPropagation();openModal('/api/image?path='+encodeURIComponent(ssPath),fname)});
|
|
1205
1642
|
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);
|
|
1643
|
+
(function(lbl,sp){ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))})})(labelEl,ssPath);
|
|
1210
1644
|
ssGridEl.appendChild(el('div',{},[thumb,labelEl]));
|
|
1211
1645
|
});
|
|
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
|
-
});
|
|
1646
|
+
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
1647
|
if(ssOpen)ssGridEl.style.display='grid';
|
|
1219
1648
|
ssEl=el('div',{className:'lt-screenshots'},[toggle,ssGridEl]);
|
|
1220
1649
|
}
|
|
1650
|
+
|
|
1651
|
+
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
1221
1652
|
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
1222
1653
|
el('div',{className:'lt-name'},[
|
|
1223
1654
|
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
1224
|
-
document.createTextNode(' '+name),
|
|
1225
|
-
summaryEl
|
|
1655
|
+
document.createTextNode(' '+name),serialBadge,summaryEl
|
|
1226
1656
|
]),
|
|
1227
|
-
el('div',{className:'lt-meta'},meta),
|
|
1228
|
-
stepsEl
|
|
1657
|
+
el('div',{className:'lt-meta'},meta),stepsEl
|
|
1229
1658
|
]);
|
|
1230
1659
|
if(ssEl)card.appendChild(ssEl);
|
|
1231
|
-
// Network API logs in live view (clickable rows)
|
|
1232
1660
|
if(t.networkLogs&&t.networkLogs.length&&!isCollapsed){
|
|
1233
1661
|
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
|
-
});
|
|
1662
|
+
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])]);
|
|
1663
|
+
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')]);
|
|
1664
|
+
var liveNetBody=el('div',{className:'rd-net-body'},[liveNetCols]);
|
|
1665
|
+
t.networkLogs.forEach(function(n){var built=buildNetRow(n);liveNetBody.appendChild(built.row);if(built.detail)liveNetBody.appendChild(built.detail)});
|
|
1666
|
+
liveNetHead.addEventListener('click',function(e){e.stopPropagation();liveNetHead.classList.toggle('open')});
|
|
1667
|
+
card.appendChild(el('div',{className:'rd-net-panel',style:'margin-top:6px'},[liveNetHead,liveNetBody]));
|
|
1255
1668
|
}
|
|
1669
|
+
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
1670
|
testGrid.appendChild(card);
|
|
1257
1671
|
if(!isCollapsed)stepsEl.scrollTop=stepsEl.scrollHeight;
|
|
1258
1672
|
});
|
|
@@ -1260,22 +1674,194 @@ function renderLive(){
|
|
|
1260
1674
|
});
|
|
1261
1675
|
}
|
|
1262
1676
|
|
|
1263
|
-
/* ── Actions ── */
|
|
1264
1677
|
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
1265
1678
|
|
|
1266
1679
|
/* ── Modal ── */
|
|
1267
1680
|
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
1268
1681
|
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
1269
|
-
document.addEventListener('keydown',function(e){if(e.key==='Escape')$('#modal').classList.remove('open')});
|
|
1270
1682
|
|
|
1271
|
-
/*
|
|
1683
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1684
|
+
Learnings (+ Cross-project, Export)
|
|
1685
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1686
|
+
function refreshLearnings(){
|
|
1687
|
+
var days=$('#learningsDays').value||30;
|
|
1688
|
+
var url=S.project?'/api/db/projects/'+S.project+'/learnings?days='+days:'/api/db/learnings?days='+days;
|
|
1689
|
+
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
1690
|
+
if(!data||data.totalRuns===0){
|
|
1691
|
+
$('#learningsEmpty').style.display='block';
|
|
1692
|
+
$('#learningsOverview').textContent='';$('#learningsTrend').textContent='';
|
|
1693
|
+
$('#learningsFlaky').textContent='';$('#learningsSelectors').textContent='';
|
|
1694
|
+
$('#learningsPages').textContent='';$('#learningsApis').textContent='';
|
|
1695
|
+
$('#learningsErrors').textContent='';
|
|
1696
|
+
$('#badgeLearnings').textContent='-';
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
$('#learningsEmpty').style.display='none';
|
|
1700
|
+
S.lastLearningsData=data;
|
|
1701
|
+
var flakyCount=data.flakyTests?data.flakyTests.length:0;
|
|
1702
|
+
$('#badgeLearnings').textContent=flakyCount>0?flakyCount:'\u2714';
|
|
1703
|
+
if(flakyCount>0){$('#badgeLearnings').style.background='var(--amber-dim)';$('#badgeLearnings').style.color='var(--amber)'}
|
|
1704
|
+
else{$('#badgeLearnings').style.background='';$('#badgeLearnings').style.color=''}
|
|
1705
|
+
renderLearnOverview(data);
|
|
1706
|
+
renderLearnTrend(data.recentTrend||[]);
|
|
1707
|
+
renderLearnFlaky(data.flakyTests||[]);
|
|
1708
|
+
renderLearnSelectors(data.unstableSelectors||[]);
|
|
1709
|
+
renderLearnPages(data.failingPages||[]);
|
|
1710
|
+
renderLearnApis(data.apiIssues||[]);
|
|
1711
|
+
renderLearnErrors(data.topErrors||[]);
|
|
1712
|
+
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
function renderLearnOverview(d){
|
|
1716
|
+
var container=$('#learningsOverview');container.textContent='';
|
|
1717
|
+
var grid=document.createElement('div');grid.className='learn-grid';
|
|
1718
|
+
[{val:d.totalRuns,lbl:'Runs',cls:'accent'},{val:d.totalTests,lbl:'Tests',cls:'accent'},
|
|
1719
|
+
{val:d.overallPassRate+'%',lbl:'Pass Rate',cls:d.overallPassRate>=90?'green':d.overallPassRate>=70?'':'red'},
|
|
1720
|
+
{val:d.avgDurationMs<1000?d.avgDurationMs+'ms':(d.avgDurationMs/1000).toFixed(1)+'s',lbl:'Avg Duration',cls:'purple'},
|
|
1721
|
+
{val:(d.flakyTests?d.flakyTests.length:0),lbl:'Flaky Tests',cls:d.flakyTests&&d.flakyTests.length>0?'red':'green'},
|
|
1722
|
+
{val:(d.unstableSelectors?d.unstableSelectors.length:0),lbl:'Unstable Selectors',cls:d.unstableSelectors&&d.unstableSelectors.length>0?'red':'green'}
|
|
1723
|
+
].forEach(function(item){
|
|
1724
|
+
var stat=document.createElement('div');stat.className='learn-stat';
|
|
1725
|
+
var valEl=document.createElement('div');valEl.className='learn-stat-val '+item.cls;valEl.textContent=item.val;
|
|
1726
|
+
var lblEl=document.createElement('div');lblEl.className='learn-stat-lbl';lblEl.textContent=item.lbl;
|
|
1727
|
+
stat.appendChild(valEl);stat.appendChild(lblEl);grid.appendChild(stat);
|
|
1728
|
+
});
|
|
1729
|
+
container.appendChild(grid);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function renderLearnTrend(trend){
|
|
1733
|
+
var container=$('#learningsTrend');container.textContent='';
|
|
1734
|
+
if(!trend.length)return;
|
|
1735
|
+
var card=document.createElement('div');card.className='card';
|
|
1736
|
+
var label=document.createElement('div');label.className='card-label';label.textContent='Pass Rate Trend (7 days)';card.appendChild(label);
|
|
1737
|
+
var chartDiv=document.createElement('div');chartDiv.className='learn-trend-chart';
|
|
1738
|
+
var w=100/trend.length;var ns='http://www.w3.org/2000/svg';
|
|
1739
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');
|
|
1740
|
+
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');
|
|
1741
|
+
svg.appendChild(bg);
|
|
1742
|
+
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);
|
|
1743
|
+
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
1744
|
+
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);
|
|
1745
|
+
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);
|
|
1746
|
+
trend.forEach(function(t,i){
|
|
1747
|
+
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)');
|
|
1748
|
+
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
1749
|
+
});
|
|
1750
|
+
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
1751
|
+
var dates=document.createElement('div');dates.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:4px';
|
|
1752
|
+
dates.appendChild(el('span',null,trend[0].date));dates.appendChild(el('span',null,trend[trend.length-1].date));
|
|
1753
|
+
card.appendChild(dates);container.appendChild(card);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
function buildLearnTable(title,headers,rows){
|
|
1757
|
+
var card=document.createElement('div');card.className='card learn-section';
|
|
1758
|
+
var h=document.createElement('div');h.className='learn-section-title';h.textContent=title;card.appendChild(h);
|
|
1759
|
+
var wrap=document.createElement('div');wrap.className='tbl-wrap';
|
|
1760
|
+
var tbl=document.createElement('table');tbl.className='learn-table';
|
|
1761
|
+
var thead=document.createElement('thead');var hr=document.createElement('tr');
|
|
1762
|
+
headers.forEach(function(hdr){var th=document.createElement('th');th.textContent=hdr;hr.appendChild(th)});
|
|
1763
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
1764
|
+
var tbody=document.createElement('tbody');
|
|
1765
|
+
rows.forEach(function(cells){
|
|
1766
|
+
var tr=document.createElement('tr');
|
|
1767
|
+
cells.forEach(function(cell){
|
|
1768
|
+
var td=document.createElement('td');
|
|
1769
|
+
if(cell.code){var code=document.createElement('code');code.textContent=cell.code;td.appendChild(code)}
|
|
1770
|
+
else if(cell.badge){var span=document.createElement('span');span.className='badge '+cell.cls;span.textContent=cell.badge;td.appendChild(span)}
|
|
1771
|
+
else{td.textContent=cell.text!==undefined?cell.text:cell}
|
|
1772
|
+
tr.appendChild(td);
|
|
1773
|
+
});
|
|
1774
|
+
tbody.appendChild(tr);
|
|
1775
|
+
});
|
|
1776
|
+
tbl.appendChild(tbody);wrap.appendChild(tbl);card.appendChild(wrap);return card;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
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}]})))}
|
|
1780
|
+
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||'-'}]})))}
|
|
1781
|
+
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}]})))}
|
|
1782
|
+
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||'-'}]})))}
|
|
1783
|
+
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||'-'}]})))}
|
|
1784
|
+
|
|
1785
|
+
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
1786
|
+
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
1787
|
+
|
|
1788
|
+
$('#btnExportLearnings').addEventListener('click',function(){
|
|
1789
|
+
var data=S.lastLearningsData;
|
|
1790
|
+
if(!data){showToast('No learnings data to export','error');return}
|
|
1791
|
+
var md='# E2E Learnings Report\n\n';
|
|
1792
|
+
md+='| Metric | Value |\n|--------|-------|\n';
|
|
1793
|
+
md+='| Total Runs | '+data.totalRuns+' |\n';
|
|
1794
|
+
md+='| Total Tests | '+data.totalTests+' |\n';
|
|
1795
|
+
md+='| Pass Rate | '+data.overallPassRate+'% |\n';
|
|
1796
|
+
md+='| Avg Duration | '+dur(data.avgDurationMs)+' |\n\n';
|
|
1797
|
+
if(data.flakyTests&&data.flakyTests.length){
|
|
1798
|
+
md+='## Flaky Tests\n\n| Test | Flaky Rate | Occurrences |\n|------|-----------|-------------|\n';
|
|
1799
|
+
data.flakyTests.forEach(function(f){md+='| '+f.test_name+' | '+f.flaky_rate+'% | '+f.flaky_count+' |\n'});md+='\n';
|
|
1800
|
+
}
|
|
1801
|
+
if(data.unstableSelectors&&data.unstableSelectors.length){
|
|
1802
|
+
md+='## Unstable Selectors\n\n| Selector | Action | Fail Rate |\n|----------|--------|-----------|\n';
|
|
1803
|
+
data.unstableSelectors.forEach(function(s){md+='| `'+s.selector+'` | '+s.action_type+' | '+s.fail_rate+'% |\n'});md+='\n';
|
|
1804
|
+
}
|
|
1805
|
+
downloadFile('learnings-report.md',md,'text/markdown');
|
|
1806
|
+
showToast('Learnings exported','success');
|
|
1807
|
+
});
|
|
1808
|
+
|
|
1809
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1810
|
+
Keyboard Shortcuts
|
|
1811
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1812
|
+
document.addEventListener('keydown',function(e){
|
|
1813
|
+
var tag=document.activeElement.tagName;
|
|
1814
|
+
if(tag==='INPUT'||tag==='SELECT'||tag==='TEXTAREA')return;
|
|
1815
|
+
if(e.key==='Escape'){
|
|
1816
|
+
if($('#kbModal').classList.contains('open')){$('#kbModal').classList.remove('open');return}
|
|
1817
|
+
if($('#modal').classList.contains('open')){$('#modal').classList.remove('open');return}
|
|
1818
|
+
if(S.selectedRun!==null){
|
|
1819
|
+
var expanded=document.querySelector('#runsBody tr.expanded');
|
|
1820
|
+
if(expanded){
|
|
1821
|
+
var next=expanded.nextElementSibling;
|
|
1822
|
+
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)}
|
|
1823
|
+
S.selectedRun=null;
|
|
1824
|
+
}
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if(e.key==='?'){$('#kbModal').classList.toggle('open');return}
|
|
1830
|
+
var viewMap={'1':'suites','2':'runs','3':'screenshots','4':'learnings','5':'live'};
|
|
1831
|
+
if(viewMap[e.key]){showView(viewMap[e.key]);return}
|
|
1832
|
+
if(e.key==='r'){
|
|
1833
|
+
if(S.view==='suites')refreshSuites();else if(S.view==='runs')refreshRuns();
|
|
1834
|
+
else if(S.view==='screenshots')refreshScreenshots();else if(S.view==='learnings')refreshLearnings();
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if(S.view==='runs'&&(e.key==='j'||e.key==='k')){
|
|
1838
|
+
var visible=_allRunRows.filter(function(item){return item.tr.style.display!=='none'});
|
|
1839
|
+
if(!visible.length)return;
|
|
1840
|
+
if(e.key==='j')S.highlightedRunIdx=Math.min(S.highlightedRunIdx+1,visible.length-1);
|
|
1841
|
+
if(e.key==='k')S.highlightedRunIdx=Math.max(S.highlightedRunIdx-1,0);
|
|
1842
|
+
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')});
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
if(S.view==='runs'&&e.key==='Enter'){
|
|
1846
|
+
var visible2=_allRunRows.filter(function(item){return item.tr.style.display!=='none'});
|
|
1847
|
+
if(S.highlightedRunIdx>=0&&S.highlightedRunIdx<visible2.length){visible2[S.highlightedRunIdx].tr.click()}
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
});
|
|
1851
|
+
$('#kbModal').addEventListener('click',function(e){if(e.target===$('#kbModal'))$('#kbModal').classList.remove('open')});
|
|
1852
|
+
|
|
1853
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1854
|
+
Init
|
|
1855
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1272
1856
|
connectWS();
|
|
1273
1857
|
refreshStatus();
|
|
1274
1858
|
refreshProjects();
|
|
1275
1859
|
refreshSuites();
|
|
1276
1860
|
refreshRuns();
|
|
1277
1861
|
refreshScreenshots();
|
|
1862
|
+
refreshLearnings();
|
|
1278
1863
|
})();
|
|
1864
|
+
|
|
1279
1865
|
</script>
|
|
1280
1866
|
</body>
|
|
1281
1867
|
</html>
|