@matware/e2e-runner 1.1.0 → 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 +505 -279
- 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 +275 -7
- package/commands/create-test.md +50 -0
- package/commands/run.md +49 -0
- package/commands/verify-issue.md +63 -0
- package/package.json +11 -3
- 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 +280 -17
- package/src/ai-generate.js +122 -11
- package/src/config.js +58 -0
- package/src/dashboard.js +173 -10
- package/src/db.js +232 -17
- 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 +575 -16
- package/src/module-resolver.js +273 -0
- package/src/narrate.js +225 -0
- package/src/neo4j-pool.js +124 -0
- package/src/reporter.js +47 -2
- package/src/runner.js +180 -40
- package/src/verify.js +19 -5
- 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 +1091 -268
- package/templates/docker-compose-neo4j.yml +19 -0
- package/templates/e2e.config.js +3 -0
package/templates/dashboard.html
CHANGED
|
@@ -53,12 +53,13 @@ a{color:var(--accent);text-decoration:none}
|
|
|
53
53
|
.ws-dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
54
54
|
|
|
55
55
|
/* ── Main ── */
|
|
56
|
-
.main{margin-left:232px;flex:1;min-height:100vh}
|
|
56
|
+
.main{margin-left:232px;flex:1;min-height:100vh;display:flex;flex-direction:column}
|
|
57
57
|
.main-header{padding:16px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;background:var(--surface);position:sticky;top:0;z-index:40}
|
|
58
58
|
.main-header .title{font-family:var(--sans);font-size:16px;font-weight:600}
|
|
59
59
|
.main-header .actions{margin-left:auto;display:flex;gap:8px}
|
|
60
60
|
.view{display:none;padding:24px}
|
|
61
61
|
.view.active{display:block}
|
|
62
|
+
#view-live.active{display:flex;flex-direction:column;flex:1;min-height:calc(100vh - 0px);padding:16px}
|
|
62
63
|
|
|
63
64
|
/* ── Buttons ── */
|
|
64
65
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;white-space:nowrap}
|
|
@@ -83,6 +84,24 @@ a{color:var(--accent);text-decoration:none}
|
|
|
83
84
|
.stat-val.accent{color:var(--accent)}
|
|
84
85
|
.stat-val.purple{color:var(--purple)}
|
|
85
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
|
+
|
|
86
105
|
/* ── Tables ── */
|
|
87
106
|
.tbl-wrap{overflow-x:auto}
|
|
88
107
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
@@ -104,20 +123,84 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
104
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}
|
|
105
124
|
.chart-bar:hover .tip{display:block}
|
|
106
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
|
+
|
|
107
139
|
/* ── Suite Cards ── */
|
|
108
|
-
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(
|
|
109
|
-
.suite-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
|
|
110
|
-
.suite-card:hover{border-color:var(--border-hi)}
|
|
111
|
-
.suite-card-head{display:flex;align-items:center;
|
|
112
|
-
.suite-card-
|
|
113
|
-
.suite-card-
|
|
114
|
-
.suite-card-
|
|
115
|
-
.suite-card-
|
|
116
|
-
.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}
|
|
117
200
|
|
|
118
201
|
/* ── Live Execution ── */
|
|
119
|
-
.live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease}
|
|
120
|
-
.live-panel.active{display:
|
|
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}
|
|
203
|
+
.live-panel.active{display:flex;flex:1;min-height:0}
|
|
121
204
|
@keyframes fadeSlide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
|
122
205
|
.live-header{padding:14px 16px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--border);background:var(--purple-dim)}
|
|
123
206
|
.live-header .label{font-weight:600;color:var(--purple);font-size:12px;display:flex;align-items:center;gap:8px}
|
|
@@ -128,7 +211,7 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
128
211
|
.live-stats span strong{color:var(--text)}
|
|
129
212
|
.live-progress{height:3px;background:var(--surface3)}
|
|
130
213
|
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
131
|
-
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;
|
|
214
|
+
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;min-height:0}
|
|
132
215
|
.live-test{padding:10px 12px;border-radius:var(--r);border-left:3px solid var(--text3);background:var(--surface2);font-size:11px;transition:border-color .2s,padding .25s,max-height .35s cubic-bezier(.4,0,.2,1)}
|
|
133
216
|
.live-test.running{border-left-color:var(--purple)}
|
|
134
217
|
.live-test.passed{border-left-color:var(--green)}
|
|
@@ -144,14 +227,14 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
144
227
|
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
145
228
|
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
146
229
|
.live-test .lt-icon{font-size:12px}
|
|
147
|
-
.lt-actions{
|
|
230
|
+
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
148
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}
|
|
149
232
|
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
150
233
|
.lt-step .step-icon.ok{color:var(--green)}
|
|
151
234
|
.lt-step .step-icon.fail{color:var(--red)}
|
|
152
235
|
.lt-step .step-icon.run{color:var(--purple)}
|
|
153
236
|
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
154
|
-
.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}
|
|
155
238
|
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
156
239
|
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
157
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}
|
|
@@ -170,11 +253,15 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
170
253
|
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
171
254
|
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
172
255
|
.lr-project-name{letter-spacing:.5px}
|
|
173
|
-
.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}
|
|
174
257
|
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
175
258
|
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
176
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}
|
|
177
260
|
.live-close:hover{color:var(--text);border-color:var(--border-hi)}
|
|
261
|
+
.live-clear-btn{padding:5px 12px;font-size:10px;font-family:var(--mono);font-weight:500;background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer;display:none;transition:all .15s}
|
|
262
|
+
.live-clear-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface3)}
|
|
263
|
+
.lr-dismiss{padding:2px 6px;font-size:9px;font-family:var(--mono);background:transparent;border:1px solid transparent;border-radius:4px;color:var(--text3);cursor:pointer;transition:all .15s;margin-left:auto}
|
|
264
|
+
.lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
|
|
178
265
|
|
|
179
266
|
.live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
180
267
|
.spinner{display:inline-block;width:12px;height:12px;border:2px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
@@ -189,42 +276,138 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
189
276
|
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
190
277
|
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
191
278
|
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
279
|
+
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
280
|
+
.ss-search input{flex:1;max-width:320px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
281
|
+
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
282
|
+
.ss-search input::placeholder{color:var(--text3)}
|
|
283
|
+
.ss-search button{padding:8px 16px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
284
|
+
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
285
|
+
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
286
|
+
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
287
|
+
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
288
|
+
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
192
289
|
|
|
193
290
|
/* ── Inline Run Detail ── */
|
|
194
|
-
.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)}
|
|
195
292
|
.run-detail-row:hover td{background:transparent!important}
|
|
196
|
-
.rd-wrap{overflow:hidden;transition:max-height .
|
|
197
|
-
.rd-wrap.open{max-height:
|
|
198
|
-
.rd-inner{padding:
|
|
199
|
-
.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)}
|
|
200
304
|
.rd-test:last-child{margin-bottom:0}
|
|
201
|
-
.rd-test-head{display:flex;align-items:center;gap:
|
|
202
|
-
.rd-test-
|
|
203
|
-
.rd-test-
|
|
204
|
-
.rd-test-
|
|
205
|
-
.rd-
|
|
206
|
-
.rd-
|
|
207
|
-
.rd-
|
|
208
|
-
.rd-
|
|
209
|
-
.rd-
|
|
210
|
-
.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)}
|
|
211
320
|
.rd-shot .rd-shot-cap .cap-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
212
321
|
.rd-shot .rd-shot-cap .ss-hash{margin-top:3px}
|
|
213
322
|
.rd-shot.err-shot{border-color:rgba(239,68,68,.4)}
|
|
214
323
|
.rd-shot.err-shot .rd-shot-cap{color:var(--red)}
|
|
215
|
-
.rd-logs{margin-
|
|
216
|
-
.rd-log-label{font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.
|
|
217
|
-
.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}
|
|
218
327
|
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
219
328
|
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
220
|
-
.rd-
|
|
221
|
-
.rd-
|
|
222
|
-
.rd-
|
|
223
|
-
.rd-
|
|
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}
|
|
348
|
+
.rd-net-row:hover{background:var(--surface2)}
|
|
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}
|
|
354
|
+
.rd-net-method.get{background:var(--accent-dim);color:var(--accent)}
|
|
355
|
+
.rd-net-method.post{background:var(--green-dim);color:var(--green)}
|
|
356
|
+
.rd-net-method.put,.rd-net-method.patch{background:var(--amber-dim);color:var(--amber)}
|
|
357
|
+
.rd-net-method.delete{background:var(--red-dim);color:var(--red)}
|
|
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}
|
|
365
|
+
.rd-net-row.open+.rd-net-detail{display:block}
|
|
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)}
|
|
224
393
|
tr.expanded td{background:var(--surface2)!important}
|
|
225
394
|
tr.expanded td:first-child{position:relative}
|
|
226
395
|
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
227
396
|
|
|
397
|
+
/* ── Screenshot Hash Badge ── */
|
|
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}
|
|
399
|
+
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
400
|
+
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
401
|
+
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
402
|
+
|
|
403
|
+
/* ── Trigger Source Badges ── */
|
|
404
|
+
.trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
405
|
+
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
406
|
+
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
407
|
+
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
408
|
+
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
409
|
+
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
410
|
+
|
|
228
411
|
/* ── Empty ── */
|
|
229
412
|
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
230
413
|
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
@@ -234,20 +417,70 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
234
417
|
.modal.open{display:flex}
|
|
235
418
|
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
236
419
|
|
|
237
|
-
/* ──
|
|
238
|
-
.
|
|
239
|
-
.
|
|
240
|
-
.
|
|
241
|
-
.
|
|
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}
|
|
242
473
|
|
|
243
474
|
/* ── Responsive ── */
|
|
244
475
|
@media(max-width:768px){
|
|
245
|
-
.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}
|
|
246
477
|
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
247
478
|
.main{margin-left:60px}
|
|
248
|
-
.suite-grid,.gallery{grid-template-columns:1fr}
|
|
479
|
+
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
249
480
|
.lr-test-grid{grid-template-columns:1fr}
|
|
481
|
+
.toast-container{right:12px;bottom:12px}
|
|
250
482
|
}
|
|
483
|
+
|
|
251
484
|
</style>
|
|
252
485
|
</head>
|
|
253
486
|
<body>
|
|
@@ -272,13 +505,16 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
272
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>
|
|
273
506
|
</div>
|
|
274
507
|
<div class="nav-item active" data-view="suites">
|
|
275
|
-
<i class="icon">▷</i><span>Suites</span>
|
|
508
|
+
<i class="icon">▷</i><span>Suites</span><span class="badge" id="badgeSuites">-</span>
|
|
276
509
|
</div>
|
|
277
510
|
<div class="nav-item" data-view="runs">
|
|
278
|
-
<i class="icon">☰</i><span>Runs</span>
|
|
511
|
+
<i class="icon">☰</i><span>Runs</span><span class="badge" id="badgeRuns">-</span>
|
|
279
512
|
</div>
|
|
280
513
|
<div class="nav-item" data-view="screenshots">
|
|
281
|
-
<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>
|
|
282
518
|
</div>
|
|
283
519
|
|
|
284
520
|
<div class="pool-status" id="poolStatus">
|
|
@@ -312,6 +548,7 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
312
548
|
<span style="color:var(--red)">Fail: <strong id="liveFail">0</strong></span>
|
|
313
549
|
<span style="color:var(--purple)">Active: <strong id="liveActive">0</strong></span>
|
|
314
550
|
</div>
|
|
551
|
+
<button class="live-clear-btn" id="liveClearBtn">Clear All</button>
|
|
315
552
|
</div>
|
|
316
553
|
<div class="live-progress"><div class="live-progress-fill" id="liveProgressFill" style="width:0"></div></div>
|
|
317
554
|
<div class="live-tests" id="liveTests"></div>
|
|
@@ -327,9 +564,10 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
327
564
|
<div class="view active" id="view-suites">
|
|
328
565
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
329
566
|
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Test Suites</div>
|
|
330
|
-
<button class="btn primary" id="btnRunAll">Run All Tests</button>
|
|
331
567
|
</div>
|
|
568
|
+
<div id="suiteAccordionContainer"></div>
|
|
332
569
|
<div class="suite-grid" id="suiteGrid"></div>
|
|
570
|
+
<div id="moduleSection"></div>
|
|
333
571
|
<div class="empty" id="suitesEmpty" style="display:none">
|
|
334
572
|
<div class="empty-icon">▷</div>
|
|
335
573
|
<p>No test suites found.</p>
|
|
@@ -338,11 +576,20 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
338
576
|
|
|
339
577
|
<!-- Runs View -->
|
|
340
578
|
<div class="view" id="view-runs">
|
|
341
|
-
<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>
|
|
342
582
|
<div class="card">
|
|
343
583
|
<div class="card-label">Pass Rate Trend</div>
|
|
344
584
|
<div class="chart" id="trendChart"></div>
|
|
345
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>
|
|
346
593
|
<div class="card" style="padding:0">
|
|
347
594
|
<div class="tbl-wrap">
|
|
348
595
|
<table>
|
|
@@ -357,9 +604,42 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
357
604
|
</div>
|
|
358
605
|
</div>
|
|
359
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
|
+
|
|
360
635
|
<!-- Screenshots View -->
|
|
361
636
|
<div class="view" id="view-screenshots">
|
|
362
637
|
<div style="font-family:var(--sans);font-size:16px;font-weight:600;margin-bottom:20px">Screenshots</div>
|
|
638
|
+
<div class="ss-search">
|
|
639
|
+
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
640
|
+
<button id="ssHashBtn">Search</button>
|
|
641
|
+
</div>
|
|
642
|
+
<div id="ssSearchResult"></div>
|
|
363
643
|
<div class="gallery" id="screenshotGallery"></div>
|
|
364
644
|
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
365
645
|
<div class="empty-icon">▣</div>
|
|
@@ -370,6 +650,22 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
370
650
|
</div>
|
|
371
651
|
|
|
372
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>
|
|
373
669
|
|
|
374
670
|
<script>
|
|
375
671
|
(function(){
|
|
@@ -393,6 +689,96 @@ function css(n){return n.replace(/[^a-zA-Z0-9\-_]/g,'_')}
|
|
|
393
689
|
function dur(ms){return ms>=1000?(ms/1000).toFixed(1)+'s':ms+'ms'}
|
|
394
690
|
function fdate(iso){return iso?new Date(iso).toLocaleString():'--'}
|
|
395
691
|
|
|
692
|
+
function prettyJson(str){
|
|
693
|
+
if(!str)return '';
|
|
694
|
+
try{return JSON.stringify(JSON.parse(str),null,2)}catch(e){return str}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function fmtHeaders(h){
|
|
698
|
+
if(!h||typeof h!=='object')return '';
|
|
699
|
+
return Object.keys(h).map(function(k){return k+': '+h[k]}).join('\n');
|
|
700
|
+
}
|
|
701
|
+
|
|
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
|
+
|
|
743
|
+
function buildNetRow(n){
|
|
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');
|
|
747
|
+
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
748
|
+
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
749
|
+
var row=el('div',{className:rowCls},[
|
|
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||''),
|
|
755
|
+
el('span',{className:'rd-net-dur'},dur(n.duration))
|
|
756
|
+
]);
|
|
757
|
+
var detail=null;
|
|
758
|
+
if(hasDetail){
|
|
759
|
+
var sections=[];
|
|
760
|
+
if(n.requestHeaders){
|
|
761
|
+
var hCount=Object.keys(n.requestHeaders).length;
|
|
762
|
+
sections.push(buildNdSection('Request Headers',buildHeaderKV(n.requestHeaders),hCount,fmtHeaders(n.requestHeaders)));
|
|
763
|
+
}
|
|
764
|
+
if(n.requestBody){
|
|
765
|
+
var rbText=prettyJson(n.requestBody);
|
|
766
|
+
sections.push(buildNdSection('Request Body',el('pre',null,rbText),null,rbText));
|
|
767
|
+
}
|
|
768
|
+
if(n.responseHeaders){
|
|
769
|
+
var rhCount=Object.keys(n.responseHeaders).length;
|
|
770
|
+
sections.push(buildNdSection('Response Headers',buildHeaderKV(n.responseHeaders),rhCount,fmtHeaders(n.responseHeaders)));
|
|
771
|
+
}
|
|
772
|
+
if(n.responseBody){
|
|
773
|
+
var respText=prettyJson(n.responseBody);
|
|
774
|
+
sections.push(buildNdSection('Response Body',el('pre',null,respText),null,respText));
|
|
775
|
+
}
|
|
776
|
+
detail=el('div',{className:'rd-net-detail'},sections);
|
|
777
|
+
row.addEventListener('click',function(e){e.stopPropagation();row.classList.toggle('open')});
|
|
778
|
+
}
|
|
779
|
+
return {row:row,detail:detail};
|
|
780
|
+
}
|
|
781
|
+
|
|
396
782
|
/* ── Screenshot hash helpers ── */
|
|
397
783
|
var ssHashCache={};
|
|
398
784
|
async function ssHash(filePath){
|
|
@@ -419,10 +805,55 @@ function createHashBadge(hash){
|
|
|
419
805
|
return badge;
|
|
420
806
|
}
|
|
421
807
|
|
|
808
|
+
function createTriggerBadge(source){
|
|
809
|
+
var s=source||'unknown';
|
|
810
|
+
var labels={dashboard:'Dashboard',mcp:'MCP',cli:'CLI',unknown:'--'};
|
|
811
|
+
var icons={dashboard:'\u{1F464}',mcp:'\u{1F916}',cli:'>_',unknown:'\u2022'};
|
|
812
|
+
var badge=el('span',{className:'trigger-badge src-'+s},[
|
|
813
|
+
el('span',{className:'trig-icon'},icons[s]||icons.unknown),
|
|
814
|
+
document.createTextNode(labels[s]||s)
|
|
815
|
+
]);
|
|
816
|
+
return badge;
|
|
817
|
+
}
|
|
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
|
+
|
|
422
850
|
/* ── State ── */
|
|
423
851
|
var S={
|
|
424
852
|
ws:null,project:null,view:'suites',selectedRun:null,
|
|
425
|
-
liveRuns:{},
|
|
853
|
+
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
854
|
+
runFilter:{status:'all',search:''},
|
|
855
|
+
lastLearningsData:null,
|
|
856
|
+
highlightedRunIdx:-1
|
|
426
857
|
};
|
|
427
858
|
|
|
428
859
|
/* ── Navigation ── */
|
|
@@ -442,33 +873,44 @@ function showView(v){
|
|
|
442
873
|
$('#view-'+v).classList.add('active');
|
|
443
874
|
}
|
|
444
875
|
|
|
445
|
-
/*
|
|
876
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
877
|
+
WebSocket
|
|
878
|
+
══════════════════════════════════════════════════════════════════ */
|
|
446
879
|
function connectWS(){
|
|
447
880
|
var proto=location.protocol==='https:'?'wss:':'ws:';
|
|
448
881
|
S.ws=new WebSocket(proto+'//'+location.host);
|
|
449
|
-
S.ws.onopen=function(){
|
|
450
|
-
|
|
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
|
+
};
|
|
451
890
|
S.ws.onerror=function(){};
|
|
452
891
|
S.ws.onmessage=function(e){try{handleWS(JSON.parse(e.data))}catch(x){}};
|
|
453
892
|
}
|
|
454
893
|
|
|
455
894
|
function getLiveRun(m){
|
|
456
895
|
var rid=m.runId;if(!rid)return null;
|
|
457
|
-
if(!S.liveRuns[rid])S.liveRuns[rid]={on:true,done:false,total:0,completed:0,passed:0,failed:0,active:0,tests:{},project:m.project||null,cwd:m.cwd||null,runId:rid,_lastEvent:Date.now()};
|
|
896
|
+
if(!S.liveRuns[rid])S.liveRuns[rid]={on:true,done:false,total:0,completed:0,passed:0,failed:0,active:0,tests:{},project:m.project||null,cwd:m.cwd||null,triggeredBy:m.triggeredBy||null,runId:rid,_lastEvent:Date.now()};
|
|
458
897
|
S.liveRuns[rid]._lastEvent=Date.now();
|
|
459
898
|
return S.liveRuns[rid];
|
|
460
899
|
}
|
|
461
900
|
function anyLiveRunning(){for(var k in S.liveRuns)if(S.liveRuns[k].on)return true;return false}
|
|
462
901
|
|
|
463
|
-
/* Staleness guard: if a live run gets no events for 15s and all tests are complete, auto-finish it */
|
|
464
902
|
setInterval(function(){
|
|
465
903
|
var changed=false;
|
|
466
904
|
for(var k in S.liveRuns){
|
|
467
905
|
var r=S.liveRuns[k];
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
906
|
+
var age=Date.now()-r._lastEvent;
|
|
907
|
+
if(r.on&&!r.done){
|
|
908
|
+
if(r.total===0&&age>10000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
909
|
+
else if(r.completed>=r.total&&r.total>0&&age>15000){r.on=false;r.done=true;r.active=0;changed=true}
|
|
910
|
+
else if(age>30000){r.on=false;r.done=true;r.stale=true;r.active=0;changed=true}
|
|
471
911
|
}
|
|
912
|
+
if(r.done&&r.stale&&r.total===0&&age>15000){delete S.liveRuns[k];changed=true}
|
|
913
|
+
else if(r.done&&age>120000){delete S.liveRuns[k];changed=true}
|
|
472
914
|
}
|
|
473
915
|
if(changed)renderLive();
|
|
474
916
|
},5000);
|
|
@@ -477,50 +919,58 @@ function handleWS(m){
|
|
|
477
919
|
switch(m.event){
|
|
478
920
|
case 'pool:status':renderPool(m.data);break;
|
|
479
921
|
case 'run:start':
|
|
922
|
+
for(var dk in S.liveRuns){if(S.liveRuns[dk].done)delete S.liveRuns[dk]}
|
|
480
923
|
var r=getLiveRun(m);
|
|
481
924
|
r.total=m.total;r.on=true;r.done=false;
|
|
482
|
-
S.
|
|
925
|
+
S.liveCollapsed=new Set();S.liveSSOpen=new Set();
|
|
483
926
|
showView('live');renderLive();break;
|
|
484
927
|
case 'test:start':
|
|
485
|
-
var
|
|
486
|
-
|
|
487
|
-
|
|
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};
|
|
488
931
|
renderLive();break;
|
|
489
932
|
case 'test:action':
|
|
490
|
-
var
|
|
491
|
-
var t=
|
|
933
|
+
var r3=getLiveRun(m);if(!r3||!r3.tests[m.name])break;
|
|
934
|
+
var t=r3.tests[m.name];
|
|
492
935
|
t.actions=m.actionIndex+1;t.totalActions=m.totalActions;t.actionType=m.action.type;
|
|
493
|
-
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});
|
|
494
937
|
if(m.screenshotPath)t.screenshots.push(m.screenshotPath);
|
|
495
938
|
renderLive();break;
|
|
496
939
|
case 'test:retry':
|
|
497
|
-
var
|
|
498
|
-
|
|
940
|
+
var r4=getLiveRun(m);if(!r4||!r4.tests[m.name])break;
|
|
941
|
+
r4.tests[m.name].retry=m.attempt+'/'+m.maxAttempts;
|
|
499
942
|
renderLive();break;
|
|
500
943
|
case 'test:complete':
|
|
501
|
-
var
|
|
502
|
-
|
|
503
|
-
if(m.success){
|
|
504
|
-
else{
|
|
505
|
-
if(
|
|
506
|
-
|
|
507
|
-
if(m.screenshots&&m.screenshots.length)
|
|
508
|
-
if(m.errorScreenshot)
|
|
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;
|
|
509
953
|
}
|
|
510
|
-
|
|
954
|
+
r5.active=Math.max(0,r5.active-1);
|
|
511
955
|
renderLive();break;
|
|
512
956
|
case 'run:complete':
|
|
513
|
-
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')}
|
|
514
961
|
renderLive();refreshRuns();refreshProjects();break;
|
|
515
962
|
case 'run:error':
|
|
516
|
-
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');
|
|
517
965
|
renderLive();break;
|
|
518
966
|
case 'db:updated':
|
|
519
|
-
refreshRuns();refreshProjects();refreshScreenshots();break;
|
|
967
|
+
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();break;
|
|
520
968
|
}
|
|
521
969
|
}
|
|
522
970
|
|
|
523
|
-
/*
|
|
971
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
972
|
+
API & Pool
|
|
973
|
+
══════════════════════════════════════════════════════════════════ */
|
|
524
974
|
function api(p){return fetch(p).then(function(r){return r.json()})}
|
|
525
975
|
function triggerRun(suite,projectId){
|
|
526
976
|
if(anyLiveRunning())return;
|
|
@@ -531,7 +981,6 @@ function triggerRun(suite,projectId){
|
|
|
531
981
|
fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
532
982
|
}
|
|
533
983
|
|
|
534
|
-
/* ── Pool ── */
|
|
535
984
|
function renderPool(d){
|
|
536
985
|
if(!d)return;
|
|
537
986
|
$('#poolDot').className='pool-dot '+(d.error||!d.available?'off':'on');
|
|
@@ -540,7 +989,9 @@ function renderPool(d){
|
|
|
540
989
|
}
|
|
541
990
|
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
542
991
|
|
|
543
|
-
/*
|
|
992
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
993
|
+
Projects
|
|
994
|
+
══════════════════════════════════════════════════════════════════ */
|
|
544
995
|
function refreshProjects(){
|
|
545
996
|
api('/api/db/projects').then(function(projects){
|
|
546
997
|
var sel=$('#projectSelect'),prev=sel.value;
|
|
@@ -554,77 +1005,234 @@ function refreshProjects(){
|
|
|
554
1005
|
$('#projectSelect').addEventListener('change',function(){
|
|
555
1006
|
S.project=this.value?parseInt(this.value,10):null;
|
|
556
1007
|
S.selectedRun=null;
|
|
557
|
-
refreshRuns();refreshSuites();refreshScreenshots();
|
|
1008
|
+
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();
|
|
558
1009
|
});
|
|
559
1010
|
|
|
560
|
-
/*
|
|
1011
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1012
|
+
Suites (+ Serial badges, Modules)
|
|
1013
|
+
══════════════════════════════════════════════════════════════════ */
|
|
561
1014
|
function refreshSuites(){
|
|
562
|
-
var grid=$('#suiteGrid'),empty=$('#suitesEmpty');
|
|
1015
|
+
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
563
1016
|
grid.textContent='';
|
|
1017
|
+
var moduleSection=$('#moduleSection');
|
|
1018
|
+
moduleSection.textContent='';
|
|
564
1019
|
|
|
565
1020
|
if(S.project){
|
|
566
|
-
// Single project — fetch its suites
|
|
567
1021
|
api('/api/db/projects/'+S.project+'/suites').then(function(suites){
|
|
568
1022
|
if(!Array.isArray(suites)||suites.length===0){empty.style.display='block';empty.querySelector('p').textContent='No test suites found for this project.';return}
|
|
569
1023
|
empty.style.display='none';
|
|
1024
|
+
$('#badgeSuites').textContent=suites.length;
|
|
570
1025
|
renderSuiteCards(grid,suites,S.project);
|
|
571
1026
|
}).catch(function(){});
|
|
1027
|
+
api('/api/db/projects/'+S.project+'/modules').then(function(modules){
|
|
1028
|
+
renderModules(moduleSection,modules);
|
|
1029
|
+
}).catch(function(){});
|
|
572
1030
|
} else {
|
|
573
|
-
// All projects — fetch each project's suites
|
|
574
1031
|
api('/api/db/projects').then(function(projects){
|
|
575
1032
|
if(!Array.isArray(projects)||projects.length===0){empty.style.display='block';empty.querySelector('p').textContent='No projects registered yet.';return}
|
|
576
|
-
var loaded=0,hasAny=false;
|
|
1033
|
+
var loaded=0,hasAny=false,totalSuites=0;
|
|
577
1034
|
projects.forEach(function(p){
|
|
578
1035
|
api('/api/db/projects/'+p.id+'/suites').then(function(suites){
|
|
579
1036
|
loaded++;
|
|
580
1037
|
if(Array.isArray(suites)&&suites.length>0){
|
|
581
|
-
hasAny=true;
|
|
1038
|
+
hasAny=true;totalSuites+=suites.length;
|
|
582
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);
|
|
583
1040
|
grid.appendChild(label);
|
|
584
1041
|
renderSuiteCards(grid,suites,p.id);
|
|
585
1042
|
}
|
|
586
|
-
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
|
+
}
|
|
587
1047
|
}).catch(function(){loaded++;});
|
|
588
1048
|
});
|
|
589
1049
|
}).catch(function(){});
|
|
590
1050
|
}
|
|
591
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={};
|
|
592
1079
|
function renderSuiteCards(container,suites,projectId){
|
|
593
1080
|
suites.forEach(function(s){
|
|
594
1081
|
var tests=el('ul',{className:'suite-card-tests'});
|
|
595
|
-
(s.tests||[]).forEach(function(t){tests.appendChild(el('li',null,t))});
|
|
596
1082
|
var pid=projectId;
|
|
597
|
-
|
|
598
|
-
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'},[
|
|
599
1129
|
el('div',{className:'suite-card-name'},s.name),
|
|
600
|
-
el('
|
|
1130
|
+
el('div',{className:'suite-card-file'},s.file||s.name+'.json')
|
|
601
1131
|
]),
|
|
602
|
-
|
|
603
|
-
|
|
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
|
+
])
|
|
604
1147
|
]);
|
|
605
1148
|
container.appendChild(card);
|
|
606
1149
|
});
|
|
607
1150
|
}
|
|
608
1151
|
|
|
609
|
-
|
|
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
|
+
|
|
610
1217
|
function refreshRuns(){
|
|
611
1218
|
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
612
1219
|
api(url).then(function(rows){
|
|
613
1220
|
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
614
1221
|
chart.textContent='';body.textContent='';
|
|
615
|
-
|
|
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}
|
|
616
1225
|
empty.style.display='none';head.parentNode.parentNode.style.display='';
|
|
1226
|
+
$('#badgeRuns').textContent=rows.length;
|
|
617
1227
|
|
|
618
|
-
// Thead
|
|
619
1228
|
var htr=document.createElement('tr');
|
|
620
1229
|
var cols=[];
|
|
621
1230
|
if(!S.project)cols.push('Project');
|
|
622
|
-
cols=cols.concat(['Suite','Date','Total','Pass','Fail','Rate','Time']);
|
|
1231
|
+
cols=cols.concat(['Suite','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
623
1232
|
cols.forEach(function(c){htr.appendChild(el('th',null,c))});
|
|
624
1233
|
head.textContent='';head.appendChild(htr);
|
|
625
1234
|
var colSpan=cols.length;
|
|
626
1235
|
|
|
627
|
-
// Chart
|
|
628
1236
|
rows.slice(0,40).slice().reverse().forEach(function(r){
|
|
629
1237
|
var rate=parseFloat(r.pass_rate)||0;
|
|
630
1238
|
var color=rate>=90?'var(--green)':rate>=70?'var(--amber)':'var(--red)';
|
|
@@ -633,13 +1241,13 @@ function refreshRuns(){
|
|
|
633
1241
|
chart.appendChild(bar);
|
|
634
1242
|
});
|
|
635
1243
|
|
|
636
|
-
// Rows
|
|
637
1244
|
rows.forEach(function(r){
|
|
638
1245
|
var tr=document.createElement('tr');
|
|
639
1246
|
tr.dataset.runId=r.id;
|
|
640
1247
|
if(r.id===S.selectedRun)tr.classList.add('expanded');
|
|
641
1248
|
if(!S.project)tr.appendChild(el('td',{style:'font-weight:600'},r.project_name||'-'));
|
|
642
1249
|
tr.appendChild(el('td',{style:'color:var(--accent)'},r.suite_name||'all'));
|
|
1250
|
+
var srcTd=document.createElement('td');srcTd.appendChild(createTriggerBadge(r.triggered_by));tr.appendChild(srcTd);
|
|
643
1251
|
tr.appendChild(el('td',null,fdate(r.generated_at)));
|
|
644
1252
|
tr.appendChild(el('td',null,String(r.total||0)));
|
|
645
1253
|
tr.appendChild(el('td',{style:'color:var(--green)'},String(r.passed||0)));
|
|
@@ -650,12 +1258,14 @@ function refreshRuns(){
|
|
|
650
1258
|
tr.addEventListener('click',function(){toggleDetail(r.id,tr,colSpan)});
|
|
651
1259
|
body.appendChild(tr);
|
|
652
1260
|
|
|
653
|
-
|
|
1261
|
+
var item={tr:tr,data:r,detailTr:null};
|
|
654
1262
|
if(r.id===S.selectedRun){
|
|
655
1263
|
var detailTr=createDetailRow(colSpan);
|
|
656
1264
|
body.appendChild(detailTr);
|
|
657
1265
|
loadDetailInline(r.id,detailTr);
|
|
1266
|
+
item.detailTr=detailTr;
|
|
658
1267
|
}
|
|
1268
|
+
_allRunRows.push(item);
|
|
659
1269
|
});
|
|
660
1270
|
}).catch(function(){});
|
|
661
1271
|
}
|
|
@@ -666,8 +1276,12 @@ function createDetailRow(colSpan){
|
|
|
666
1276
|
var td=document.createElement('td');
|
|
667
1277
|
td.setAttribute('colspan',colSpan);
|
|
668
1278
|
var wrap=el('div',{className:'rd-wrap'});
|
|
669
|
-
var inner=el('div',{className:'rd-inner'}
|
|
670
|
-
|
|
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
|
+
]);
|
|
671
1285
|
wrap.appendChild(inner);
|
|
672
1286
|
td.appendChild(wrap);
|
|
673
1287
|
detailTr.appendChild(td);
|
|
@@ -675,7 +1289,6 @@ function createDetailRow(colSpan){
|
|
|
675
1289
|
}
|
|
676
1290
|
|
|
677
1291
|
function toggleDetail(id,clickedTr,colSpan){
|
|
678
|
-
// If already expanded, collapse
|
|
679
1292
|
if(S.selectedRun===id){
|
|
680
1293
|
var existing=clickedTr.nextElementSibling;
|
|
681
1294
|
if(existing&&existing.classList.contains('run-detail-row')){
|
|
@@ -688,7 +1301,6 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
688
1301
|
return;
|
|
689
1302
|
}
|
|
690
1303
|
|
|
691
|
-
// Collapse any other open detail
|
|
692
1304
|
var prevTr=document.querySelector('#runsBody tr.expanded');
|
|
693
1305
|
if(prevTr){
|
|
694
1306
|
prevTr.classList.remove('expanded');
|
|
@@ -700,81 +1312,106 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
700
1312
|
}
|
|
701
1313
|
}
|
|
702
1314
|
|
|
703
|
-
// Expand new
|
|
704
1315
|
S.selectedRun=id;
|
|
705
1316
|
clickedTr.classList.add('expanded');
|
|
706
1317
|
var detailTr=createDetailRow(colSpan);
|
|
707
1318
|
clickedTr.parentNode.insertBefore(detailTr,clickedTr.nextSibling);
|
|
708
|
-
|
|
709
|
-
// Animate open
|
|
710
1319
|
requestAnimationFrame(function(){
|
|
711
1320
|
requestAnimationFrame(function(){
|
|
712
1321
|
var w2=detailTr.querySelector('.rd-wrap');
|
|
713
1322
|
if(w2)w2.classList.add('open');
|
|
714
1323
|
});
|
|
715
1324
|
});
|
|
716
|
-
|
|
717
1325
|
loadDetailInline(id,detailTr);
|
|
718
1326
|
}
|
|
719
1327
|
|
|
1328
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1329
|
+
Run Detail (+ Action Narratives, Retry badges, Export)
|
|
1330
|
+
══════════════════════════════════════════════════════════════════ */
|
|
720
1331
|
function loadDetailInline(id,detailTr){
|
|
721
1332
|
api('/api/db/runs/'+id).then(function(d){
|
|
722
1333
|
if(d.error)return;
|
|
723
1334
|
var inner=detailTr.querySelector('.rd-inner');
|
|
724
1335
|
inner.textContent='';
|
|
725
|
-
|
|
726
1336
|
var results=d.results||[];
|
|
727
1337
|
|
|
728
|
-
|
|
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
|
+
]);
|
|
1347
|
+
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
729
1348
|
var summ=el('div',{className:'rd-summary'},[
|
|
730
|
-
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')]),
|
|
1350
|
+
srcBlock,
|
|
731
1351
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
732
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))]),
|
|
733
|
-
el('div',null,[el('div',{className:'rd-s-label'},'Failed'),el('div',{className:'rd-s-val',style:'color:var(--red)'},String(d.summary.failed))]),
|
|
734
|
-
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
|
|
735
1356
|
]);
|
|
736
1357
|
inner.appendChild(summ);
|
|
737
1358
|
|
|
738
|
-
// Test result cards
|
|
739
1359
|
results.forEach(function(r){
|
|
740
1360
|
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
741
1361
|
var flaky=r.success&&r.attempt>1;
|
|
1362
|
+
var state=flaky?'flaky':(r.success?'pass':'fail');
|
|
742
1363
|
|
|
743
|
-
// Header: badge + name + duration
|
|
744
1364
|
var badges=el('div',{style:'display:flex;gap:6px;align-items:center;flex-shrink:0'});
|
|
745
1365
|
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
746
1366
|
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
747
1367
|
|
|
748
|
-
var head=el('div',{className:'rd-test-head'},[
|
|
749
|
-
badges,
|
|
750
|
-
el('div',{className:'rd-test-name'},r.name),
|
|
751
|
-
el('div',{className:'rd-test-dur'},d2)
|
|
752
|
-
]);
|
|
753
|
-
|
|
754
|
-
// 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)]);
|
|
755
1369
|
var body=el('div',{className:'rd-test-body'});
|
|
756
1370
|
|
|
757
|
-
|
|
758
|
-
if(r.
|
|
759
|
-
|
|
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);
|
|
760
1377
|
}
|
|
761
1378
|
|
|
762
|
-
//
|
|
763
|
-
if(r.
|
|
764
|
-
|
|
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]));
|
|
765
1408
|
}
|
|
766
1409
|
|
|
767
|
-
// Screenshots
|
|
1410
|
+
// Screenshots
|
|
768
1411
|
var shots=[];
|
|
769
1412
|
var hashes=r.screenshotHashes||{};
|
|
770
|
-
(r.screenshots||[]).forEach(function(p){
|
|
771
|
-
|
|
772
|
-
shots.push({path:p,label:fname,type:'screenshot',hash:hashes[p]||null});
|
|
773
|
-
});
|
|
774
|
-
if(r.errorScreenshot){
|
|
775
|
-
var ename=r.errorScreenshot.split('/').pop();
|
|
776
|
-
shots.push({path:r.errorScreenshot,label:ename,type:'error',hash:hashes[r.errorScreenshot]||null});
|
|
777
|
-
}
|
|
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})}
|
|
778
1415
|
if(shots.length){
|
|
779
1416
|
var shotsWrap=el('div',{className:'rd-shots'});
|
|
780
1417
|
shots.forEach(function(s){
|
|
@@ -783,95 +1420,138 @@ function loadDetailInline(id,detailTr){
|
|
|
783
1420
|
var capEl=el('div',{className:'rd-shot-cap'},[el('span',{className:'cap-name'},s.label)]);
|
|
784
1421
|
if(s.hash){capEl.appendChild(createHashBadge(s.hash))}
|
|
785
1422
|
else{(function(c,fp){ssHash(fp).then(function(h){c.appendChild(createHashBadge(h))})})(capEl,s.path)}
|
|
786
|
-
|
|
787
|
-
img,capEl
|
|
788
|
-
]);
|
|
789
|
-
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]));
|
|
790
1424
|
});
|
|
791
1425
|
body.appendChild(shotsWrap);
|
|
792
1426
|
}
|
|
793
1427
|
|
|
794
|
-
// Console logs
|
|
1428
|
+
// Console logs
|
|
795
1429
|
var cIssues=(r.consoleLogs||[]).filter(function(l){return l.type==='error'||l.type==='warn'||l.type==='warning'});
|
|
796
1430
|
if(cIssues.length){
|
|
797
|
-
var
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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]));
|
|
803
1446
|
}
|
|
804
1447
|
|
|
805
1448
|
// Network errors
|
|
806
1449
|
if(r.networkErrors&&r.networkErrors.length){
|
|
807
|
-
var
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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]));
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// Network panel
|
|
1463
|
+
if(r.networkLogs&&r.networkLogs.length){
|
|
1464
|
+
var errCount=r.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
1465
|
+
var netHead=el('div',{className:'rd-net-head'},[
|
|
1466
|
+
el('span',{className:'net-arrow'},'\u25B6'),
|
|
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
|
+
])
|
|
1472
|
+
]);
|
|
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]));
|
|
813
1478
|
}
|
|
814
1479
|
|
|
815
|
-
|
|
816
|
-
inner.appendChild(testCard);
|
|
1480
|
+
inner.appendChild(el('div',{className:'rd-test '+state},[head,body]));
|
|
817
1481
|
});
|
|
818
1482
|
|
|
819
|
-
// Re-trigger open animation if not yet open
|
|
820
1483
|
var w=detailTr.querySelector('.rd-wrap');
|
|
821
|
-
if(w&&!w.classList.contains('open')){
|
|
822
|
-
|
|
823
|
-
}
|
|
824
|
-
}).catch(function(err){
|
|
1484
|
+
if(w&&!w.classList.contains('open')){requestAnimationFrame(function(){w.classList.add('open')})}
|
|
1485
|
+
}).catch(function(){
|
|
825
1486
|
var inner=detailTr.querySelector('.rd-inner');
|
|
826
1487
|
if(inner)inner.textContent='Failed to load run detail';
|
|
827
1488
|
});
|
|
828
1489
|
}
|
|
829
1490
|
|
|
830
|
-
/*
|
|
1491
|
+
/* ══════════════════════════════════════════════════════════════════
|
|
1492
|
+
Screenshots
|
|
1493
|
+
══════════════════════════════════════════════════════════════════ */
|
|
831
1494
|
function refreshScreenshots(){
|
|
832
1495
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
833
1496
|
gal.textContent='';
|
|
834
|
-
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}
|
|
835
1498
|
api('/api/db/projects/'+S.project+'/screenshots').then(function(files){
|
|
836
|
-
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}
|
|
837
1500
|
empty.style.display='none';
|
|
1501
|
+
$('#badgeScreenshots').textContent=files.length;
|
|
838
1502
|
files.forEach(function(f){
|
|
839
1503
|
var src='/api/image?path='+encodeURIComponent(f.path);
|
|
840
1504
|
var img=document.createElement('img');img.src=src;img.alt=f.name;img.loading='lazy';
|
|
841
1505
|
var capEl=el('div',{className:'cap'},[el('span',{className:'cap-name'},f.name)]);
|
|
842
|
-
(function(c,fp){
|
|
843
|
-
|
|
844
|
-
})(capEl,f.path);
|
|
845
|
-
var item=el('div',{className:'gallery-item',onclick:function(){openModal(src)}},[
|
|
846
|
-
img,capEl
|
|
847
|
-
]);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]));
|
|
848
1508
|
});
|
|
849
1509
|
}).catch(function(){});
|
|
850
1510
|
}
|
|
851
1511
|
|
|
852
|
-
|
|
1512
|
+
function searchByHash(){
|
|
1513
|
+
var container=$('#ssSearchResult');
|
|
1514
|
+
container.textContent='';
|
|
1515
|
+
var raw=$('#ssHashInput').value.trim();
|
|
1516
|
+
if(!raw)return;
|
|
1517
|
+
var hash=raw.replace(/^ss:/,'');
|
|
1518
|
+
if(!/^[a-f0-9]{1,8}$/i.test(hash)){
|
|
1519
|
+
container.appendChild(el('div',{className:'ss-search-error'},'Invalid hash format. Expected 8 hex characters (e.g. ss:a3f2b1c9).'));
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
fetch('/api/screenshot-hash/'+hash).then(function(res){
|
|
1523
|
+
if(!res.ok){container.appendChild(el('div',{className:'ss-search-error'},'Screenshot not found for hash: ss:'+hash));return}
|
|
1524
|
+
return res.blob();
|
|
1525
|
+
}).then(function(blob){
|
|
1526
|
+
if(!blob)return;
|
|
1527
|
+
var url=URL.createObjectURL(blob);
|
|
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;
|
|
1530
|
+
img.addEventListener('click',function(){openModal(url)});
|
|
1531
|
+
wrap.appendChild(img);
|
|
1532
|
+
container.appendChild(wrap);
|
|
1533
|
+
}).catch(function(){container.appendChild(el('div',{className:'ss-search-error'},'Error searching for screenshot.'))});
|
|
1534
|
+
}
|
|
1535
|
+
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1536
|
+
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1537
|
+
|
|
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()}
|
|
1543
|
+
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
1544
|
+
|
|
853
1545
|
function renderLive(){
|
|
854
1546
|
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
855
|
-
var runs=S.liveRuns;
|
|
856
|
-
var runIds=Object.keys(runs);
|
|
1547
|
+
var runs=S.liveRuns;var runIds=Object.keys(runs);
|
|
857
1548
|
|
|
858
|
-
if(runIds.length===0){
|
|
859
|
-
panel.classList.remove('active');
|
|
860
|
-
navLive.style.display='none';
|
|
861
|
-
liveEmpty.style.display='block';
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
1549
|
+
if(runIds.length===0){panel.classList.remove('active');navLive.style.display='none';liveEmpty.style.display='block';$('#liveClearBtn').style.display='none';return}
|
|
864
1550
|
|
|
865
|
-
navLive.style.display='';
|
|
866
|
-
liveEmpty.style.display='none';
|
|
867
|
-
panel.classList.add('active');
|
|
1551
|
+
navLive.style.display='';liveEmpty.style.display='none';panel.classList.add('active');
|
|
868
1552
|
|
|
869
|
-
// Aggregate stats across all runs
|
|
870
1553
|
var gTotal=0,gCompleted=0,gPassed=0,gFailed=0,gActive=0,gRunning=false,gDone=true;
|
|
871
|
-
runIds.forEach(function(rid){
|
|
872
|
-
var r=runs[rid];gTotal+=r.total;gCompleted+=r.completed;gPassed+=r.passed;gFailed+=r.failed;gActive+=r.active;
|
|
873
|
-
if(r.on)gRunning=true;if(!r.done)gDone=false;
|
|
874
|
-
});
|
|
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});
|
|
875
1555
|
|
|
876
1556
|
var badgeActive=0;
|
|
877
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++})});
|
|
@@ -879,17 +1559,13 @@ function renderLive(){
|
|
|
879
1559
|
$('#liveBadge').style.background=gRunning?'var(--purple-dim)':gFailed>0?'var(--red-dim)':'var(--green-dim)';
|
|
880
1560
|
$('#liveBadge').style.color=gRunning?'var(--purple)':gFailed>0?'var(--red)':'var(--green)';
|
|
881
1561
|
|
|
882
|
-
$('#liveTotal').textContent=gTotal;
|
|
883
|
-
$('#
|
|
884
|
-
$('#liveFail').textContent=gFailed;
|
|
885
|
-
$('#liveActive').textContent=gActive;
|
|
886
|
-
var pct=gTotal>0?gCompleted/gTotal*100:0;
|
|
887
|
-
$('#liveProgressFill').style.width=pct+'%';
|
|
888
|
-
|
|
889
|
-
// 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)+'%';
|
|
890
1564
|
$('#liveProject').style.display='none';
|
|
891
1565
|
|
|
892
|
-
|
|
1566
|
+
var hasFinished=runIds.some(function(rid){return runs[rid].done||!runs[rid].on});
|
|
1567
|
+
$('#liveClearBtn').style.display=hasFinished?'inline-block':'none';
|
|
1568
|
+
|
|
893
1569
|
var lbl=panel.querySelector('.live-header .label');
|
|
894
1570
|
var anyStale=runIds.some(function(rid){return runs[rid].stale});
|
|
895
1571
|
if(!gRunning&&gDone){
|
|
@@ -898,124 +1574,99 @@ function renderLive(){
|
|
|
898
1574
|
var dot=lbl.querySelector('.dot');if(dot)dot.remove();
|
|
899
1575
|
$('#liveProgressFill').style.background=anyStale?'var(--yellow)':gFailed>0?'var(--red)':'var(--green)';
|
|
900
1576
|
} else {
|
|
901
|
-
if(!lbl.querySelector('.dot')){
|
|
902
|
-
|
|
903
|
-
var d=el('span',{className:'dot'});lbl.appendChild(d);lbl.appendChild(document.createTextNode(' RUNNING'));
|
|
904
|
-
}
|
|
905
|
-
lbl.style.color='var(--purple)';
|
|
906
|
-
$('#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)';
|
|
907
1579
|
}
|
|
908
1580
|
|
|
909
|
-
// Render per-run sections
|
|
910
1581
|
grid.textContent='';
|
|
911
1582
|
runIds.forEach(function(rid){
|
|
912
1583
|
var L=runs[rid];
|
|
913
|
-
// Project section header
|
|
914
1584
|
var projLabel=L.project||(L.cwd?L.cwd.split('/').pop():'Run');
|
|
915
1585
|
var runStatus=L.done?(L.failed>0?'fail':'pass'):'running';
|
|
916
|
-
var
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
]);
|
|
924
|
-
grid.appendChild(sectionHeader);
|
|
1586
|
+
var dismissBtn=null;
|
|
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]),
|
|
1591
|
+
dismissBtn
|
|
1592
|
+
]));
|
|
925
1593
|
|
|
926
1594
|
var testGrid=el('div',{className:'lr-test-grid'});
|
|
927
|
-
|
|
928
|
-
names.forEach(function(name){
|
|
1595
|
+
Object.keys(L.tests).forEach(function(name){
|
|
929
1596
|
if(name==='__error')return;
|
|
930
|
-
var t=L.tests[name];
|
|
931
|
-
var testKey=rid+'::'+name;
|
|
1597
|
+
var t=L.tests[name];var testKey=rid+'::'+name;
|
|
932
1598
|
var iconText=t.status==='passed'?'\u2714':t.status==='failed'?'\u2718':'\u25CF';
|
|
933
1599
|
var iconColor=t.status==='passed'?'color:var(--green)':t.status==='failed'?'color:var(--red)':'color:var(--purple)';
|
|
934
1600
|
var meta='';
|
|
935
|
-
if(t.status==='running'){
|
|
936
|
-
|
|
937
|
-
if(t.retry)meta='Retry '+t.retry;
|
|
938
|
-
} 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'}
|
|
939
1603
|
else if(t.status==='failed'){meta=t.error||'failed'}
|
|
940
|
-
|
|
1604
|
+
|
|
941
1605
|
var stepsEl=el('div',{className:'lt-actions'});
|
|
942
1606
|
if(t.actionLog&&t.actionLog.length>0){
|
|
943
1607
|
t.actionLog.forEach(function(a){
|
|
944
|
-
var detail=a.selector||a.value||a.text||'';
|
|
1608
|
+
var detail=a.narrative||a.selector||a.value||a.text||'';
|
|
945
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)}
|
|
946
1612
|
stepsEl.appendChild(el('div',{className:'lt-step'},[
|
|
947
1613
|
el('span',{className:'step-icon '+(a.success?'ok':'fail')},a.success?'\u2714':'\u2718'),
|
|
948
1614
|
el('span',{className:'step-type'},a.type),
|
|
949
1615
|
el('span',{className:'step-detail'},detail),
|
|
1616
|
+
retryBadge,
|
|
950
1617
|
el('span',{className:'step-dur'},durText)
|
|
951
1618
|
]));
|
|
952
1619
|
});
|
|
953
|
-
if(t.status==='running'&&t.actions<t.totalActions){
|
|
954
|
-
stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'waiting...')]));
|
|
955
|
-
}
|
|
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...')]))}
|
|
956
1621
|
} else if(t.status==='running'){
|
|
957
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...')]));
|
|
958
1623
|
}
|
|
959
1624
|
var isFinished=t.status==='passed'||t.status==='failed';
|
|
960
|
-
var isCollapsed=isFinished
|
|
961
|
-
var summaryEl=el('div',{className:'lt-summary'},[
|
|
962
|
-
|
|
963
|
-
el('span',{className:'lt-expand'},isCollapsed?'\u25BC':'\u25B2')
|
|
964
|
-
]);
|
|
965
|
-
// 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
|
+
|
|
966
1628
|
var ssEl=null;
|
|
967
1629
|
var allSS=(t.screenshots||[]).slice();
|
|
968
1630
|
if(t.errorScreenshot)allSS.push(t.errorScreenshot);
|
|
969
1631
|
if(allSS.length>0){
|
|
970
1632
|
var ssOpen=S.liveSSOpen&&S.liveSSOpen.has(testKey);
|
|
971
|
-
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[
|
|
972
|
-
el('span',{className:'ss-arrow'},'\u25B6'),
|
|
973
|
-
el('span',{},'Screenshots ('+allSS.length+')')
|
|
974
|
-
]);
|
|
1633
|
+
var toggle=el('div',{className:'lt-screenshots-toggle'+(ssOpen?' open':'')},[el('span',{className:'ss-arrow'},'\u25B6'),el('span',{},'Screenshots ('+allSS.length+')')]);
|
|
975
1634
|
var ssGridEl=el('div',{className:'lt-screenshots-grid'});
|
|
976
1635
|
allSS.forEach(function(ssPath){
|
|
977
|
-
var fname=ssPath.split('/').pop();
|
|
978
|
-
var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
1636
|
+
var fname=ssPath.split('/').pop();var isErr=t.errorScreenshot&&ssPath===t.errorScreenshot;
|
|
979
1637
|
var thumb=el('div',{className:'lt-ss-thumb'});
|
|
980
|
-
var img=document.createElement('img');
|
|
981
|
-
img.src='/api/image?path='+encodeURIComponent(ssPath);
|
|
982
|
-
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';
|
|
983
1639
|
if(isErr)thumb.style.borderColor='var(--red)';
|
|
984
1640
|
thumb.appendChild(img);
|
|
985
1641
|
thumb.addEventListener('click',function(e){e.stopPropagation();openModal('/api/image?path='+encodeURIComponent(ssPath),fname)});
|
|
986
1642
|
var labelEl=el('div',{className:'lt-ss-label'},[el('span',{style:'overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},fname)]);
|
|
987
|
-
|
|
988
|
-
(function(lbl,sp){
|
|
989
|
-
ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))});
|
|
990
|
-
})(labelEl,ssPath);
|
|
1643
|
+
(function(lbl,sp){ssHash(sp).then(function(h){lbl.appendChild(createHashBadge(h))})})(labelEl,ssPath);
|
|
991
1644
|
ssGridEl.appendChild(el('div',{},[thumb,labelEl]));
|
|
992
1645
|
});
|
|
993
|
-
toggle.addEventListener('click',function(e){
|
|
994
|
-
e.stopPropagation();
|
|
995
|
-
if(S.liveSSOpen.has(testKey))S.liveSSOpen.delete(testKey);else S.liveSSOpen.add(testKey);
|
|
996
|
-
toggle.classList.toggle('open');
|
|
997
|
-
ssGridEl.style.display=ssGridEl.style.display==='grid'?'none':'grid';
|
|
998
|
-
});
|
|
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'});
|
|
999
1647
|
if(ssOpen)ssGridEl.style.display='grid';
|
|
1000
1648
|
ssEl=el('div',{className:'lt-screenshots'},[toggle,ssGridEl]);
|
|
1001
1649
|
}
|
|
1650
|
+
|
|
1651
|
+
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
1002
1652
|
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
1003
1653
|
el('div',{className:'lt-name'},[
|
|
1004
1654
|
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
1005
|
-
document.createTextNode(' '+name),
|
|
1006
|
-
summaryEl
|
|
1655
|
+
document.createTextNode(' '+name),serialBadge,summaryEl
|
|
1007
1656
|
]),
|
|
1008
|
-
el('div',{className:'lt-meta'},meta),
|
|
1009
|
-
stepsEl
|
|
1657
|
+
el('div',{className:'lt-meta'},meta),stepsEl
|
|
1010
1658
|
]);
|
|
1011
1659
|
if(ssEl)card.appendChild(ssEl);
|
|
1012
|
-
if(
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
});
|
|
1660
|
+
if(t.networkLogs&&t.networkLogs.length&&!isCollapsed){
|
|
1661
|
+
var liveErrCount=t.networkLogs.filter(function(n){return n.status>=400}).length;
|
|
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]));
|
|
1018
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()})}
|
|
1019
1670
|
testGrid.appendChild(card);
|
|
1020
1671
|
if(!isCollapsed)stepsEl.scrollTop=stepsEl.scrollHeight;
|
|
1021
1672
|
});
|
|
@@ -1023,22 +1674,194 @@ function renderLive(){
|
|
|
1023
1674
|
});
|
|
1024
1675
|
}
|
|
1025
1676
|
|
|
1026
|
-
/* ── Actions ── */
|
|
1027
1677
|
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
1028
1678
|
|
|
1029
1679
|
/* ── Modal ── */
|
|
1030
1680
|
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
1031
1681
|
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
1032
|
-
document.addEventListener('keydown',function(e){if(e.key==='Escape')$('#modal').classList.remove('open')});
|
|
1033
1682
|
|
|
1034
|
-
/*
|
|
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
|
+
══════════════════════════════════════════════════════════════════ */
|
|
1035
1856
|
connectWS();
|
|
1036
1857
|
refreshStatus();
|
|
1037
1858
|
refreshProjects();
|
|
1038
1859
|
refreshSuites();
|
|
1039
1860
|
refreshRuns();
|
|
1040
1861
|
refreshScreenshots();
|
|
1862
|
+
refreshLearnings();
|
|
1041
1863
|
})();
|
|
1864
|
+
|
|
1042
1865
|
</script>
|
|
1043
1866
|
</body>
|
|
1044
1867
|
</html>
|