@matware/e2e-runner 1.2.1 → 1.3.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/marketplace.json +52 -0
- package/.claude-plugin/plugin.json +17 -3
- package/.mcp.json +2 -2
- package/.opencode/commands/create-test.md +63 -0
- package/.opencode/commands/run.md +50 -0
- package/.opencode/commands/verify-issue.md +62 -0
- package/.opencode/skills/e2e-testing/SKILL.md +181 -0
- package/.opencode/skills/e2e-testing/references/action-types.md +143 -0
- package/.opencode/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/.opencode/skills/e2e-testing/references/graphql.md +59 -0
- package/.opencode/skills/e2e-testing/references/issue-verification.md +59 -0
- package/.opencode/skills/e2e-testing/references/multi-pool.md +60 -0
- package/.opencode/skills/e2e-testing/references/network-debugging.md +62 -0
- package/.opencode/skills/e2e-testing/references/test-json-format.md +163 -0
- package/.opencode/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/.opencode/skills/e2e-testing/references/variables.md +41 -0
- package/.opencode/skills/e2e-testing/references/visual-verification.md +89 -0
- package/LICENSE +190 -0
- package/OPENCODE.md +166 -0
- package/README.md +165 -104
- package/agents/test-creator.md +54 -1
- package/agents/test-improver.md +37 -0
- package/bin/cli.js +409 -16
- package/commands/capture.md +45 -0
- package/commands/create-test.md +16 -1
- package/opencode.json +11 -0
- package/package.json +7 -2
- package/scripts/setup-opencode.sh +113 -0
- package/skills/e2e-testing/SKILL.md +10 -3
- package/skills/e2e-testing/references/action-types.md +48 -5
- package/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/skills/e2e-testing/references/graphql.md +59 -0
- package/skills/e2e-testing/references/issue-verification.md +59 -0
- package/skills/e2e-testing/references/multi-pool.md +60 -0
- package/skills/e2e-testing/references/network-debugging.md +62 -0
- package/skills/e2e-testing/references/test-json-format.md +4 -0
- package/skills/e2e-testing/references/troubleshooting.md +44 -2
- package/skills/e2e-testing/references/variables.md +41 -0
- package/skills/e2e-testing/references/visual-verification.md +89 -0
- package/src/actions.js +475 -2
- package/src/ai-generate.js +139 -8
- package/src/app-pool.js +339 -0
- package/src/config.js +266 -5
- package/src/dashboard.js +216 -17
- package/src/db.js +191 -7
- package/src/index.js +12 -9
- package/src/learner-sqlite.js +458 -0
- package/src/learner.js +78 -6
- package/src/mcp-tools.js +1348 -51
- package/src/module-resolver.js +37 -0
- package/src/narrate.js +65 -0
- package/src/pool-manager.js +229 -0
- package/src/pool.js +301 -31
- package/src/reporter.js +86 -2
- package/src/runner.js +480 -71
- package/src/sync/auth.js +354 -0
- package/src/sync/client.js +572 -0
- package/src/sync/hub-routes.js +816 -0
- package/src/sync/index.js +68 -0
- package/src/sync/middleware.js +347 -0
- package/src/sync/queue.js +209 -0
- package/src/sync/schema.js +540 -0
- package/src/verify.js +10 -7
- package/src/visual-diff.js +446 -0
- package/src/watch.js +384 -0
- package/templates/build-dashboard.js +47 -6
- package/templates/dashboard/js/api.js +62 -0
- package/templates/dashboard/js/init.js +13 -0
- package/templates/dashboard/js/keyboard.js +46 -0
- package/templates/dashboard/js/state.js +40 -0
- package/templates/dashboard/js/toast.js +41 -0
- package/templates/dashboard/js/utils.js +216 -0
- package/templates/dashboard/js/view-live.js +181 -0
- package/templates/dashboard/js/view-runs.js +676 -0
- package/templates/dashboard/js/view-tests.js +294 -0
- package/templates/dashboard/js/view-watch.js +242 -0
- package/templates/dashboard/js/websocket.js +116 -0
- package/templates/dashboard/styles/base.css +69 -0
- package/templates/dashboard/styles/components.css +117 -0
- package/templates/dashboard/styles/view-live.css +97 -0
- package/templates/dashboard/styles/view-runs.css +243 -0
- package/templates/dashboard/styles/view-tests.css +96 -0
- package/templates/dashboard/styles/view-watch.css +53 -0
- package/templates/dashboard/template.html +181 -100
- package/templates/dashboard.html +1614 -547
- package/templates/sample-test.json +0 -8
- package/templates/dashboard/app.js +0 -1152
- package/templates/dashboard/styles.css +0 -413
package/templates/dashboard.html
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
9
9
|
<style>
|
|
10
|
+
/* ── base.css ── */
|
|
11
|
+
/* ── Reset & Variables ── */
|
|
10
12
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
11
13
|
:root{
|
|
12
14
|
--bg:#090a10;--surface:#11131b;--surface2:#181b26;--surface3:#1f2333;
|
|
@@ -51,6 +53,11 @@ a{color:var(--accent);text-decoration:none}
|
|
|
51
53
|
.pool-info{font-size:11px;color:var(--text2);line-height:1.7}
|
|
52
54
|
.pool-info strong{color:var(--text)}
|
|
53
55
|
.ws-dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
56
|
+
.pool-list{margin-top:6px;display:flex;flex-direction:column;gap:3px}
|
|
57
|
+
.pool-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--text2);font-family:var(--mono);padding:2px 0}
|
|
58
|
+
.pool-item .pool-dot{width:6px;height:6px;margin-right:0}
|
|
59
|
+
.pool-item strong{color:var(--text);font-weight:500}
|
|
60
|
+
.pool-item .pool-sessions{margin-left:auto;color:var(--text3);font-size:9px}
|
|
54
61
|
|
|
55
62
|
/* ── Main ── */
|
|
56
63
|
.main{margin-left:232px;flex:1;min-height:100vh;display:flex;flex-direction:column}
|
|
@@ -61,6 +68,18 @@ a{color:var(--accent);text-decoration:none}
|
|
|
61
68
|
.view.active{display:block}
|
|
62
69
|
#view-live.active{display:flex;flex-direction:column;flex:1;min-height:calc(100vh - 0px);padding:16px}
|
|
63
70
|
|
|
71
|
+
/* ── Responsive ── */
|
|
72
|
+
@media(max-width:768px){
|
|
73
|
+
.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}
|
|
74
|
+
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
75
|
+
.main{margin-left:60px}
|
|
76
|
+
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
77
|
+
.lr-test-grid{grid-template-columns:1fr}
|
|
78
|
+
.toast-container{right:12px;bottom:12px}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
/* ── components.css ── */
|
|
64
83
|
/* ── Buttons ── */
|
|
65
84
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;white-space:nowrap}
|
|
66
85
|
.btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
@@ -84,24 +103,6 @@ a{color:var(--accent);text-decoration:none}
|
|
|
84
103
|
.stat-val.accent{color:var(--accent)}
|
|
85
104
|
.stat-val.purple{color:var(--purple)}
|
|
86
105
|
|
|
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
|
-
|
|
105
106
|
/* ── Tables ── */
|
|
106
107
|
.tbl-wrap{overflow-x:auto}
|
|
107
108
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
@@ -110,32 +111,151 @@ td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
|
110
111
|
tbody tr{cursor:pointer;transition:background .1s}
|
|
111
112
|
tbody tr:hover td{background:var(--surface2)}
|
|
112
113
|
tbody tr.selected td{background:var(--accent-dim)}
|
|
114
|
+
|
|
115
|
+
/* ── Badges ── */
|
|
113
116
|
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
114
117
|
.badge.pass{background:var(--green-dim);color:var(--green)}
|
|
115
118
|
.badge.fail{background:var(--red-dim);color:var(--red)}
|
|
116
119
|
.badge.flaky{background:var(--amber-dim);color:var(--amber)}
|
|
117
120
|
.badge.run{background:var(--purple-dim);color:var(--purple)}
|
|
118
121
|
|
|
119
|
-
/* ──
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.chart-bar:hover{opacity:.75}
|
|
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}
|
|
124
|
-
.chart-bar:hover .tip{display:block}
|
|
122
|
+
/* ── Empty ── */
|
|
123
|
+
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
124
|
+
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
125
125
|
|
|
126
|
-
/* ──
|
|
127
|
-
.
|
|
128
|
-
.
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.
|
|
133
|
-
.
|
|
134
|
-
.
|
|
135
|
-
.
|
|
136
|
-
.
|
|
137
|
-
.
|
|
126
|
+
/* ── Modal ── */
|
|
127
|
+
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
128
|
+
.modal.open{display:flex}
|
|
129
|
+
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
130
|
+
|
|
131
|
+
/* ── Toast Notifications ── */
|
|
132
|
+
.toast-container{position:fixed;bottom:24px;right:24px;z-index:300;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}
|
|
133
|
+
.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}
|
|
134
|
+
.toast.success{background:var(--green);border:1px solid rgba(255,255,255,.15)}
|
|
135
|
+
.toast.error{background:var(--red);border:1px solid rgba(255,255,255,.15)}
|
|
136
|
+
.toast.info{background:var(--accent);border:1px solid rgba(255,255,255,.15)}
|
|
137
|
+
.toast.fade-out{animation:toastOut .3s ease forwards}
|
|
138
|
+
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}
|
|
139
|
+
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(24px)}}
|
|
140
|
+
.toast.clickable{cursor:pointer}
|
|
141
|
+
.toast.clickable:hover{filter:brightness(1.1)}
|
|
142
|
+
|
|
143
|
+
/* ── Copy Button ── */
|
|
144
|
+
.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}
|
|
145
|
+
.copy-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-dim)}
|
|
146
|
+
.copy-btn.copied{color:var(--green);border-color:var(--green);background:var(--green-dim)}
|
|
147
|
+
|
|
148
|
+
/* ── Screenshot Hash Badge ── */
|
|
149
|
+
.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}
|
|
150
|
+
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
151
|
+
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
152
|
+
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
138
153
|
|
|
154
|
+
/* ── Trigger Source Badges ── */
|
|
155
|
+
.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}
|
|
156
|
+
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
157
|
+
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
158
|
+
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
159
|
+
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
160
|
+
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
161
|
+
|
|
162
|
+
/* ── Driver Badges ── */
|
|
163
|
+
.driver-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}
|
|
164
|
+
.driver-badge.drv-browserless{background:var(--accent-dim)}
|
|
165
|
+
.driver-badge.drv-cdp{background:var(--purple-dim)}
|
|
166
|
+
.driver-badge.drv-steel{background:var(--amber-dim)}
|
|
167
|
+
.driver-badge .drv-icon{font-size:11px;line-height:1}
|
|
168
|
+
|
|
169
|
+
/* ── Filter Bar ── */
|
|
170
|
+
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
171
|
+
.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}
|
|
172
|
+
.filter-btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
173
|
+
.filter-btn.active{background:var(--accent-dim);border-color:var(--accent);color:var(--accent)}
|
|
174
|
+
.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}
|
|
175
|
+
.filter-bar input:focus{outline:none;border-color:var(--accent)}
|
|
176
|
+
.filter-bar input::placeholder{color:var(--text3)}
|
|
177
|
+
|
|
178
|
+
/* ── Inner Tabs ── */
|
|
179
|
+
.tab-bar{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px}
|
|
180
|
+
.tab-btn{padding:8px 16px;font-family:var(--mono);font-size:11px;font-weight:500;color:var(--text3);cursor:pointer;border:none;background:transparent;border-bottom:2px solid transparent;transition:all .15s}
|
|
181
|
+
.tab-btn:hover{color:var(--text2);background:var(--surface2)}
|
|
182
|
+
.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
183
|
+
.tab-pane{display:none}
|
|
184
|
+
.tab-pane.active{display:block}
|
|
185
|
+
|
|
186
|
+
/* ── Keyboard Shortcuts Modal ── */
|
|
187
|
+
.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}
|
|
188
|
+
.kb-modal.open{display:flex}
|
|
189
|
+
.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}
|
|
190
|
+
.kb-modal-content h2{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
|
191
|
+
.kb-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
192
|
+
.kb-row:last-child{border-bottom:none}
|
|
193
|
+
.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)}
|
|
194
|
+
.kb-desc{font-size:12px;color:var(--text2)}
|
|
195
|
+
|
|
196
|
+
/* ── Serial / Pool Badges ── */
|
|
197
|
+
.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}
|
|
198
|
+
.pool-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:8px;font-size:9px;font-weight:600;background:rgba(99,102,241,.15);color:#818cf8;vertical-align:middle;margin-left:4px;font-family:var(--mono);letter-spacing:-.3px}
|
|
199
|
+
.pool-badge::before{content:'\1F517';font-size:8px}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
/* ── view-watch.css ── */
|
|
203
|
+
/* ── Watch View: Project Cards ── */
|
|
204
|
+
.watch-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-bottom:24px}
|
|
205
|
+
.watch-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;transition:border-color .2s,box-shadow .2s}
|
|
206
|
+
.watch-card:hover{border-color:var(--border-hi);box-shadow:0 2px 12px rgba(0,0,0,.25)}
|
|
207
|
+
.watch-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
|
208
|
+
.watch-card-name{font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
209
|
+
.watch-card-icons{display:flex;gap:6px;flex-shrink:0;margin-left:8px}
|
|
210
|
+
.watch-card-icons .btn{padding:3px 8px;font-size:10px}
|
|
211
|
+
|
|
212
|
+
/* ── Sparkline ── */
|
|
213
|
+
.watch-sparkline{height:40px;margin-bottom:10px}
|
|
214
|
+
.watch-sparkline svg{width:100%;height:100%;display:block}
|
|
215
|
+
|
|
216
|
+
/* ── Card Footer ── */
|
|
217
|
+
.watch-card-footer{display:flex;align-items:center;justify-content:space-between;font-size:11px}
|
|
218
|
+
.watch-card-status{display:flex;align-items:center;gap:6px}
|
|
219
|
+
.watch-card-status .status-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
220
|
+
.watch-card-status .status-dot.green{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
221
|
+
.watch-card-status .status-dot.red{background:var(--red);box-shadow:0 0 6px var(--red)}
|
|
222
|
+
.watch-card-status .status-dot.amber{background:var(--amber);box-shadow:0 0 6px var(--amber)}
|
|
223
|
+
.watch-card-status .status-dot.dim{background:var(--text3)}
|
|
224
|
+
.watch-card-rate{font-weight:600}
|
|
225
|
+
.watch-card-rate.green{color:var(--green)}
|
|
226
|
+
.watch-card-rate.amber{color:var(--amber)}
|
|
227
|
+
.watch-card-rate.red{color:var(--red)}
|
|
228
|
+
|
|
229
|
+
.watch-card-meta{display:flex;flex-direction:column;gap:4px;margin-top:10px;font-size:10px;color:var(--text3)}
|
|
230
|
+
.watch-card-meta span{display:flex;align-items:center;gap:6px}
|
|
231
|
+
.watch-card-countdown{color:var(--accent);font-weight:500;font-variant-numeric:tabular-nums}
|
|
232
|
+
.watch-card-commit{font-family:var(--mono);color:var(--text3)}
|
|
233
|
+
|
|
234
|
+
/* ── Event Log ── */
|
|
235
|
+
.watch-event-log{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden}
|
|
236
|
+
.watch-event-log-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
237
|
+
.watch-event-log-header .title{font-family:var(--sans);font-size:13px;font-weight:600}
|
|
238
|
+
.watch-event-log-body{max-height:400px;overflow-y:auto}
|
|
239
|
+
.watch-event-row{display:grid;grid-template-columns:120px 130px minmax(80px,1fr) 46px 74px 40px 52px 60px;align-items:center;gap:0;padding:7px 16px;border-bottom:1px solid var(--border);font-size:11px;transition:background .1s}
|
|
240
|
+
.watch-event-row:last-child{border-bottom:none}
|
|
241
|
+
.watch-event-row:hover{background:var(--surface2)}
|
|
242
|
+
.watch-event-row.we-header{font-size:9px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.5px;padding:6px 16px;background:var(--surface2);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:1}
|
|
243
|
+
.watch-event-row.we-header:hover{background:var(--surface2)}
|
|
244
|
+
.watch-event-time{color:var(--text3);font-variant-numeric:tabular-nums;font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
245
|
+
.watch-event-project{color:var(--text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
246
|
+
.watch-event-suite{color:var(--accent);font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
247
|
+
.watch-event-result{justify-self:center}
|
|
248
|
+
.watch-event-counts{font-family:var(--mono);font-size:10px;color:var(--text2);white-space:nowrap;text-align:center}
|
|
249
|
+
.watch-event-counts .we-counts-ok{color:var(--green)}
|
|
250
|
+
.watch-event-rate{font-weight:600;color:var(--text2);font-variant-numeric:tabular-nums;text-align:right}
|
|
251
|
+
.watch-event-duration{color:var(--text3);font-family:var(--mono);font-size:10px;text-align:right}
|
|
252
|
+
.we-trigger{color:var(--text3);font-size:9px;padding:1px 6px;border-radius:8px;background:var(--surface3);white-space:nowrap;text-align:center;justify-self:end}
|
|
253
|
+
|
|
254
|
+
/* ── Watch Table (legacy) ── */
|
|
255
|
+
.watch-jobs-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
/* ── view-tests.css ── */
|
|
139
259
|
/* ── Suite Cards ── */
|
|
140
260
|
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;padding:16px}
|
|
141
261
|
.suite-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;transition:border-color .2s,box-shadow .2s}
|
|
@@ -198,94 +318,49 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
198
318
|
.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
319
|
.suite-modal-expect-label{font-weight:600;flex-shrink:0}
|
|
200
320
|
|
|
201
|
-
/* ──
|
|
202
|
-
.
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
.
|
|
206
|
-
.
|
|
207
|
-
.
|
|
208
|
-
|
|
209
|
-
.
|
|
210
|
-
.
|
|
211
|
-
.
|
|
212
|
-
.
|
|
213
|
-
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
214
|
-
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;min-height:0}
|
|
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)}
|
|
216
|
-
.live-test.running{border-left-color:var(--purple)}
|
|
217
|
-
.live-test.passed{border-left-color:var(--green)}
|
|
218
|
-
.live-test.failed{border-left-color:var(--red)}
|
|
219
|
-
.live-test.collapsed{cursor:pointer;padding:6px 12px}
|
|
220
|
-
.live-test.collapsed:hover{background:var(--surface3)}
|
|
221
|
-
.live-test.collapsed .lt-meta,.live-test.collapsed .lt-actions,.live-test.collapsed .lt-screenshots{display:none}
|
|
222
|
-
.live-test.collapsed .lt-name{margin-bottom:0}
|
|
223
|
-
.live-test.collapsed .lt-summary{display:flex}
|
|
224
|
-
.live-test .lt-name{font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
|
|
225
|
-
.live-test .lt-summary{display:none;align-items:center;gap:8px;margin-left:auto;font-size:10px;color:var(--text3);font-family:var(--mono)}
|
|
226
|
-
.live-test .lt-summary .lt-dur{color:var(--text2)}
|
|
227
|
-
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
228
|
-
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
229
|
-
.live-test .lt-icon{font-size:12px}
|
|
230
|
-
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
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}
|
|
232
|
-
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
233
|
-
.lt-step .step-icon.ok{color:var(--green)}
|
|
234
|
-
.lt-step .step-icon.fail{color:var(--red)}
|
|
235
|
-
.lt-step .step-icon.run{color:var(--purple)}
|
|
236
|
-
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
237
|
-
.lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
|
|
238
|
-
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
239
|
-
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
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}
|
|
241
|
-
.lt-screenshots-toggle:hover{color:var(--text)}
|
|
242
|
-
.lt-screenshots-toggle .ss-arrow{transition:transform .2s;font-size:8px}
|
|
243
|
-
.lt-screenshots-toggle.open .ss-arrow{transform:rotate(90deg)}
|
|
244
|
-
.lt-screenshots-grid{display:none;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:6px;padding-top:6px}
|
|
245
|
-
.lt-screenshots-toggle.open+.lt-screenshots-grid{display:grid}
|
|
246
|
-
.lt-ss-thumb{position:relative;border-radius:4px;overflow:hidden;border:1px solid var(--border);cursor:pointer;aspect-ratio:16/10;background:var(--surface2)}
|
|
247
|
-
.lt-ss-thumb img{width:100%;height:100%;object-fit:cover;display:block}
|
|
248
|
-
.lt-ss-thumb:hover{border-color:var(--purple);box-shadow:0 0 0 1px var(--purple)}
|
|
249
|
-
.lt-ss-label{font-size:8px;color:var(--text3);font-family:var(--mono);text-align:center;padding:2px 2px 0;display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap}
|
|
250
|
-
.lr-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;margin:6px 0 2px;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;border-left:3px solid var(--purple)}
|
|
251
|
-
.lr-section-header.running{border-color:var(--purple);background:rgba(139,92,246,.08);color:var(--purple)}
|
|
252
|
-
.lr-section-header.pass{border-color:var(--green);background:rgba(52,211,153,.08);color:var(--green)}
|
|
253
|
-
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
254
|
-
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
255
|
-
.lr-project-name{letter-spacing:.5px}
|
|
256
|
-
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
|
|
257
|
-
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
258
|
-
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
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}
|
|
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)}
|
|
321
|
+
/* ── Project Accordion ── */
|
|
322
|
+
.project-accordion{margin-bottom:2px}
|
|
323
|
+
.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}
|
|
324
|
+
.project-accordion-header:hover{background:var(--surface2);border-color:var(--border-hi)}
|
|
325
|
+
.project-accordion.open>.project-accordion-header{border-radius:var(--r) var(--r) 0 0;border-bottom-color:transparent;background:var(--surface2)}
|
|
326
|
+
.project-accordion-chevron{font-size:10px;color:var(--text3);transition:transform .2s ease;flex-shrink:0;width:16px;text-align:center}
|
|
327
|
+
.project-accordion.open>.project-accordion-header .project-accordion-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
328
|
+
.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}
|
|
329
|
+
.project-accordion-meta{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
330
|
+
.project-accordion-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--surface3);color:var(--text2)}
|
|
331
|
+
.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)}
|
|
332
|
+
.project-accordion.open>.project-accordion-body{max-height:5000px}
|
|
265
333
|
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
.
|
|
269
|
-
|
|
334
|
+
/* ── Module Cards ── */
|
|
335
|
+
.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}
|
|
336
|
+
.module-section-title .mod-icon{color:var(--purple)}
|
|
337
|
+
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
|
338
|
+
.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}
|
|
339
|
+
.module-card:hover{border-color:var(--border-hi);border-left-color:var(--purple)}
|
|
340
|
+
.module-card-name{font-weight:600;font-size:13px;color:var(--purple);margin-bottom:4px}
|
|
341
|
+
.module-card-desc{font-size:11px;color:var(--text2);margin-bottom:8px}
|
|
342
|
+
.module-card-meta{font-size:10px;color:var(--text3);display:flex;gap:12px}
|
|
343
|
+
.module-card-params{list-style:none;font-size:10px;color:var(--text2);margin-top:6px}
|
|
344
|
+
.module-card-params li{padding:2px 0}
|
|
345
|
+
.module-card-params li::before{content:'$';color:var(--purple);margin-right:4px}
|
|
346
|
+
|
|
347
|
+
/* ── Variables ── */
|
|
348
|
+
.var-table{width:100%;border-collapse:collapse;font-size:12px}
|
|
349
|
+
.var-table th{text-align:left;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
350
|
+
.var-table td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
351
|
+
.var-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:11px}
|
|
352
|
+
.var-add-form{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:16px}
|
|
353
|
+
.var-add-form input,.var-add-form select{padding:6px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
354
|
+
.var-add-form input:focus,.var-add-form select:focus{outline:none;border-color:var(--accent)}
|
|
270
355
|
|
|
271
|
-
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
.
|
|
275
|
-
.
|
|
276
|
-
.
|
|
277
|
-
.
|
|
278
|
-
.
|
|
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}
|
|
356
|
+
|
|
357
|
+
/* ── view-runs.css ── */
|
|
358
|
+
/* ── Trend Chart ── */
|
|
359
|
+
.chart{display:flex;align-items:flex-end;gap:3px;height:60px}
|
|
360
|
+
.chart-bar{flex:1;min-width:3px;max-width:16px;border-radius:2px 2px 0 0;cursor:pointer;position:relative;transition:opacity .15s}
|
|
361
|
+
.chart-bar:hover{opacity:.75}
|
|
362
|
+
.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}
|
|
363
|
+
.chart-bar:hover .tip{display:block}
|
|
289
364
|
|
|
290
365
|
/* ── Inline Run Detail ── */
|
|
291
366
|
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--purple)}
|
|
@@ -312,6 +387,8 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
312
387
|
.rd-test-body{padding:16px}
|
|
313
388
|
.rd-retries{font-size:11px;color:var(--amber);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
314
389
|
.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}
|
|
390
|
+
.rd-error-msg .copy-btn{position:absolute;top:8px;right:8px;opacity:0}
|
|
391
|
+
.rd-error-msg:hover .copy-btn{opacity:1}
|
|
315
392
|
.rd-shots{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
316
393
|
.rd-shot{width:140px;border-radius:6px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--surface2)}
|
|
317
394
|
.rd-shot:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.35)}
|
|
@@ -326,6 +403,8 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
326
403
|
.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}
|
|
327
404
|
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
328
405
|
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
406
|
+
|
|
407
|
+
/* ── Network Panel ── */
|
|
329
408
|
.rd-net-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
330
409
|
.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
410
|
.rd-net-head:hover{background:var(--surface3)}
|
|
@@ -359,10 +438,19 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
359
438
|
.rd-net-status.s2xx{color:var(--green)}
|
|
360
439
|
.rd-net-status.s3xx{color:var(--amber)}
|
|
361
440
|
.rd-net-status.s4xx,.rd-net-status.s5xx{color:var(--red)}
|
|
441
|
+
.rd-net-op{flex-shrink:0;padding:1px 7px;border-radius:3px;font-size:9px;font-weight:600;background:var(--purple-dim,rgba(168,85,247,.12));color:var(--purple,#a855f7);font-family:var(--sans);letter-spacing:.01em;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
362
442
|
.rd-net-url{flex:1;min-width:0;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
363
443
|
.rd-net-dur{width:60px;flex-shrink:0;text-align:right;color:var(--text3);font-variant-numeric:tabular-nums}
|
|
364
444
|
.rd-net-detail{display:none;border-bottom:1px solid var(--border);background:var(--bg);overflow:hidden}
|
|
365
445
|
.rd-net-row.open+.rd-net-detail{display:block}
|
|
446
|
+
.rd-net-row .copy-btn{opacity:0;padding:1px 6px}
|
|
447
|
+
.rd-net-row:hover .copy-btn{opacity:1}
|
|
448
|
+
.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}
|
|
449
|
+
.rd-net-body .rd-log-item:last-child{border-bottom:none}
|
|
450
|
+
.rd-net-body .rd-log-item.error{border-left-color:var(--red)}
|
|
451
|
+
.rd-net-body .rd-log-item.warn,.rd-net-body .rd-log-item.warning{border-left-color:var(--amber)}
|
|
452
|
+
|
|
453
|
+
/* ── Network Detail Sections ── */
|
|
366
454
|
.rd-nd-section{border-bottom:1px solid var(--border)}
|
|
367
455
|
.rd-nd-section:last-child{border-bottom:none}
|
|
368
456
|
.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}
|
|
@@ -379,107 +467,237 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
379
467
|
.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
468
|
.rd-hdr-val{padding:3px 0;color:var(--text2);border-bottom:1px solid var(--border);word-break:break-all}
|
|
381
469
|
.rd-nd-empty{color:var(--text3);font-size:11px;padding:4px 0;font-style:italic}
|
|
382
|
-
|
|
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)}
|
|
470
|
+
|
|
393
471
|
tr.expanded td{background:var(--surface2)!important}
|
|
394
472
|
tr.expanded td:first-child{position:relative}
|
|
395
473
|
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
396
474
|
|
|
397
|
-
/* ──
|
|
398
|
-
.
|
|
399
|
-
.
|
|
400
|
-
.
|
|
401
|
-
.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.
|
|
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
|
-
|
|
411
|
-
/* ── Empty ── */
|
|
412
|
-
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
413
|
-
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
414
|
-
|
|
415
|
-
/* ── Modal ── */
|
|
416
|
-
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
417
|
-
.modal.open{display:flex}
|
|
418
|
-
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
475
|
+
/* ── Actions Panel in Run Detail ── */
|
|
476
|
+
.rd-actions-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
477
|
+
.rd-actions-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
478
|
+
.rd-actions-head:hover{background:var(--surface3)}
|
|
479
|
+
.rd-actions-head .act-arrow{font-size:9px;color:var(--text3);transition:transform .2s}
|
|
480
|
+
.rd-actions-head.open .act-arrow{transform:rotate(90deg)}
|
|
481
|
+
.rd-actions-body{display:none;max-height:500px;overflow-y:auto;padding:8px 14px}
|
|
482
|
+
.rd-actions-head.open~.rd-actions-body{display:block}
|
|
419
483
|
|
|
420
|
-
/* ──
|
|
421
|
-
.
|
|
422
|
-
.
|
|
423
|
-
.
|
|
424
|
-
.
|
|
425
|
-
.
|
|
426
|
-
.
|
|
427
|
-
|
|
428
|
-
|
|
484
|
+
/* ── Run Detail Insights ── */
|
|
485
|
+
.rd-insights{margin-bottom:16px;display:flex;flex-direction:column;gap:6px}
|
|
486
|
+
.rd-insights:empty{display:none}
|
|
487
|
+
.rd-ins-health{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:12px}
|
|
488
|
+
.rd-ins-rate{font-size:18px;font-weight:700}
|
|
489
|
+
.rd-ins-rate.green{color:var(--green)}
|
|
490
|
+
.rd-ins-rate.amber{color:var(--amber)}
|
|
491
|
+
.rd-ins-rate.red{color:var(--red)}
|
|
492
|
+
.rd-ins-trend{font-size:11px;color:var(--text2)}
|
|
493
|
+
.rd-ins-trend.green{color:var(--green)}
|
|
494
|
+
.rd-ins-trend.red{color:var(--red)}
|
|
495
|
+
.rd-ins-tag{padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
496
|
+
.rd-ins-tag.amber{background:var(--amber-dim);color:var(--amber)}
|
|
497
|
+
.rd-ins-tag.red{background:var(--red-dim);color:var(--red)}
|
|
498
|
+
.rd-ins-item{display:flex;align-items:flex-start;gap:8px;padding:6px 12px;border-radius:var(--r);font-size:11px;color:var(--text2);border-left:3px solid var(--border)}
|
|
499
|
+
.rd-ins-item.red{border-left-color:var(--red);background:var(--red-dim)}
|
|
500
|
+
.rd-ins-item.green{border-left-color:var(--green);background:var(--green-dim)}
|
|
501
|
+
.rd-ins-item.amber{border-left-color:var(--amber);background:var(--amber-dim)}
|
|
502
|
+
.rd-ins-icon{font-size:12px;flex-shrink:0;width:14px;text-align:center}
|
|
503
|
+
|
|
504
|
+
/* ── Health Banner ── */
|
|
505
|
+
.health-banner{display:flex;align-items:center;gap:1px;background:var(--border);border-radius:var(--r);overflow:hidden;margin-bottom:20px}
|
|
506
|
+
.health-banner:empty{display:none}
|
|
507
|
+
.hb-item{flex:1;background:var(--surface);padding:12px 16px;text-align:center;min-width:0;transition:background .15s}
|
|
508
|
+
.hb-item:hover{background:var(--surface2)}
|
|
509
|
+
.hb-val{font-size:20px;font-weight:700;margin-bottom:2px}
|
|
510
|
+
.hb-val.green{color:var(--green)}
|
|
511
|
+
.hb-val.amber{color:var(--amber)}
|
|
512
|
+
.hb-val.red{color:var(--red)}
|
|
513
|
+
.hb-val.accent{color:var(--accent)}
|
|
514
|
+
.hb-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
515
|
+
.hb-trend{font-size:10px;margin-top:2px}
|
|
516
|
+
.hb-trend.green{color:var(--green)}
|
|
517
|
+
.hb-trend.red{color:var(--red)}
|
|
518
|
+
.hb-trend.dim{color:var(--text3)}
|
|
519
|
+
.hb-link{background:var(--surface);padding:12px 16px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s;min-width:100px}
|
|
520
|
+
.hb-link:hover{background:var(--accent-dim)}
|
|
521
|
+
.hb-link span{font-size:11px;color:var(--accent);font-weight:600}
|
|
429
522
|
|
|
430
|
-
/* ──
|
|
431
|
-
.
|
|
432
|
-
.
|
|
433
|
-
.
|
|
434
|
-
.
|
|
435
|
-
.
|
|
436
|
-
.
|
|
437
|
-
.
|
|
523
|
+
/* ── Screenshots ── */
|
|
524
|
+
.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
|
525
|
+
.gallery-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:border-color .15s}
|
|
526
|
+
.gallery-item:hover{border-color:var(--accent)}
|
|
527
|
+
.gallery-item img{width:100%;height:150px;object-fit:cover;display:block}
|
|
528
|
+
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
529
|
+
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
530
|
+
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
531
|
+
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
532
|
+
.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}
|
|
533
|
+
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
534
|
+
.ss-search input::placeholder{color:var(--text3)}
|
|
535
|
+
.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}
|
|
536
|
+
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
537
|
+
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
538
|
+
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
539
|
+
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
540
|
+
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
438
541
|
|
|
439
|
-
/* ──
|
|
440
|
-
.
|
|
441
|
-
.
|
|
442
|
-
.
|
|
443
|
-
.
|
|
444
|
-
.
|
|
445
|
-
.
|
|
446
|
-
.
|
|
447
|
-
.
|
|
448
|
-
.
|
|
449
|
-
.
|
|
450
|
-
.
|
|
542
|
+
/* ── Learnings ── */
|
|
543
|
+
.learn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;margin-bottom:20px}
|
|
544
|
+
.learn-stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px;text-align:center}
|
|
545
|
+
.learn-stat-val{font-size:24px;font-weight:700;margin-bottom:2px}
|
|
546
|
+
.learn-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
547
|
+
.learn-section{margin-bottom:20px}
|
|
548
|
+
.learn-section-title{font-family:var(--sans);font-size:13px;font-weight:600;margin-bottom:10px;color:var(--text)}
|
|
549
|
+
.learn-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
550
|
+
.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}
|
|
551
|
+
.learn-table th:hover{color:var(--text2)}
|
|
552
|
+
.learn-table th.sorted::after{content:' \25B2';font-size:8px}
|
|
553
|
+
.learn-table th.sorted.desc::after{content:' \25BC'}
|
|
554
|
+
.learn-table td{padding:6px 10px;border-bottom:1px solid var(--border);color:var(--text2)}
|
|
555
|
+
.learn-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:10px;color:var(--text)}
|
|
556
|
+
.learn-table tbody tr:hover td{background:var(--surface2);color:var(--text)}
|
|
557
|
+
.learn-trend-chart{width:100%;height:100px;margin-bottom:20px}
|
|
558
|
+
.learn-trend-chart svg{width:100%;height:100%}
|
|
451
559
|
|
|
452
|
-
/* ──
|
|
453
|
-
.
|
|
454
|
-
.
|
|
455
|
-
.
|
|
456
|
-
.
|
|
457
|
-
.
|
|
458
|
-
.
|
|
459
|
-
.
|
|
460
|
-
.
|
|
560
|
+
/* ── Learnings Dashboard (visual cards) ── */
|
|
561
|
+
.learn-hero{display:flex;align-items:center;gap:24px;margin-bottom:20px;padding:20px 24px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
562
|
+
.learn-hero-ring{position:relative;width:100px;height:100px;flex-shrink:0}
|
|
563
|
+
.learn-hero-ring svg{width:100%;height:100%;transform:rotate(-90deg)}
|
|
564
|
+
.learn-hero-ring-bg{fill:none;stroke:var(--surface3);stroke-width:8}
|
|
565
|
+
.learn-hero-ring-fg{fill:none;stroke-width:8;stroke-linecap:round;transition:stroke-dashoffset .6s ease}
|
|
566
|
+
.learn-hero-pct{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;font-family:var(--mono)}
|
|
567
|
+
.learn-hero-stats{flex:1;display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
|
|
568
|
+
.learn-hero-stat{text-align:center}
|
|
569
|
+
.learn-hero-stat-val{font-size:18px;font-weight:700;font-family:var(--mono)}
|
|
570
|
+
.learn-hero-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.08em;margin-top:2px}
|
|
571
|
+
|
|
572
|
+
.learn-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
|
|
573
|
+
@media(max-width:900px){.learn-cols{grid-template-columns:1fr}}
|
|
574
|
+
|
|
575
|
+
.learn-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px 16px}
|
|
576
|
+
.learn-card-title{font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
577
|
+
.learn-card-title .lc-icon{font-size:13px}
|
|
578
|
+
.learn-card-empty{font-size:11px;color:var(--text3);font-style:italic}
|
|
579
|
+
|
|
580
|
+
.learn-item{display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
581
|
+
.learn-item:last-child{border-bottom:none}
|
|
582
|
+
.learn-item-bar{flex:1;min-width:0}
|
|
583
|
+
.learn-item-label{font-size:11px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px}
|
|
584
|
+
.learn-item-label code{background:var(--surface3);padding:1px 4px;border-radius:3px;font-size:10px}
|
|
585
|
+
.learn-item-sub{font-size:9px;color:var(--text3)}
|
|
586
|
+
.learn-item-val{font-size:13px;font-weight:700;font-family:var(--mono);flex-shrink:0;min-width:44px;text-align:right}
|
|
587
|
+
|
|
588
|
+
.learn-bar{height:4px;border-radius:2px;background:var(--surface3);overflow:hidden;margin-top:3px}
|
|
589
|
+
.learn-bar-fill{height:100%;border-radius:2px;transition:width .4s ease}
|
|
590
|
+
|
|
591
|
+
.learn-verdict{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
592
|
+
.learn-verdict.good{background:var(--green-dim);color:var(--green)}
|
|
593
|
+
.learn-verdict.warn{background:var(--amber-dim);color:var(--amber)}
|
|
594
|
+
.learn-verdict.bad{background:var(--red-dim);color:var(--red)}
|
|
595
|
+
|
|
596
|
+
/* ── Pool Distribution ── */
|
|
597
|
+
.pool-dist{display:flex;align-items:stretch;gap:0;border-radius:6px;overflow:hidden;height:22px;margin:8px 0;font-size:10px;font-weight:600;font-family:var(--mono)}
|
|
598
|
+
.pool-dist-seg{display:flex;align-items:center;justify-content:center;gap:4px;padding:0 8px;color:#fff;white-space:nowrap;min-width:40px;transition:flex .3s}
|
|
599
|
+
.pool-dist-legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin:4px 0 8px;font-size:10px;font-family:var(--mono);color:var(--text2)}
|
|
600
|
+
.pool-dist-legend span::before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;vertical-align:middle}
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
/* ── view-live.css ── */
|
|
604
|
+
/* ── Live Execution ── */
|
|
605
|
+
.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}
|
|
606
|
+
.live-panel.active{display:flex;flex:1;min-height:0}
|
|
607
|
+
@keyframes fadeSlide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
|
608
|
+
.live-header{padding:14px 16px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--border);background:var(--purple-dim)}
|
|
609
|
+
.live-header .label{font-weight:600;color:var(--purple);font-size:12px;display:flex;align-items:center;gap:8px}
|
|
610
|
+
.live-header .label .dot{width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
611
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
612
|
+
.live-project{display:flex;align-items:center;padding:2px 10px;background:rgba(255,255,255,.05);border-radius:4px;border:1px solid var(--border)}
|
|
613
|
+
.live-stats{display:flex;gap:16px;font-size:11px;color:var(--text2);margin-left:auto}
|
|
614
|
+
.live-stats span strong{color:var(--text)}
|
|
615
|
+
.live-progress{height:3px;background:var(--surface3)}
|
|
616
|
+
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
617
|
+
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;min-height:0;flex:1}
|
|
618
|
+
.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)}
|
|
619
|
+
.live-test.running{border-left-color:var(--purple)}
|
|
620
|
+
.live-test.passed{border-left-color:var(--green)}
|
|
621
|
+
.live-test.failed{border-left-color:var(--red)}
|
|
622
|
+
.live-test.collapsed{cursor:pointer;padding:6px 12px}
|
|
623
|
+
.live-test.collapsed:hover{background:var(--surface3)}
|
|
624
|
+
.live-test.collapsed .lt-meta,.live-test.collapsed .lt-actions,.live-test.collapsed .lt-screenshots{display:none}
|
|
625
|
+
.live-test.collapsed .lt-name{margin-bottom:0}
|
|
626
|
+
.live-test.collapsed .lt-summary{display:flex}
|
|
627
|
+
.live-test .lt-name{font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
|
|
628
|
+
.live-test .lt-summary{display:none;align-items:center;gap:8px;margin-left:auto;font-size:10px;color:var(--text3);font-family:var(--mono)}
|
|
629
|
+
.live-test .lt-summary .lt-dur{color:var(--text2)}
|
|
630
|
+
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
631
|
+
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
632
|
+
.live-test .lt-icon{font-size:12px}
|
|
633
|
+
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
634
|
+
.lt-step{display:flex;align-items:flex-start;gap:6px;padding:2px 0;font-size:10px;font-family:var(--mono);line-height:1.4}
|
|
635
|
+
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
636
|
+
.lt-step .step-icon.ok{color:var(--green)}
|
|
637
|
+
.lt-step .step-icon.fail{color:var(--red)}
|
|
638
|
+
.lt-step .step-icon.run{color:var(--purple)}
|
|
639
|
+
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
640
|
+
.lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
|
|
641
|
+
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
642
|
+
.lt-step.pool-log{background:rgba(99,102,241,.06);border-left:2px solid rgba(99,102,241,.3);padding-left:8px}
|
|
643
|
+
.lt-step.pool-log .step-icon{color:#818cf8}
|
|
644
|
+
.lt-step.pool-log .step-type{color:#818cf8;font-weight:600}
|
|
645
|
+
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
646
|
+
.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}
|
|
647
|
+
.lt-screenshots-toggle:hover{color:var(--text)}
|
|
648
|
+
.lt-screenshots-toggle .ss-arrow{transition:transform .2s;font-size:8px}
|
|
649
|
+
.lt-screenshots-toggle.open .ss-arrow{transform:rotate(90deg)}
|
|
650
|
+
.lt-screenshots-grid{display:none;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:6px;padding-top:6px}
|
|
651
|
+
.lt-screenshots-toggle.open+.lt-screenshots-grid{display:grid}
|
|
652
|
+
.lt-ss-thumb{position:relative;border-radius:4px;overflow:hidden;border:1px solid var(--border);cursor:pointer;aspect-ratio:16/10;background:var(--surface2)}
|
|
653
|
+
.lt-ss-thumb img{width:100%;height:100%;object-fit:cover;display:block}
|
|
654
|
+
.lt-ss-thumb:hover{border-color:var(--purple);box-shadow:0 0 0 1px var(--purple)}
|
|
655
|
+
.lt-ss-label{font-size:8px;color:var(--text3);font-family:var(--mono);text-align:center;padding:2px 2px 0;display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap}
|
|
461
656
|
|
|
462
|
-
/* ──
|
|
463
|
-
.
|
|
464
|
-
.
|
|
465
|
-
.
|
|
466
|
-
.
|
|
467
|
-
.
|
|
468
|
-
.
|
|
469
|
-
.
|
|
657
|
+
/* ── Live Run Sections ── */
|
|
658
|
+
.lr-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;margin:6px 0 2px;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;border-left:3px solid var(--purple)}
|
|
659
|
+
.lr-section-header.running{border-color:var(--purple);background:rgba(139,92,246,.08);color:var(--purple)}
|
|
660
|
+
.lr-section-header.pass{border-color:var(--green);background:rgba(52,211,153,.08);color:var(--green)}
|
|
661
|
+
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
662
|
+
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
663
|
+
.lr-project-name{letter-spacing:.5px}
|
|
664
|
+
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
|
|
665
|
+
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
666
|
+
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
667
|
+
.live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
|
|
668
|
+
.live-close:hover{color:var(--text);border-color:var(--border-hi)}
|
|
669
|
+
.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}
|
|
670
|
+
.live-clear-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface3)}
|
|
671
|
+
.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}
|
|
672
|
+
.lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
|
|
470
673
|
|
|
471
|
-
/* ──
|
|
472
|
-
.
|
|
674
|
+
/* ── Screencast Panel ── */
|
|
675
|
+
.live-body{display:flex;flex:1;min-height:0;overflow:hidden}
|
|
676
|
+
.live-body .live-tests{flex:1;min-width:0}
|
|
677
|
+
.screencast-panel{width:420px;flex-shrink:0;display:flex;flex-direction:column;border-left:1px solid var(--border);background:var(--surface2)}
|
|
678
|
+
.screencast-header{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border);background:var(--surface3)}
|
|
679
|
+
.screencast-label{font-size:11px;font-weight:600;color:var(--purple);white-space:nowrap}
|
|
680
|
+
.screencast-select{flex:1;padding:4px 8px;font-size:10px;font-family:var(--mono);background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);outline:none;cursor:pointer}
|
|
681
|
+
.screencast-select:focus{border-color:var(--purple)}
|
|
682
|
+
.screencast-viewport{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#000;position:relative}
|
|
683
|
+
.screencast-viewport img{max-width:100%;max-height:100%;object-fit:contain;display:none}
|
|
684
|
+
.screencast-placeholder{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--text3);font-size:12px;font-family:var(--mono)}
|
|
685
|
+
|
|
686
|
+
/* ── Screencast focus badge on test cards ── */
|
|
687
|
+
.sc-focus-badge{cursor:pointer;font-size:10px;padding:1px 4px;border-radius:3px;opacity:.4;transition:all .15s}
|
|
688
|
+
.sc-focus-badge:hover{opacity:.8}
|
|
689
|
+
.sc-focus-badge.active{opacity:1;background:var(--purple-dim);border-radius:3px}
|
|
690
|
+
|
|
691
|
+
/* ── Screencast toggle in Tests view ── */
|
|
692
|
+
.screencast-toggle-label{display:flex;align-items:center;gap:4px;cursor:pointer;font-size:14px;padding:4px 8px;border-radius:4px;border:1px solid var(--border);background:var(--surface2);transition:all .15s;user-select:none}
|
|
693
|
+
.screencast-toggle-label:hover{border-color:var(--purple);background:var(--surface3)}
|
|
694
|
+
.screencast-toggle-label input{display:none}
|
|
695
|
+
.screencast-toggle-label:has(input:checked){border-color:var(--purple);background:var(--purple-dim);color:var(--purple)}
|
|
473
696
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
.main{margin-left:60px}
|
|
479
|
-
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
480
|
-
.lr-test-grid{grid-template-columns:1fr}
|
|
481
|
-
.toast-container{right:12px;bottom:12px}
|
|
482
|
-
}
|
|
697
|
+
.live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
698
|
+
.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}
|
|
699
|
+
.spinner-small{display:inline-block;width:8px;height:8px;border:1.5px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
700
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
483
701
|
|
|
484
702
|
</style>
|
|
485
703
|
</head>
|
|
@@ -501,20 +719,17 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
501
719
|
<div class="sidebar-section">
|
|
502
720
|
<div class="sidebar-section-label">Navigation</div>
|
|
503
721
|
</div>
|
|
504
|
-
<div class="nav-item" data-view="
|
|
505
|
-
<i class="icon"
|
|
722
|
+
<div class="nav-item active" data-view="watch">
|
|
723
|
+
<i class="icon">⏲</i><span>Watch</span>
|
|
506
724
|
</div>
|
|
507
|
-
<div class="nav-item
|
|
508
|
-
<i class="icon">▷</i><span>
|
|
725
|
+
<div class="nav-item" data-view="tests">
|
|
726
|
+
<i class="icon">▷</i><span>Tests</span><span class="badge" id="badgeSuites">-</span>
|
|
509
727
|
</div>
|
|
510
728
|
<div class="nav-item" data-view="runs">
|
|
511
729
|
<i class="icon">☰</i><span>Runs</span><span class="badge" id="badgeRuns">-</span>
|
|
512
730
|
</div>
|
|
513
|
-
<div class="nav-item" data-view="
|
|
514
|
-
<i class="icon"
|
|
515
|
-
</div>
|
|
516
|
-
<div class="nav-item" data-view="learnings">
|
|
517
|
-
<i class="icon">★</i><span>Learnings</span><span class="badge" id="badgeLearnings">-</span>
|
|
731
|
+
<div class="nav-item" data-view="live" id="navLive" style="display:none">
|
|
732
|
+
<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>
|
|
518
733
|
</div>
|
|
519
734
|
|
|
520
735
|
<div class="pool-status" id="poolStatus">
|
|
@@ -523,6 +738,7 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
523
738
|
<strong>Pool</strong> <span id="poolLabel">--</span>
|
|
524
739
|
</div>
|
|
525
740
|
<div class="pool-info">Sessions: <strong id="poolSessions">-/-</strong></div>
|
|
741
|
+
<div class="pool-list" id="poolList" style="display:none"></div>
|
|
526
742
|
<div class="pool-info" style="margin-top:6px">
|
|
527
743
|
<span class="ws-dot" id="wsDot" style="background:var(--red)"></span>
|
|
528
744
|
<span id="wsLabel" style="font-size:10px;color:var(--text3)">ws: connecting</span>
|
|
@@ -532,7 +748,148 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
532
748
|
|
|
533
749
|
<div class="main">
|
|
534
750
|
|
|
535
|
-
<!--
|
|
751
|
+
<!-- ════════════════ Watch View (default) ════════════════ -->
|
|
752
|
+
<div class="view active" id="view-watch">
|
|
753
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
754
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Watch</div>
|
|
755
|
+
</div>
|
|
756
|
+
<div class="watch-grid" id="watchCards"></div>
|
|
757
|
+
<div class="watch-event-log">
|
|
758
|
+
<div class="watch-event-log-header">
|
|
759
|
+
<span class="title">Recent Runs</span>
|
|
760
|
+
</div>
|
|
761
|
+
<div class="watch-event-log-body" id="watchEventLog"></div>
|
|
762
|
+
</div>
|
|
763
|
+
<div class="empty" id="watchEmpty" style="display:none">
|
|
764
|
+
<div class="empty-icon">⏲</div>
|
|
765
|
+
<p>No projects registered yet. Run some tests to see project cards here.</p>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
|
|
769
|
+
<!-- ════════════════ Tests View (inner tabs: Suites / Modules / Variables) ════════════════ -->
|
|
770
|
+
<div class="view" id="view-tests">
|
|
771
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
772
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Tests</div>
|
|
773
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
774
|
+
<label class="screencast-toggle-label" title="Enable live browser screencast during test runs">
|
|
775
|
+
<input type="checkbox" id="screencastToggle" />
|
|
776
|
+
<span>📹</span>
|
|
777
|
+
</label>
|
|
778
|
+
<button class="btn sm primary" id="btnRunAll">▷ Run All</button>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
<div class="tab-bar">
|
|
782
|
+
<button class="tab-btn active" data-tab="testsTabSuites">Suites</button>
|
|
783
|
+
<button class="tab-btn" data-tab="testsTabModules">Modules</button>
|
|
784
|
+
<button class="tab-btn" data-tab="testsTabVariables">Variables</button>
|
|
785
|
+
</div>
|
|
786
|
+
<!-- Suites tab -->
|
|
787
|
+
<div class="tab-pane active" id="testsTabSuites">
|
|
788
|
+
<div id="suiteAccordionContainer"></div>
|
|
789
|
+
<div class="suite-grid" id="suiteGrid"></div>
|
|
790
|
+
<div class="empty" id="suitesEmpty" style="display:none">
|
|
791
|
+
<div class="empty-icon">▷</div>
|
|
792
|
+
<p>No test suites found.</p>
|
|
793
|
+
</div>
|
|
794
|
+
</div>
|
|
795
|
+
<!-- Modules tab -->
|
|
796
|
+
<div class="tab-pane" id="testsTabModules">
|
|
797
|
+
<div id="moduleSection"></div>
|
|
798
|
+
</div>
|
|
799
|
+
<!-- Variables tab -->
|
|
800
|
+
<div class="tab-pane" id="testsTabVariables">
|
|
801
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
802
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Variables</div>
|
|
803
|
+
<button class="btn sm primary" id="btnAddVar">+ Add Variable</button>
|
|
804
|
+
</div>
|
|
805
|
+
<div id="varAddForm" style="display:none"></div>
|
|
806
|
+
<div id="variablesContainer"></div>
|
|
807
|
+
<div class="empty" id="variablesEmpty" style="display:none">
|
|
808
|
+
<div class="empty-icon">⚙</div>
|
|
809
|
+
<p>No variables set. Add variables to use <code>{{var.KEY}}</code> in your tests.</p>
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
|
|
814
|
+
<!-- ════════════════ Runs View (inner tabs: History / Screenshots / Learnings) ════════════════ -->
|
|
815
|
+
<div class="view" id="view-runs">
|
|
816
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
817
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Runs</div>
|
|
818
|
+
</div>
|
|
819
|
+
<div class="tab-bar">
|
|
820
|
+
<button class="tab-btn active" data-tab="runsTabHistory">History</button>
|
|
821
|
+
<button class="tab-btn" data-tab="runsTabScreenshots">Screenshots<span class="badge" id="badgeScreenshots" style="margin-left:6px">-</span></button>
|
|
822
|
+
<button class="tab-btn" data-tab="runsTabLearnings" id="runsTabLearnings">Learnings<span class="badge" id="badgeLearnings" style="margin-left:6px">-</span></button>
|
|
823
|
+
</div>
|
|
824
|
+
<!-- History tab -->
|
|
825
|
+
<div class="tab-pane active" id="runsTabHistory">
|
|
826
|
+
<div class="health-banner" id="runsHealthBanner"></div>
|
|
827
|
+
<div class="card">
|
|
828
|
+
<div class="card-label">Pass Rate Trend</div>
|
|
829
|
+
<div class="chart" id="trendChart"></div>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="filter-bar" id="filterBar">
|
|
832
|
+
<button class="filter-btn active" data-filter="all">All</button>
|
|
833
|
+
<button class="filter-btn" data-filter="pass">Pass</button>
|
|
834
|
+
<button class="filter-btn" data-filter="fail">Fail</button>
|
|
835
|
+
<button class="filter-btn" data-filter="mixed">Mixed</button>
|
|
836
|
+
<input type="text" id="runSearchInput" placeholder="Search suite..." spellcheck="false">
|
|
837
|
+
</div>
|
|
838
|
+
<div class="card" style="padding:0">
|
|
839
|
+
<div class="tbl-wrap">
|
|
840
|
+
<table>
|
|
841
|
+
<thead id="runsHead"><tr></tr></thead>
|
|
842
|
+
<tbody id="runsBody"></tbody>
|
|
843
|
+
</table>
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
846
|
+
<div class="empty" id="runsEmpty" style="display:none">
|
|
847
|
+
<div class="empty-icon">☰</div>
|
|
848
|
+
<p>No runs recorded yet.</p>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
<!-- Screenshots tab -->
|
|
852
|
+
<div class="tab-pane" id="runsTabScreenshots">
|
|
853
|
+
<div class="ss-search">
|
|
854
|
+
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
855
|
+
<button id="ssHashBtn">Search</button>
|
|
856
|
+
</div>
|
|
857
|
+
<div id="ssSearchResult"></div>
|
|
858
|
+
<div class="gallery" id="screenshotGallery"></div>
|
|
859
|
+
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
860
|
+
<div class="empty-icon">▣</div>
|
|
861
|
+
<p>Select a project to view screenshots.</p>
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
<!-- Learnings tab -->
|
|
865
|
+
<div class="tab-pane" id="runsTabLearnings">
|
|
866
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
867
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Learnings</div>
|
|
868
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
869
|
+
<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">
|
|
870
|
+
<option value="7">7 days</option>
|
|
871
|
+
<option value="14">14 days</option>
|
|
872
|
+
<option value="30" selected>30 days</option>
|
|
873
|
+
<option value="90">90 days</option>
|
|
874
|
+
</select>
|
|
875
|
+
<button class="btn sm" id="btnExportLearnings">Export MD</button>
|
|
876
|
+
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
879
|
+
<div id="learnDash">
|
|
880
|
+
<div id="learnHero"></div>
|
|
881
|
+
<div id="learnCards" class="learn-cols"></div>
|
|
882
|
+
<div id="learnTrend"></div>
|
|
883
|
+
<div id="learnBottom" class="learn-cols"></div>
|
|
884
|
+
</div>
|
|
885
|
+
<div class="empty" id="learningsEmpty" style="display:none">
|
|
886
|
+
<div class="empty-icon">★</div>
|
|
887
|
+
<p>No learnings data yet. Run some tests to start building knowledge.</p>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
|
|
892
|
+
<!-- ════════════════ Live View ════════════════ -->
|
|
536
893
|
<div class="view" id="view-live">
|
|
537
894
|
<div class="live-panel active" id="livePanel">
|
|
538
895
|
<div class="live-header">
|
|
@@ -551,114 +908,56 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
551
908
|
<button class="live-clear-btn" id="liveClearBtn">Clear All</button>
|
|
552
909
|
</div>
|
|
553
910
|
<div class="live-progress"><div class="live-progress-fill" id="liveProgressFill" style="width:0"></div></div>
|
|
554
|
-
<div class="live-
|
|
911
|
+
<div class="live-body">
|
|
912
|
+
<div class="live-tests" id="liveTests"></div>
|
|
913
|
+
<div class="screencast-panel" id="screencastPanel" style="display:none">
|
|
914
|
+
<div class="screencast-header">
|
|
915
|
+
<span class="screencast-label">📹 Screencast</span>
|
|
916
|
+
<select id="screencastSelect" class="screencast-select"><option value="">Select test...</option></select>
|
|
917
|
+
</div>
|
|
918
|
+
<div class="screencast-viewport">
|
|
919
|
+
<img id="screencastImg" alt="Browser screencast" />
|
|
920
|
+
<div class="screencast-placeholder" id="screencastPlaceholder">Select a running test to watch</div>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
555
924
|
</div>
|
|
556
925
|
<div class="empty" id="liveEmpty">
|
|
557
926
|
<div class="empty-icon" style="font-size:48px;opacity:.3">●</div>
|
|
558
|
-
<p>No tests running. Start a test from the
|
|
927
|
+
<p>No tests running. Start a test from the Tests view or another console.</p>
|
|
559
928
|
<p style="margin-top:8px;font-size:11px;color:var(--text3)">This view activates automatically when tests are detected.</p>
|
|
560
929
|
</div>
|
|
561
930
|
</div>
|
|
562
931
|
|
|
563
|
-
|
|
564
|
-
<div class="view active" id="view-suites">
|
|
565
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
566
|
-
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Test Suites</div>
|
|
567
|
-
</div>
|
|
568
|
-
<div id="suiteAccordionContainer"></div>
|
|
569
|
-
<div class="suite-grid" id="suiteGrid"></div>
|
|
570
|
-
<div id="moduleSection"></div>
|
|
571
|
-
<div class="empty" id="suitesEmpty" style="display:none">
|
|
572
|
-
<div class="empty-icon">▷</div>
|
|
573
|
-
<p>No test suites found.</p>
|
|
574
|
-
</div>
|
|
575
|
-
</div>
|
|
932
|
+
</div>
|
|
576
933
|
|
|
577
|
-
|
|
578
|
-
<div class="
|
|
579
|
-
<div
|
|
580
|
-
<div
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
<div class="chart" id="trendChart"></div>
|
|
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>
|
|
593
|
-
<div class="card" style="padding:0">
|
|
594
|
-
<div class="tbl-wrap">
|
|
595
|
-
<table>
|
|
596
|
-
<thead id="runsHead"><tr></tr></thead>
|
|
597
|
-
<tbody id="runsBody"></tbody>
|
|
598
|
-
</table>
|
|
934
|
+
<div class="suite-modal-overlay" id="suiteModalOverlay">
|
|
935
|
+
<div class="suite-modal" id="suiteModal">
|
|
936
|
+
<div class="suite-modal-header">
|
|
937
|
+
<div class="suite-card-icon">▷</div>
|
|
938
|
+
<div class="suite-modal-title">
|
|
939
|
+
<h2 id="suiteModalName"></h2>
|
|
940
|
+
<span id="suiteModalFile"></span>
|
|
599
941
|
</div>
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
<p>No runs recorded yet.</p>
|
|
604
|
-
</div>
|
|
605
|
-
</div>
|
|
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>
|
|
942
|
+
<div class="suite-modal-actions">
|
|
943
|
+
<button class="btn sm primary" id="suiteModalRun">▷ Run</button>
|
|
944
|
+
<button class="suite-modal-close" id="suiteModalClose">×</button>
|
|
620
945
|
</div>
|
|
621
946
|
</div>
|
|
622
|
-
<div id="
|
|
623
|
-
|
|
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
|
-
|
|
635
|
-
<!-- Screenshots View -->
|
|
636
|
-
<div class="view" id="view-screenshots">
|
|
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>
|
|
643
|
-
<div class="gallery" id="screenshotGallery"></div>
|
|
644
|
-
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
645
|
-
<div class="empty-icon">▣</div>
|
|
646
|
-
<p>Select a project to view screenshots.</p>
|
|
947
|
+
<div class="suite-modal-body" id="suiteModalBody">
|
|
948
|
+
<div class="suite-modal-loading">Loading...</div>
|
|
647
949
|
</div>
|
|
648
950
|
</div>
|
|
649
|
-
|
|
650
951
|
</div>
|
|
651
|
-
|
|
652
952
|
<div class="modal" id="modal"><img id="modalImg" src="" alt=""></div>
|
|
653
953
|
<div class="toast-container" id="toastContainer"></div>
|
|
654
954
|
<div class="kb-modal" id="kbModal">
|
|
655
955
|
<div class="kb-modal-content">
|
|
656
956
|
<h2>Keyboard Shortcuts</h2>
|
|
657
|
-
<div class="kb-row"><span class="kb-key">1</span><span class="kb-desc">
|
|
658
|
-
<div class="kb-row"><span class="kb-key">2</span><span class="kb-desc">
|
|
659
|
-
<div class="kb-row"><span class="kb-key">3</span><span class="kb-desc">
|
|
660
|
-
<div class="kb-row"><span class="kb-key">4</span><span class="kb-desc">
|
|
661
|
-
<div class="kb-row"><span class="kb-key">5</span><span class="kb-desc">Live view</span></div>
|
|
957
|
+
<div class="kb-row"><span class="kb-key">1</span><span class="kb-desc">Watch view</span></div>
|
|
958
|
+
<div class="kb-row"><span class="kb-key">2</span><span class="kb-desc">Tests view</span></div>
|
|
959
|
+
<div class="kb-row"><span class="kb-key">3</span><span class="kb-desc">Runs view</span></div>
|
|
960
|
+
<div class="kb-row"><span class="kb-key">4</span><span class="kb-desc">Live view</span></div>
|
|
662
961
|
<div class="kb-row"><span class="kb-key">j / k</span><span class="kb-desc">Navigate runs (next / previous)</span></div>
|
|
663
962
|
<div class="kb-row"><span class="kb-key">Enter</span><span class="kb-desc">Expand / collapse selected run</span></div>
|
|
664
963
|
<div class="kb-row"><span class="kb-key">Esc</span><span class="kb-desc">Close modal / collapse run</span></div>
|
|
@@ -670,6 +969,8 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
670
969
|
<script>
|
|
671
970
|
(function(){
|
|
672
971
|
'use strict';
|
|
972
|
+
/* ── utils.js ── */
|
|
973
|
+
/* ── DOM Helpers ── */
|
|
673
974
|
var $=function(s){return document.querySelector(s)};
|
|
674
975
|
var $$=function(s){return document.querySelectorAll(s)};
|
|
675
976
|
|
|
@@ -740,20 +1041,37 @@ function buildNdSection(title,contentEl,count,copyText){
|
|
|
740
1041
|
return el('div',{className:'rd-nd-section'},[toggle,contentWrap]);
|
|
741
1042
|
}
|
|
742
1043
|
|
|
1044
|
+
function gqlOp(n){
|
|
1045
|
+
if(n.requestBody){
|
|
1046
|
+
try{
|
|
1047
|
+
var b=JSON.parse(n.requestBody);
|
|
1048
|
+
if(b.operationName)return b.operationName;
|
|
1049
|
+
if(b.query){var m=b.query.match(/^(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/);if(m)return m[1]}
|
|
1050
|
+
}catch(e){}
|
|
1051
|
+
}
|
|
1052
|
+
if(n.url){
|
|
1053
|
+
try{var u=new URL(n.url,location.href);var op=u.searchParams.get('operationName');if(op)return op}catch(e){}
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
743
1058
|
function buildNetRow(n){
|
|
744
1059
|
var mCls='rd-net-method '+(n.method||'GET').toLowerCase();
|
|
745
1060
|
var sCode=n.status||0;
|
|
746
1061
|
var sCls='rd-net-status '+(sCode<300?'s2xx':sCode<400?'s3xx':sCode<500?'s4xx':'s5xx');
|
|
747
1062
|
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
748
1063
|
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
749
|
-
var
|
|
1064
|
+
var opName=gqlOp(n);
|
|
1065
|
+
var children=[
|
|
750
1066
|
el('span',{className:'rd-net-expand'},hasDetail?'\u25B6':''),
|
|
751
1067
|
el('span',{className:mCls},n.method||'GET'),
|
|
752
|
-
el('span',{className:sCls},String(sCode))
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1068
|
+
el('span',{className:sCls},String(sCode))
|
|
1069
|
+
];
|
|
1070
|
+
if(opName)children.push(el('span',{className:'rd-net-op'},opName));
|
|
1071
|
+
children.push(el('span',{className:'rd-net-url'},n.url||''));
|
|
1072
|
+
children.push(makeCopyBtn(n.url||''));
|
|
1073
|
+
children.push(el('span',{className:'rd-net-dur'},dur(n.duration)));
|
|
1074
|
+
var row=el('div',{className:rowCls},children);
|
|
757
1075
|
var detail=null;
|
|
758
1076
|
if(hasDetail){
|
|
759
1077
|
var sections=[];
|
|
@@ -816,11 +1134,108 @@ function createTriggerBadge(source){
|
|
|
816
1134
|
return badge;
|
|
817
1135
|
}
|
|
818
1136
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1137
|
+
function createDriverBadge(driver){
|
|
1138
|
+
if(!driver)return document.createTextNode('--');
|
|
1139
|
+
var labels={browserless:'Browserless',cdp:'CDP',steel:'Steel',auto:'Auto'};
|
|
1140
|
+
var colors={browserless:'var(--accent)',cdp:'var(--purple)',steel:'var(--amber)'};
|
|
1141
|
+
var icons={browserless:'\u{1F310}',cdp:'\u{1F50C}',steel:'\u{1F6E1}'};
|
|
1142
|
+
// Handle multi-driver (e.g. "browserless,steel")
|
|
1143
|
+
var parts=driver.split(',');
|
|
1144
|
+
if(parts.length>1){
|
|
1145
|
+
var wrap=el('span',{style:'display:inline-flex;gap:4px'});
|
|
1146
|
+
parts.forEach(function(d){wrap.appendChild(createDriverBadge(d.trim()))});
|
|
1147
|
+
return wrap;
|
|
1148
|
+
}
|
|
1149
|
+
var d=driver.trim();
|
|
1150
|
+
var badge=el('span',{className:'driver-badge drv-'+d,style:'color:'+(colors[d]||'var(--text3)')},[
|
|
1151
|
+
el('span',{className:'drv-icon'},icons[d]||'\u2699'),
|
|
1152
|
+
document.createTextNode(labels[d]||d)
|
|
1153
|
+
]);
|
|
1154
|
+
return badge;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/* ── Pool Distribution Summary ── */
|
|
1158
|
+
var POOL_COLORS=['#6366f1','#22d3ee','#f59e0b','#10b981','#ef4444','#8b5cf6','#ec4899','#14b8a6'];
|
|
1159
|
+
function buildPoolDistribution(tests){
|
|
1160
|
+
var pools={};var total=0;
|
|
1161
|
+
Object.keys(tests).forEach(function(n){
|
|
1162
|
+
if(n==='__error')return;var t=tests[n];
|
|
1163
|
+
if(!t.poolUrl)return;
|
|
1164
|
+
var label=t.poolUrl.replace('ws://','').replace('wss://','');
|
|
1165
|
+
if(!pools[label])pools[label]={count:0,passed:0,failed:0};
|
|
1166
|
+
pools[label].count++;total++;
|
|
1167
|
+
if(t.status==='passed'||t.success)pools[label].passed++;
|
|
1168
|
+
if(t.status==='failed'||t.success===false)pools[label].failed++;
|
|
1169
|
+
});
|
|
1170
|
+
var keys=Object.keys(pools);
|
|
1171
|
+
if(keys.length<2)return null;
|
|
1172
|
+
var bar=el('div',{className:'pool-dist'});
|
|
1173
|
+
var legend=el('div',{className:'pool-dist-legend'});
|
|
1174
|
+
keys.forEach(function(k,i){
|
|
1175
|
+
var pct=Math.round(pools[k].count/total*100);
|
|
1176
|
+
var color=POOL_COLORS[i%POOL_COLORS.length];
|
|
1177
|
+
var seg=el('div',{className:'pool-dist-seg'});
|
|
1178
|
+
seg.style.flex=pools[k].count;seg.style.background=color;
|
|
1179
|
+
seg.textContent=k+' ('+pools[k].count+')';
|
|
1180
|
+
bar.appendChild(seg);
|
|
1181
|
+
var lg=el('span',{},k+': '+pools[k].count+' tests ('+pct+'%)');
|
|
1182
|
+
lg.style.cssText='display:inline-flex;align-items:center;gap:4px';
|
|
1183
|
+
var dot=el('span',{});dot.style.cssText='width:8px;height:8px;border-radius:2px;background:'+color+';flex-shrink:0';
|
|
1184
|
+
lg.insertBefore(dot,lg.firstChild);
|
|
1185
|
+
legend.appendChild(lg);
|
|
1186
|
+
});
|
|
1187
|
+
return el('div',{style:'padding:4px 12px'},[bar,legend]);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
/* ── state.js ── */
|
|
1192
|
+
/* ── Global State ── */
|
|
1193
|
+
var S={
|
|
1194
|
+
ws:null,project:null,view:'watch',selectedRun:null,
|
|
1195
|
+
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
1196
|
+
runFilter:{status:'all',search:''},
|
|
1197
|
+
lastLearningsData:null,
|
|
1198
|
+
highlightedRunIdx:-1
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
/* ── Navigation ── */
|
|
1202
|
+
$$('.nav-item').forEach(function(n){
|
|
1203
|
+
n.addEventListener('click',function(){
|
|
1204
|
+
showView(n.dataset.view);
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
function showView(v){
|
|
1208
|
+
S.view=v;
|
|
1209
|
+
$$('.nav-item').forEach(function(n){n.classList.toggle('active',n.dataset.view===v)});
|
|
1210
|
+
$$('.view').forEach(function(x){x.classList.remove('active')});
|
|
1211
|
+
var viewEl=$('#view-'+v);
|
|
1212
|
+
if(viewEl)viewEl.classList.add('active');
|
|
1213
|
+
if(v==='watch'&&typeof startWatchPolling==='function')startWatchPolling();
|
|
1214
|
+
else if(typeof stopWatchPolling==='function')stopWatchPolling();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/* ── Inner Tabs ── */
|
|
1218
|
+
function initTabs(){
|
|
1219
|
+
$$('.tab-bar').forEach(function(bar){
|
|
1220
|
+
var container=bar.parentElement;
|
|
1221
|
+
bar.querySelectorAll('.tab-btn').forEach(function(btn){
|
|
1222
|
+
btn.addEventListener('click',function(){
|
|
1223
|
+
bar.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active')});
|
|
1224
|
+
btn.classList.add('active');
|
|
1225
|
+
container.querySelectorAll('.tab-pane').forEach(function(p){p.classList.remove('active')});
|
|
1226
|
+
var pane=container.querySelector('#'+btn.dataset.tab);
|
|
1227
|
+
if(pane)pane.classList.add('active');
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
/* ── toast.js ── */
|
|
1235
|
+
/* ── Toast Notifications ── */
|
|
1236
|
+
function showToast(message,type,timeout){
|
|
823
1237
|
type=type||'info';
|
|
1238
|
+
timeout=timeout||5000;
|
|
824
1239
|
var container=$('#toastContainer');
|
|
825
1240
|
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
826
1241
|
var t=el('div',{className:'toast '+type},[
|
|
@@ -831,12 +1246,24 @@ function showToast(message,type){
|
|
|
831
1246
|
setTimeout(function(){
|
|
832
1247
|
t.classList.add('fade-out');
|
|
833
1248
|
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
834
|
-
},
|
|
1249
|
+
},timeout);
|
|
835
1250
|
}
|
|
836
1251
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1252
|
+
function showEnrichedToast(message,type){
|
|
1253
|
+
var container=$('#toastContainer');
|
|
1254
|
+
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
1255
|
+
var t=el('div',{className:'toast clickable '+type,onclick:function(){showView('runs');var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
1256
|
+
el('span',null,icons[type]||''),
|
|
1257
|
+
el('span',null,message)
|
|
1258
|
+
]);
|
|
1259
|
+
container.appendChild(t);
|
|
1260
|
+
setTimeout(function(){
|
|
1261
|
+
t.classList.add('fade-out');
|
|
1262
|
+
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
1263
|
+
},7000);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/* ── Download helper ── */
|
|
840
1267
|
function downloadFile(filename,content,mimeType){
|
|
841
1268
|
var blob=new Blob([content],{type:mimeType||'text/plain'});
|
|
842
1269
|
var url=URL.createObjectURL(blob);
|
|
@@ -847,35 +1274,74 @@ function downloadFile(filename,content,mimeType){
|
|
|
847
1274
|
URL.revokeObjectURL(url);
|
|
848
1275
|
}
|
|
849
1276
|
|
|
850
|
-
/* ── State ── */
|
|
851
|
-
var S={
|
|
852
|
-
ws:null,project:null,view:'suites',selectedRun:null,
|
|
853
|
-
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
854
|
-
runFilter:{status:'all',search:''},
|
|
855
|
-
lastLearningsData:null,
|
|
856
|
-
highlightedRunIdx:-1
|
|
857
|
-
};
|
|
858
1277
|
|
|
859
|
-
/* ──
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
$$('.nav-item').forEach(function(n){n.classList.toggle('active',n.dataset.view===v)});
|
|
872
|
-
$$('.view').forEach(function(x){x.classList.remove('active')});
|
|
873
|
-
$('#view-'+v).classList.add('active');
|
|
1278
|
+
/* ── api.js ── */
|
|
1279
|
+
/* ── API & Pool ── */
|
|
1280
|
+
function api(p){return fetch(p).then(function(r){return r.json()})}
|
|
1281
|
+
function triggerRun(suite,projectId){
|
|
1282
|
+
if(anyLiveRunning())return;
|
|
1283
|
+
var body={};
|
|
1284
|
+
if(suite)body.suite=suite;
|
|
1285
|
+
if(projectId)body.projectId=projectId;
|
|
1286
|
+
else if(S.project)body.projectId=S.project;
|
|
1287
|
+
var scToggle=$('#screencastToggle');
|
|
1288
|
+
if(scToggle&&scToggle.checked)body.screencast=true;
|
|
1289
|
+
fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
874
1290
|
}
|
|
875
1291
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1292
|
+
function renderPool(d){
|
|
1293
|
+
if(!d)return;
|
|
1294
|
+
var poolList=$('#poolList');
|
|
1295
|
+
if(d.pools&&d.pools.length>1){
|
|
1296
|
+
var anyAvail=d.availableCount>0;
|
|
1297
|
+
$('#poolDot').className='pool-dot '+(anyAvail?'on':'off');
|
|
1298
|
+
$('#poolLabel').textContent=anyAvail?d.availableCount+'/'+d.totalPools+' ready':'all busy';
|
|
1299
|
+
$('#poolSessions').textContent=(d.totalRunning||0)+'/'+(d.totalMaxConcurrent||0);
|
|
1300
|
+
poolList.textContent='';poolList.style.display='';
|
|
1301
|
+
d.pools.forEach(function(p){
|
|
1302
|
+
var label=(p.url||'').replace('ws://','').replace('wss://','');
|
|
1303
|
+
var ok=!p.error&&p.available;
|
|
1304
|
+
var dot=el('span',{className:'pool-dot '+(ok?'on':'off')});
|
|
1305
|
+
var name=el('strong',{},label);
|
|
1306
|
+
var status=el('span',{},p.error?'offline':p.available?'ready':'busy');
|
|
1307
|
+
var sess=el('span',{className:'pool-sessions'},(p.running||0)+'/'+(p.maxConcurrent||0));
|
|
1308
|
+
poolList.appendChild(el('div',{className:'pool-item'},[dot,name,status,sess]));
|
|
1309
|
+
});
|
|
1310
|
+
}else if(d.pools&&d.pools.length===1){
|
|
1311
|
+
var p=d.pools[0];
|
|
1312
|
+
$('#poolDot').className='pool-dot '+(p.error||!p.available?'off':'on');
|
|
1313
|
+
$('#poolLabel').textContent=p.error?'offline':p.available?'ready':'busy';
|
|
1314
|
+
$('#poolSessions').textContent=(p.running||0)+'/'+(p.maxConcurrent||0);
|
|
1315
|
+
poolList.style.display='none';
|
|
1316
|
+
}else{
|
|
1317
|
+
$('#poolDot').className='pool-dot '+(d.error||!d.available?'off':'on');
|
|
1318
|
+
$('#poolLabel').textContent=d.error?'offline':d.available?'ready':'busy';
|
|
1319
|
+
$('#poolSessions').textContent=(d.running||0)+'/'+(d.maxConcurrent||0);
|
|
1320
|
+
poolList.style.display='none';
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
1324
|
+
|
|
1325
|
+
/* ── Projects ── */
|
|
1326
|
+
function refreshProjects(){
|
|
1327
|
+
api('/api/db/projects').then(function(projects){
|
|
1328
|
+
var sel=$('#projectSelect'),prev=sel.value;
|
|
1329
|
+
while(sel.options.length>1)sel.remove(1);
|
|
1330
|
+
if(Array.isArray(projects))projects.forEach(function(p){
|
|
1331
|
+
var o=document.createElement('option');o.value=p.id;o.textContent=p.name;sel.appendChild(o);
|
|
1332
|
+
});
|
|
1333
|
+
sel.value=prev||'';
|
|
1334
|
+
}).catch(function(){});
|
|
1335
|
+
}
|
|
1336
|
+
$('#projectSelect').addEventListener('change',function(){
|
|
1337
|
+
S.project=this.value?parseInt(this.value,10):null;
|
|
1338
|
+
S.selectedRun=null;
|
|
1339
|
+
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();refreshWatch();
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
/* ── websocket.js ── */
|
|
1344
|
+
/* ── WebSocket ── */
|
|
879
1345
|
function connectWS(){
|
|
880
1346
|
var proto=location.protocol==='https:'?'wss:':'ws:';
|
|
881
1347
|
S.ws=new WebSocket(proto+'//'+location.host);
|
|
@@ -929,6 +1395,11 @@ function handleWS(m){
|
|
|
929
1395
|
r2.active=m.activeCount;
|
|
930
1396
|
r2.tests[m.name]={status:'running',actions:0,totalActions:0,error:null,actionLog:[],screenshots:[],serial:m.serial||false};
|
|
931
1397
|
renderLive();break;
|
|
1398
|
+
case 'test:pool':
|
|
1399
|
+
var rp=getLiveRun(m);if(!rp||!rp.tests[m.name])break;
|
|
1400
|
+
rp.tests[m.name].poolUrl=m.poolUrl||null;
|
|
1401
|
+
rp.tests[m.name].actionLog.unshift({type:'pool',narrative:'\uD83D\uDD17 '+m.name+' \u2192 '+(m.poolUrl||'').replace('ws://','').replace('wss://',''),success:true,duration:null,isPoolLog:true});
|
|
1402
|
+
renderLive();break;
|
|
932
1403
|
case 'test:action':
|
|
933
1404
|
var r3=getLiveRun(m);if(!r3||!r3.tests[m.name])break;
|
|
934
1405
|
var t=r3.tests[m.name];
|
|
@@ -950,66 +1421,292 @@ function handleWS(m){
|
|
|
950
1421
|
if(m.screenshots&&m.screenshots.length)r5.tests[m.name].screenshots=m.screenshots;
|
|
951
1422
|
if(m.errorScreenshot)r5.tests[m.name].errorScreenshot=m.errorScreenshot;
|
|
952
1423
|
if(m.networkLogs&&m.networkLogs.length)r5.tests[m.name].networkLogs=m.networkLogs;
|
|
1424
|
+
if(m.poolUrl)r5.tests[m.name].poolUrl=m.poolUrl;
|
|
953
1425
|
}
|
|
954
1426
|
r5.active=Math.max(0,r5.active-1);
|
|
955
1427
|
renderLive();break;
|
|
956
1428
|
case 'run:complete':
|
|
957
1429
|
var r6=getLiveRun(m);if(r6){r6.on=false;r6.done=true;r6.active=0}
|
|
958
1430
|
var summary=m.summary||{};
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1431
|
+
var baseMsg='Run complete: '+(summary.failed>0?summary.failed+' failed':'all '+(summary.total||0)+' passed');
|
|
1432
|
+
var baseType=summary.failed>0?'error':'success';
|
|
1433
|
+
var healthUrl=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
1434
|
+
fetch(healthUrl).then(function(r){return r.json()}).then(function(h){
|
|
1435
|
+
if(h&&h.passRate!==undefined){
|
|
1436
|
+
var extra='. Pass rate: '+h.passRate+'%';
|
|
1437
|
+
if(h.passRateTrend==='declining')extra+=' (declining, '+h.trendDelta+'%)';
|
|
1438
|
+
else if(h.passRateTrend==='improving')extra+=' (improving, +'+h.trendDelta+'%)';
|
|
1439
|
+
if(h.flakyCount>0)extra+='. '+h.flakyCount+' flaky test(s)';
|
|
1440
|
+
showEnrichedToast(baseMsg+extra,baseType);
|
|
1441
|
+
} else {
|
|
1442
|
+
showToast(baseMsg,baseType);
|
|
1443
|
+
}
|
|
1444
|
+
}).catch(function(){showToast(baseMsg,baseType)});
|
|
1445
|
+
renderLive();refreshRuns();refreshProjects();refreshWatch();break;
|
|
962
1446
|
case 'run:error':
|
|
963
1447
|
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
964
1448
|
showToast('Run error: '+m.error,'error');
|
|
965
1449
|
renderLive();break;
|
|
1450
|
+
case 'test:frame':
|
|
1451
|
+
if(S.screencastTest===m.name&&m.data){
|
|
1452
|
+
var img=$('#screencastImg');
|
|
1453
|
+
if(img)img.src='data:image/jpeg;base64,'+m.data;
|
|
1454
|
+
}
|
|
1455
|
+
break;
|
|
966
1456
|
case 'db:updated':
|
|
967
|
-
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();break;
|
|
1457
|
+
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();refreshWatch();break;
|
|
968
1458
|
}
|
|
969
1459
|
}
|
|
970
1460
|
|
|
1461
|
+
|
|
1462
|
+
/* ── view-watch.js ── */
|
|
971
1463
|
/* ══════════════════════════════════════════════════════════════════
|
|
972
|
-
|
|
1464
|
+
Watch View — Project Cards + Sparklines + Event Log
|
|
973
1465
|
══════════════════════════════════════════════════════════════════ */
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1466
|
+
var _watchInterval=null;
|
|
1467
|
+
var _countdownInterval=null;
|
|
1468
|
+
var _watchData=null;
|
|
1469
|
+
|
|
1470
|
+
function refreshWatch(){
|
|
1471
|
+
// Fetch projects overview (sparklines)
|
|
1472
|
+
api('/api/db/projects/overview').then(function(projects){
|
|
1473
|
+
if(!Array.isArray(projects)||!projects.length){
|
|
1474
|
+
$('#watchCards').textContent='';
|
|
1475
|
+
$('#watchEmpty').style.display='block';
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
$('#watchEmpty').style.display='none';
|
|
1479
|
+
_watchData=projects;
|
|
1480
|
+
renderWatchCards(projects);
|
|
1481
|
+
}).catch(function(){
|
|
1482
|
+
// Fallback: use regular projects list
|
|
1483
|
+
api('/api/db/projects').then(function(projects){
|
|
1484
|
+
if(!Array.isArray(projects)||!projects.length){$('#watchEmpty').style.display='block';return}
|
|
1485
|
+
$('#watchEmpty').style.display='none';
|
|
1486
|
+
_watchData=projects.map(function(p){return Object.assign({},p,{sparkline:[]})});
|
|
1487
|
+
renderWatchCards(_watchData);
|
|
1488
|
+
}).catch(function(){});
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// Fetch event log (recent runs)
|
|
1492
|
+
var runsUrl=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
1493
|
+
api(runsUrl).then(function(runs){
|
|
1494
|
+
renderEventLog(runs);
|
|
1495
|
+
}).catch(function(){});
|
|
1496
|
+
|
|
1497
|
+
// Fetch watch jobs status for countdown
|
|
1498
|
+
fetch('/api/watch/status').then(function(r){
|
|
1499
|
+
if(!r.ok)throw new Error('not running');
|
|
1500
|
+
return r.json();
|
|
1501
|
+
}).then(function(jobs){
|
|
1502
|
+
applyWatchJobData(jobs);
|
|
1503
|
+
}).catch(function(){
|
|
1504
|
+
// Watch engine not running — that's fine, cards still show
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function renderWatchCards(projects){
|
|
1509
|
+
var container=$('#watchCards');
|
|
1510
|
+
container.textContent='';
|
|
1511
|
+
|
|
1512
|
+
projects.forEach(function(p){
|
|
1513
|
+
var sparkline=p.sparkline||[];
|
|
1514
|
+
var lastRate=sparkline.length?sparkline[sparkline.length-1]:null;
|
|
1515
|
+
var rateColor=lastRate===null?'dim':lastRate>=90?'green':lastRate>=70?'amber':'red';
|
|
1516
|
+
var dotColor=rateColor;
|
|
1517
|
+
|
|
1518
|
+
var sparkEl=el('div',{className:'watch-sparkline'});
|
|
1519
|
+
if(sparkline.length>=2){
|
|
1520
|
+
sparkEl.appendChild(buildSparkline(sparkline));
|
|
1521
|
+
} else {
|
|
1522
|
+
sparkEl.style.cssText='height:40px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:10px';
|
|
1523
|
+
sparkEl.textContent=sparkline.length?'1 run':'No runs yet';
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
var triggerBtn=el('button',{className:'btn sm',onclick:function(e){e.stopPropagation();triggerRun(null,p.id)}},'\u25B6');
|
|
1527
|
+
var detailBtn=el('button',{className:'btn sm',onclick:function(e){
|
|
1528
|
+
e.stopPropagation();
|
|
1529
|
+
S.project=p.id;$('#projectSelect').value=p.id;
|
|
1530
|
+
showView('runs');
|
|
1531
|
+
refreshRuns();refreshSuites();
|
|
1532
|
+
}},'\uD83D\uDD0D');
|
|
1533
|
+
|
|
1534
|
+
var card=el('div',{className:'watch-card',id:'watch-card-'+p.id},[
|
|
1535
|
+
el('div',{className:'watch-card-header'},[
|
|
1536
|
+
el('div',{className:'watch-card-name'},p.name),
|
|
1537
|
+
el('div',{className:'watch-card-icons'},[triggerBtn,detailBtn])
|
|
1538
|
+
]),
|
|
1539
|
+
sparkEl,
|
|
1540
|
+
el('div',{className:'watch-card-footer'},[
|
|
1541
|
+
el('div',{className:'watch-card-status'},[
|
|
1542
|
+
el('span',{className:'status-dot '+dotColor}),
|
|
1543
|
+
el('span',{className:'watch-card-rate '+rateColor},lastRate!==null?lastRate+'%':'—')
|
|
1544
|
+
]),
|
|
1545
|
+
el('span',{style:'color:var(--text3);font-size:10px'},p.runCount?p.runCount+' runs':'')
|
|
1546
|
+
]),
|
|
1547
|
+
el('div',{className:'watch-card-meta'},[
|
|
1548
|
+
el('span',{className:'watch-card-countdown',id:'watch-countdown-'+p.id},''),
|
|
1549
|
+
p.lastCommit?el('span',{className:'watch-card-commit'},'\u{1F4CB} '+p.lastCommit.slice(0,8)):null
|
|
1550
|
+
])
|
|
1551
|
+
]);
|
|
1552
|
+
|
|
1553
|
+
container.appendChild(card);
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function buildSparkline(data){
|
|
1558
|
+
var ns='http://www.w3.org/2000/svg';
|
|
1559
|
+
var svg=document.createElementNS(ns,'svg');
|
|
1560
|
+
svg.setAttribute('viewBox','0 0 200 40');
|
|
1561
|
+
svg.setAttribute('preserveAspectRatio','none');
|
|
1562
|
+
|
|
1563
|
+
var n=data.length;
|
|
1564
|
+
var w=200/(n-1||1);
|
|
1565
|
+
var pts=data.map(function(v,i){return (i*w)+','+(40-v*0.4)}).join(' ');
|
|
1566
|
+
|
|
1567
|
+
// Gradient fill
|
|
1568
|
+
var poly=document.createElementNS(ns,'polygon');
|
|
1569
|
+
poly.setAttribute('points','0,40 '+pts+' '+((n-1)*w)+',40');
|
|
1570
|
+
poly.setAttribute('fill','var(--accent-dim)');
|
|
1571
|
+
svg.appendChild(poly);
|
|
1572
|
+
|
|
1573
|
+
// Line
|
|
1574
|
+
var pl=document.createElementNS(ns,'polyline');
|
|
1575
|
+
pl.setAttribute('points',pts);
|
|
1576
|
+
pl.setAttribute('fill','none');
|
|
1577
|
+
pl.setAttribute('stroke','var(--accent)');
|
|
1578
|
+
pl.setAttribute('stroke-width','1.5');
|
|
1579
|
+
svg.appendChild(pl);
|
|
1580
|
+
|
|
1581
|
+
// End dot
|
|
1582
|
+
if(n>0){
|
|
1583
|
+
var lastVal=data[n-1];
|
|
1584
|
+
var dotColor=lastVal>=90?'var(--green)':lastVal>=70?'var(--amber)':'var(--red)';
|
|
1585
|
+
var circle=document.createElementNS(ns,'circle');
|
|
1586
|
+
circle.setAttribute('cx',''+(n-1)*w);
|
|
1587
|
+
circle.setAttribute('cy',''+(40-lastVal*0.4));
|
|
1588
|
+
circle.setAttribute('r','3');
|
|
1589
|
+
circle.setAttribute('fill',dotColor);
|
|
1590
|
+
svg.appendChild(circle);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return svg;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function applyWatchJobData(jobs){
|
|
1597
|
+
if(!jobs||!jobs.length)return;
|
|
1598
|
+
jobs.forEach(function(j){
|
|
1599
|
+
// Find matching card by project name
|
|
1600
|
+
if(!_watchData)return;
|
|
1601
|
+
var match=_watchData.find(function(p){return p.name===j.name||p.cwd===j.cwd});
|
|
1602
|
+
if(!match)return;
|
|
1603
|
+
var cdEl=$('#watch-countdown-'+match.id);
|
|
1604
|
+
if(cdEl&&j.nextRunAt){
|
|
1605
|
+
cdEl.dataset.nextRunAt=j.nextRunAt;
|
|
1606
|
+
updateCountdown(cdEl);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
startCountdownTimer();
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function startCountdownTimer(){
|
|
1613
|
+
if(_countdownInterval)return;
|
|
1614
|
+
_countdownInterval=setInterval(function(){
|
|
1615
|
+
$$('.watch-card-countdown[data-next-run-at]').forEach(updateCountdown);
|
|
1616
|
+
},1000);
|
|
982
1617
|
}
|
|
983
1618
|
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1619
|
+
function updateCountdown(cdEl){
|
|
1620
|
+
var next=cdEl.dataset.nextRunAt;
|
|
1621
|
+
if(!next){cdEl.textContent='';return}
|
|
1622
|
+
var diff=new Date(next)-Date.now();
|
|
1623
|
+
if(diff<=0){cdEl.textContent='\u23F1 Running...';return}
|
|
1624
|
+
var m=Math.floor(diff/60000);
|
|
1625
|
+
var s=Math.floor((diff%60000)/1000);
|
|
1626
|
+
cdEl.textContent='\u23F1 Next: '+m+'m '+String(s).padStart(2,'0')+'s';
|
|
989
1627
|
}
|
|
990
|
-
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
991
1628
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1629
|
+
function renderEventLog(runs){
|
|
1630
|
+
var container=$('#watchEventLog');
|
|
1631
|
+
if(!container)return;
|
|
1632
|
+
container.textContent='';
|
|
1633
|
+
|
|
1634
|
+
if(!Array.isArray(runs)||!runs.length){
|
|
1635
|
+
container.appendChild(el('div',{style:'padding:16px;text-align:center;color:var(--text3);font-size:11px'},'No runs recorded yet.'));
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Column header row
|
|
1640
|
+
container.appendChild(el('div',{className:'watch-event-row we-header'},[
|
|
1641
|
+
el('span',null,'Time'),
|
|
1642
|
+
el('span',null,'Project'),
|
|
1643
|
+
el('span',null,'Suite'),
|
|
1644
|
+
el('span',{style:'justify-self:center'},'Status'),
|
|
1645
|
+
el('span',{style:'text-align:center'},'Tests'),
|
|
1646
|
+
el('span',{style:'text-align:right'},'Rate'),
|
|
1647
|
+
el('span',{style:'text-align:right'},'Duration'),
|
|
1648
|
+
el('span',{style:'text-align:right'},'Source')
|
|
1649
|
+
]));
|
|
1650
|
+
|
|
1651
|
+
var recent=runs.slice(0,30);
|
|
1652
|
+
recent.forEach(function(r){
|
|
1653
|
+
var rate=parseFloat(r.pass_rate)||0;
|
|
1654
|
+
var badgeCls=r.failed>0?'fail':'pass';
|
|
1655
|
+
var badgeText=r.failed>0?'FAIL':'PASS';
|
|
1656
|
+
|
|
1657
|
+
// Test counts: "5/5" or "3/5 (2 fail)"
|
|
1658
|
+
var countsText=r.passed+'/'+r.total;
|
|
1659
|
+
var countsParts=[el('span',{className:'we-counts-ok'},String(r.passed))];
|
|
1660
|
+
countsParts.push(document.createTextNode('/'+r.total));
|
|
1661
|
+
if(r.failed>0){
|
|
1662
|
+
countsParts.push(document.createTextNode(' ('));
|
|
1663
|
+
countsParts.push(el('span',{style:'color:var(--red)'},r.failed+' fail'));
|
|
1664
|
+
countsParts.push(document.createTextNode(')'));
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// Trigger badge
|
|
1668
|
+
var triggerIcon={'cli':'\u2318','dashboard':'\uD83D\uDCBB','mcp':'\u2699','watch':'\u23F1','api':'\u26A1'};
|
|
1669
|
+
var trigSrc=r.triggered_by||'cli';
|
|
1670
|
+
var trigEl=el('span',{className:'we-trigger',title:'Triggered by: '+trigSrc},(triggerIcon[trigSrc]||'\u2318')+' '+trigSrc);
|
|
1671
|
+
|
|
1672
|
+
var row=el('div',{className:'watch-event-row',style:'cursor:pointer'},[
|
|
1673
|
+
el('span',{className:'watch-event-time'},fdate(r.generated_at)),
|
|
1674
|
+
el('span',{className:'watch-event-project'},r.project_name||'—'),
|
|
1675
|
+
el('span',{className:'watch-event-suite'},r.suite_name||'all'),
|
|
1676
|
+
el('span',{className:'watch-event-result'},[el('span',{className:'badge '+badgeCls},badgeText)]),
|
|
1677
|
+
el('span',{className:'watch-event-counts'},countsParts),
|
|
1678
|
+
el('span',{className:'watch-event-rate'},rate>0?rate.toFixed(0)+'%':'—'),
|
|
1679
|
+
el('span',{className:'watch-event-duration'},r.duration?dur(r.duration):'—'),
|
|
1680
|
+
trigEl
|
|
1681
|
+
]);
|
|
1682
|
+
|
|
1683
|
+
// Click to navigate to run detail
|
|
1684
|
+
(function(run){
|
|
1685
|
+
row.addEventListener('click',function(){
|
|
1686
|
+
S.project=run.project_id;$('#projectSelect').value=run.project_id;
|
|
1687
|
+
showView('runs');
|
|
1688
|
+
refreshRuns();
|
|
1689
|
+
});
|
|
1690
|
+
})(r);
|
|
1691
|
+
|
|
1692
|
+
container.appendChild(row);
|
|
1693
|
+
});
|
|
1004
1694
|
}
|
|
1005
|
-
$('#projectSelect').addEventListener('change',function(){
|
|
1006
|
-
S.project=this.value?parseInt(this.value,10):null;
|
|
1007
|
-
S.selectedRun=null;
|
|
1008
|
-
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();
|
|
1009
|
-
});
|
|
1010
1695
|
|
|
1696
|
+
function startWatchPolling(){
|
|
1697
|
+
if(_watchInterval)return;
|
|
1698
|
+
refreshWatch();
|
|
1699
|
+
_watchInterval=setInterval(refreshWatch,10000);
|
|
1700
|
+
}
|
|
1701
|
+
function stopWatchPolling(){
|
|
1702
|
+
if(_watchInterval){clearInterval(_watchInterval);_watchInterval=null}
|
|
1703
|
+
if(_countdownInterval){clearInterval(_countdownInterval);_countdownInterval=null}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
/* ── view-tests.js ── */
|
|
1011
1708
|
/* ══════════════════════════════════════════════════════════════════
|
|
1012
|
-
Suites
|
|
1709
|
+
Tests View — Suites + Modules + Variables (inner tabs)
|
|
1013
1710
|
══════════════════════════════════════════════════════════════════ */
|
|
1014
1711
|
function refreshSuites(){
|
|
1015
1712
|
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
@@ -1067,15 +1764,75 @@ function renderProjectAccordion(container,project,suites){
|
|
|
1067
1764
|
]);
|
|
1068
1765
|
|
|
1069
1766
|
var wrapper=el('div',{className:'project-accordion'},[header,body]);
|
|
1070
|
-
|
|
1071
|
-
header.addEventListener('click',function(){
|
|
1072
|
-
wrapper.classList.toggle('open');
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1767
|
+
header.addEventListener('click',function(){wrapper.classList.toggle('open')});
|
|
1075
1768
|
container.appendChild(wrapper);
|
|
1076
1769
|
}
|
|
1077
1770
|
|
|
1771
|
+
/* ── Suite Modal ── */
|
|
1078
1772
|
var _suiteCache={};
|
|
1773
|
+
|
|
1774
|
+
function openSuiteModal(suiteName,projectId){
|
|
1775
|
+
var overlay=$('#suiteModalOverlay');
|
|
1776
|
+
var body=$('#suiteModalBody');
|
|
1777
|
+
$('#suiteModalName').textContent=suiteName;
|
|
1778
|
+
$('#suiteModalFile').textContent=suiteName+'.json';
|
|
1779
|
+
body.textContent='';
|
|
1780
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'Loading\u2026'));
|
|
1781
|
+
overlay.classList.add('open');
|
|
1782
|
+
|
|
1783
|
+
$('#suiteModalRun').onclick=function(){triggerRun(suiteName,projectId)};
|
|
1784
|
+
$('#suiteModalClose').onclick=function(){overlay.classList.remove('open')};
|
|
1785
|
+
overlay.addEventListener('click',function(e){if(e.target===overlay)overlay.classList.remove('open')});
|
|
1786
|
+
|
|
1787
|
+
var cacheKey=projectId+'::'+suiteName;
|
|
1788
|
+
var p=_suiteCache[cacheKey]||api('/api/db/projects/'+projectId+'/suites/'+encodeURIComponent(suiteName));
|
|
1789
|
+
_suiteCache[cacheKey]=p;
|
|
1790
|
+
p.then(function(data){
|
|
1791
|
+
body.textContent='';
|
|
1792
|
+
if(!data||!data.tests||!data.tests.length){
|
|
1793
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'No tests found'));
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
data.tests.forEach(function(test){
|
|
1797
|
+
var actionsDiv=el('div',{className:'suite-modal-test-actions'});
|
|
1798
|
+
(test.actions||[]).forEach(function(a,i){
|
|
1799
|
+
var detailContent;
|
|
1800
|
+
if(a.selector&&(a.value||a.text)){
|
|
1801
|
+
detailContent=[el('span',{className:'step-sel'},a.selector),el('span',{className:'step-arrow'},'\u2192'),el('span',{className:'step-val'},a.text||a.value)];
|
|
1802
|
+
} else {
|
|
1803
|
+
detailContent=a.selector||a.value||a.text||'';
|
|
1804
|
+
}
|
|
1805
|
+
actionsDiv.appendChild(el('div',{className:'suite-modal-step'},[
|
|
1806
|
+
el('span',{className:'suite-modal-step-num'},String(i+1)),
|
|
1807
|
+
el('span',{className:'suite-modal-step-type'},a.type),
|
|
1808
|
+
el('span',{className:'suite-modal-step-detail'},detailContent)
|
|
1809
|
+
]));
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
var header=el('div',{className:'suite-modal-test-header'},[
|
|
1813
|
+
el('span',{className:'suite-modal-test-chevron'},'\u25B6'),
|
|
1814
|
+
el('span',{className:'suite-modal-test-name'},test.name),
|
|
1815
|
+
el('span',{className:'suite-modal-test-badge'},(test.actions||[]).length+' actions')
|
|
1816
|
+
]);
|
|
1817
|
+
|
|
1818
|
+
var testEl=el('div',{className:'suite-modal-test'},[header,actionsDiv]);
|
|
1819
|
+
if(test.expect){
|
|
1820
|
+
var expectText=Array.isArray(test.expect)?test.expect.join(', '):test.expect;
|
|
1821
|
+
var expectEl=el('div',{className:'suite-modal-expect'},[
|
|
1822
|
+
el('span',{className:'suite-modal-expect-label'},'Expect:'),
|
|
1823
|
+
document.createTextNode(expectText)
|
|
1824
|
+
]);
|
|
1825
|
+
testEl.insertBefore(expectEl,actionsDiv);
|
|
1826
|
+
}
|
|
1827
|
+
header.addEventListener('click',function(){testEl.classList.toggle('open')});
|
|
1828
|
+
body.appendChild(testEl);
|
|
1829
|
+
});
|
|
1830
|
+
}).catch(function(){
|
|
1831
|
+
body.textContent='';
|
|
1832
|
+
body.appendChild(el('div',{className:'suite-modal-loading',style:'color:var(--red)'},'Failed to load suite'));
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1079
1836
|
function renderSuiteCards(container,suites,projectId){
|
|
1080
1837
|
suites.forEach(function(s){
|
|
1081
1838
|
var tests=el('ul',{className:'suite-card-tests'});
|
|
@@ -1177,9 +1934,79 @@ function renderModules(container,modules){
|
|
|
1177
1934
|
container.appendChild(grid);
|
|
1178
1935
|
}
|
|
1179
1936
|
|
|
1937
|
+
/* ── Variables ── */
|
|
1938
|
+
function refreshVariables(){
|
|
1939
|
+
var container=$('#variablesContainer'),empty=$('#variablesEmpty');
|
|
1940
|
+
container.textContent='';
|
|
1941
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to manage variables.';return}
|
|
1942
|
+
api('/api/db/projects/'+S.project+'/variables').then(function(vars){
|
|
1943
|
+
if(!Array.isArray(vars)||!vars.length){empty.style.display='block';empty.querySelector('p').textContent='No variables set. Add variables to use {{var.KEY}} in your tests.';return}
|
|
1944
|
+
empty.style.display='none';
|
|
1945
|
+
renderVariables(vars);
|
|
1946
|
+
}).catch(function(){empty.style.display='block'});
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
function renderVariables(vars){
|
|
1950
|
+
var container=$('#variablesContainer');
|
|
1951
|
+
var tbl=el('table',{className:'var-table'});
|
|
1952
|
+
var thead=document.createElement('thead');
|
|
1953
|
+
var hr=document.createElement('tr');
|
|
1954
|
+
['Key','Value','Scope','Actions'].forEach(function(h){hr.appendChild(el('th',null,h))});
|
|
1955
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
1956
|
+
var tbody=document.createElement('tbody');
|
|
1957
|
+
vars.forEach(function(v){
|
|
1958
|
+
var tr=document.createElement('tr');
|
|
1959
|
+
tr.appendChild(el('td',null,[el('code',null,v.key)]));
|
|
1960
|
+
tr.appendChild(el('td',{style:'max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},v.is_secret?'\u2022\u2022\u2022\u2022\u2022\u2022':v.value));
|
|
1961
|
+
tr.appendChild(el('td',{style:'color:var(--text3)'},v.scope||'project'));
|
|
1962
|
+
var delBtn=el('button',{className:'btn sm danger',onclick:function(){
|
|
1963
|
+
if(!confirm('Delete variable "'+v.key+'"?'))return;
|
|
1964
|
+
fetch('/api/db/projects/'+S.project+'/variables/'+encodeURIComponent(v.key),{method:'DELETE'}).then(function(){refreshVariables();showToast('Variable deleted','success')}).catch(function(){showToast('Delete failed','error')});
|
|
1965
|
+
}},'\u2715');
|
|
1966
|
+
tr.appendChild(el('td',null,[delBtn]));
|
|
1967
|
+
tbody.appendChild(tr);
|
|
1968
|
+
});
|
|
1969
|
+
tbl.appendChild(tbody);
|
|
1970
|
+
container.appendChild(tbl);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/* ── Variable Add Form ── */
|
|
1974
|
+
$('#btnAddVar').addEventListener('click',function(){
|
|
1975
|
+
var form=$('#varAddForm');
|
|
1976
|
+
if(form.style.display==='none'){
|
|
1977
|
+
form.style.display='';
|
|
1978
|
+
form.textContent='';
|
|
1979
|
+
var keyInput=el('input',{type:'text',placeholder:'KEY',style:'margin-right:8px;width:120px'});
|
|
1980
|
+
var valInput=el('input',{type:'text',placeholder:'Value',style:'margin-right:8px;width:200px'});
|
|
1981
|
+
var secretCheck=el('input',{type:'checkbox',style:'margin-right:4px'});
|
|
1982
|
+
var saveBtn=el('button',{className:'btn sm primary',onclick:function(){
|
|
1983
|
+
var k=keyInput.value.trim(),v=valInput.value;
|
|
1984
|
+
if(!k){showToast('Key is required','error');return}
|
|
1985
|
+
if(!S.project){showToast('Select a project first','error');return}
|
|
1986
|
+
fetch('/api/db/projects/'+S.project+'/variables',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:k,value:v,is_secret:secretCheck.checked})}).then(function(r){return r.json()}).then(function(){
|
|
1987
|
+
form.style.display='none';refreshVariables();showToast('Variable saved','success');
|
|
1988
|
+
}).catch(function(){showToast('Save failed','error')});
|
|
1989
|
+
}},'Save');
|
|
1990
|
+
var cancelBtn=el('button',{className:'btn sm',onclick:function(){form.style.display='none'}},'Cancel');
|
|
1991
|
+
form.appendChild(el('div',{className:'var-add-form',style:'display:flex;align-items:center;gap:8px;flex-wrap:wrap'},[
|
|
1992
|
+
keyInput,valInput,
|
|
1993
|
+
el('label',{style:'font-size:11px;color:var(--text2);display:flex;align-items:center;gap:4px'},[secretCheck,document.createTextNode('Secret')]),
|
|
1994
|
+
saveBtn,cancelBtn
|
|
1995
|
+
]));
|
|
1996
|
+
} else {
|
|
1997
|
+
form.style.display='none';
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
|
|
2001
|
+
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
/* ── view-runs.js ── */
|
|
1180
2005
|
/* ══════════════════════════════════════════════════════════════════
|
|
1181
|
-
Runs
|
|
2006
|
+
Runs View — History + Screenshots + Learnings (inner tabs)
|
|
1182
2007
|
══════════════════════════════════════════════════════════════════ */
|
|
2008
|
+
|
|
2009
|
+
/* ── Filters ── */
|
|
1183
2010
|
$$('.filter-btn').forEach(function(btn){
|
|
1184
2011
|
btn.addEventListener('click',function(){
|
|
1185
2012
|
$$('.filter-btn').forEach(function(b){b.classList.remove('active')});
|
|
@@ -1214,7 +2041,44 @@ function applyRunFilters(){
|
|
|
1214
2041
|
});
|
|
1215
2042
|
}
|
|
1216
2043
|
|
|
2044
|
+
function renderRunsHealthBanner(){
|
|
2045
|
+
var banner=$('#runsHealthBanner');
|
|
2046
|
+
banner.textContent='';
|
|
2047
|
+
var url=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
2048
|
+
fetch(url).then(function(r){return r.json()}).then(function(h){
|
|
2049
|
+
if(!h||!h.passRate)return;
|
|
2050
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2051
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2052
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'dim';
|
|
2053
|
+
var deltaStr=h.trendDelta!==0?(h.trendDelta>0?'+':'')+h.trendDelta+'%':'';
|
|
2054
|
+
|
|
2055
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2056
|
+
el('div',{className:'hb-val '+rateColor},h.passRate+'%'),
|
|
2057
|
+
el('div',{className:'hb-lbl'},'Pass Rate'),
|
|
2058
|
+
el('div',{className:'hb-trend '+trendCls},trendIcon+' '+h.passRateTrend+(deltaStr?' ('+deltaStr+')':''))
|
|
2059
|
+
]));
|
|
2060
|
+
if(h.flakyCount>0){
|
|
2061
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2062
|
+
el('div',{className:'hb-val amber'},String(h.flakyCount)),
|
|
2063
|
+
el('div',{className:'hb-lbl'},'Flaky Tests')
|
|
2064
|
+
]));
|
|
2065
|
+
}
|
|
2066
|
+
if(h.topErrorPattern){
|
|
2067
|
+
var cat=h.topErrorPattern.category||h.topErrorPattern.pattern||'unknown';
|
|
2068
|
+
var pat=cat.replace(/-/g,' ').replace(/\b\w/g,function(c){return c.toUpperCase()});
|
|
2069
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2070
|
+
el('div',{className:'hb-val red',style:'font-size:13px'},pat),
|
|
2071
|
+
el('div',{className:'hb-lbl'},'Top Error ('+h.topErrorPattern.count+'x)')
|
|
2072
|
+
]));
|
|
2073
|
+
}
|
|
2074
|
+
banner.appendChild(el('div',{className:'hb-link',onclick:function(){var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
2075
|
+
el('span',null,'\u2192 View Learnings')
|
|
2076
|
+
]));
|
|
2077
|
+
}).catch(function(){});
|
|
2078
|
+
}
|
|
2079
|
+
|
|
1217
2080
|
function refreshRuns(){
|
|
2081
|
+
renderRunsHealthBanner();
|
|
1218
2082
|
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
1219
2083
|
api(url).then(function(rows){
|
|
1220
2084
|
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
@@ -1228,7 +2092,7 @@ function refreshRuns(){
|
|
|
1228
2092
|
var htr=document.createElement('tr');
|
|
1229
2093
|
var cols=[];
|
|
1230
2094
|
if(!S.project)cols.push('Project');
|
|
1231
|
-
cols=cols.concat(['Suite','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
2095
|
+
cols=cols.concat(['Suite','Driver','Source','Date','Total','Pass','Fail','Rate','Time']);
|
|
1232
2096
|
cols.forEach(function(c){htr.appendChild(el('th',null,c))});
|
|
1233
2097
|
head.textContent='';head.appendChild(htr);
|
|
1234
2098
|
var colSpan=cols.length;
|
|
@@ -1247,6 +2111,7 @@ function refreshRuns(){
|
|
|
1247
2111
|
if(r.id===S.selectedRun)tr.classList.add('expanded');
|
|
1248
2112
|
if(!S.project)tr.appendChild(el('td',{style:'font-weight:600'},r.project_name||'-'));
|
|
1249
2113
|
tr.appendChild(el('td',{style:'color:var(--accent)'},r.suite_name||'all'));
|
|
2114
|
+
var driverTd=document.createElement('td');driverTd.appendChild(createDriverBadge(r.pool_driver));tr.appendChild(driverTd);
|
|
1250
2115
|
var srcTd=document.createElement('td');srcTd.appendChild(createTriggerBadge(r.triggered_by));tr.appendChild(srcTd);
|
|
1251
2116
|
tr.appendChild(el('td',null,fdate(r.generated_at)));
|
|
1252
2117
|
tr.appendChild(el('td',null,String(r.total||0)));
|
|
@@ -1325,9 +2190,7 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
1325
2190
|
loadDetailInline(id,detailTr);
|
|
1326
2191
|
}
|
|
1327
2192
|
|
|
1328
|
-
/*
|
|
1329
|
-
Run Detail (+ Action Narratives, Retry badges, Export)
|
|
1330
|
-
══════════════════════════════════════════════════════════════════ */
|
|
2193
|
+
/* ── Run Detail ── */
|
|
1331
2194
|
function loadDetailInline(id,detailTr){
|
|
1332
2195
|
api('/api/db/runs/'+id).then(function(d){
|
|
1333
2196
|
if(d.error)return;
|
|
@@ -1345,8 +2208,10 @@ function loadDetailInline(id,detailTr){
|
|
|
1345
2208
|
])
|
|
1346
2209
|
]);
|
|
1347
2210
|
var srcBlock=el('div',null,[el('div',{className:'rd-s-label'},'Source'),el('div',{style:'margin-top:4px'},[createTriggerBadge(d.triggeredBy)])]);
|
|
2211
|
+
var drvBlock=el('div',null,[el('div',{className:'rd-s-label'},'Driver'),el('div',{style:'margin-top:4px'},[createDriverBadge(d.poolDriver)])]);
|
|
1348
2212
|
var summ=el('div',{className:'rd-summary'},[
|
|
1349
2213
|
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')]),
|
|
2214
|
+
drvBlock,
|
|
1350
2215
|
srcBlock,
|
|
1351
2216
|
el('div',null,[el('div',{className:'rd-s-label'},'Total'),el('div',{className:'rd-s-val'},String(d.summary.total))]),
|
|
1352
2217
|
el('div',null,[el('div',{className:'rd-s-label'},'Passed'),el('div',{className:'rd-s-val',style:'color:var(--green)'},String(d.summary.passed))]),
|
|
@@ -1356,6 +2221,43 @@ function loadDetailInline(id,detailTr){
|
|
|
1356
2221
|
]);
|
|
1357
2222
|
inner.appendChild(summ);
|
|
1358
2223
|
|
|
2224
|
+
// Insights
|
|
2225
|
+
var insightsContainer=el('div',{className:'rd-insights'});
|
|
2226
|
+
inner.appendChild(insightsContainer);
|
|
2227
|
+
fetch('/api/db/runs/'+id+'/insights').then(function(r){return r.json()}).then(function(ins){
|
|
2228
|
+
if(!ins||ins.error)return;
|
|
2229
|
+
var items=[];
|
|
2230
|
+
var h=ins.health;
|
|
2231
|
+
if(h){
|
|
2232
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2233
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2234
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'';
|
|
2235
|
+
items.push(el('div',{className:'rd-ins-health'},[
|
|
2236
|
+
el('span',{className:'rd-ins-rate '+rateColor},h.passRate+'%'),
|
|
2237
|
+
el('span',{className:'rd-ins-trend '+trendCls},trendIcon+' '+h.passRateTrend),
|
|
2238
|
+
h.flakyCount>0?el('span',{className:'rd-ins-tag amber'},h.flakyCount+' flaky'):null,
|
|
2239
|
+
h.unstableSelectorCount>0?el('span',{className:'rd-ins-tag red'},h.unstableSelectorCount+' unstable sel.'):null
|
|
2240
|
+
]));
|
|
2241
|
+
}
|
|
2242
|
+
var insights=ins.insights||[];
|
|
2243
|
+
insights.forEach(function(i){
|
|
2244
|
+
var icon=i.type==='new-failure'?'\u2718':i.type==='recovered'?'\u2714':i.type==='flaky'?'\u223C':'!';
|
|
2245
|
+
var cls=i.type==='new-failure'?'red':i.type==='recovered'?'green':i.type==='flaky'?'amber':'';
|
|
2246
|
+
items.push(el('div',{className:'rd-ins-item '+cls},[
|
|
2247
|
+
el('span',{className:'rd-ins-icon'},icon),
|
|
2248
|
+
el('span',null,i.message)
|
|
2249
|
+
]));
|
|
2250
|
+
});
|
|
2251
|
+
if(items.length>0){items.forEach(function(it){insightsContainer.appendChild(it)})}
|
|
2252
|
+
else{insightsContainer.style.display='none'}
|
|
2253
|
+
}).catch(function(){insightsContainer.style.display='none'});
|
|
2254
|
+
|
|
2255
|
+
// Pool distribution bar
|
|
2256
|
+
var histPoolTests={};
|
|
2257
|
+
results.forEach(function(r){if(!r.poolUrl)return;histPoolTests[r.name]={poolUrl:r.poolUrl,success:r.success}});
|
|
2258
|
+
var histPoolDist=buildPoolDistribution(histPoolTests);
|
|
2259
|
+
if(histPoolDist)inner.appendChild(histPoolDist);
|
|
2260
|
+
|
|
1359
2261
|
results.forEach(function(r){
|
|
1360
2262
|
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
1361
2263
|
var flaky=r.success&&r.attempt>1;
|
|
@@ -1365,7 +2267,8 @@ function loadDetailInline(id,detailTr){
|
|
|
1365
2267
|
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
1366
2268
|
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
1367
2269
|
|
|
1368
|
-
var
|
|
2270
|
+
var poolEl=r.poolUrl?el('span',{className:'pool-badge'},r.poolUrl.replace('ws://','').replace('wss://','')) :null;
|
|
2271
|
+
var head=el('div',{className:'rd-test-head'},[badges,el('div',{className:'rd-test-name'},[document.createTextNode(r.name),poolEl]),el('div',{className:'rd-test-dur'},d2)]);
|
|
1369
2272
|
var body=el('div',{className:'rd-test-body'});
|
|
1370
2273
|
|
|
1371
2274
|
if(r.maxAttempts>1){body.appendChild(el('div',{className:'rd-retries'},'Attempt '+r.attempt+' of '+r.maxAttempts))}
|
|
@@ -1488,9 +2391,7 @@ function loadDetailInline(id,detailTr){
|
|
|
1488
2391
|
});
|
|
1489
2392
|
}
|
|
1490
2393
|
|
|
1491
|
-
/*
|
|
1492
|
-
Screenshots
|
|
1493
|
-
══════════════════════════════════════════════════════════════════ */
|
|
2394
|
+
/* ── Screenshots ── */
|
|
1494
2395
|
function refreshScreenshots(){
|
|
1495
2396
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
1496
2397
|
gal.textContent='';
|
|
@@ -1535,13 +2436,289 @@ function searchByHash(){
|
|
|
1535
2436
|
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1536
2437
|
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1537
2438
|
|
|
2439
|
+
/* ── Learnings ── */
|
|
2440
|
+
function refreshLearnings(){
|
|
2441
|
+
var days=$('#learningsDays').value||30;
|
|
2442
|
+
var url=S.project?'/api/db/projects/'+S.project+'/learnings?days='+days:'/api/db/learnings?days='+days;
|
|
2443
|
+
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
2444
|
+
if(!data||data.totalRuns===0){
|
|
2445
|
+
$('#learningsEmpty').style.display='block';
|
|
2446
|
+
$('#learnHero').textContent='';$('#learnCards').textContent='';
|
|
2447
|
+
$('#learnTrend').textContent='';$('#learnBottom').textContent='';
|
|
2448
|
+
$('#badgeLearnings').textContent='-';
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
$('#learningsEmpty').style.display='none';
|
|
2452
|
+
S.lastLearningsData=data;
|
|
2453
|
+
var flakyCount=data.flakyTests?data.flakyTests.length:0;
|
|
2454
|
+
var passRate=data.overallPassRate||0;
|
|
2455
|
+
var declining=data.recentTrend&&Array.isArray(data.recentTrend.data||data.recentTrend)&&(function(){
|
|
2456
|
+
var td=data.recentTrend.data||data.recentTrend;
|
|
2457
|
+
if(td.length<2)return false;
|
|
2458
|
+
var last=td[td.length-1].pass_rate;
|
|
2459
|
+
var prior=td.slice(0,-1).reduce(function(s,t){return s+t.pass_rate},0)/(td.length-1);
|
|
2460
|
+
return last-prior<-2;
|
|
2461
|
+
})();
|
|
2462
|
+
if(passRate<70){
|
|
2463
|
+
$('#badgeLearnings').textContent='\u26A0';
|
|
2464
|
+
$('#badgeLearnings').style.background='var(--red-dim)';$('#badgeLearnings').style.color='var(--red)';
|
|
2465
|
+
} else if(flakyCount>0||declining){
|
|
2466
|
+
$('#badgeLearnings').textContent=flakyCount>0?flakyCount:(declining?'\u25BC':'\u2714');
|
|
2467
|
+
$('#badgeLearnings').style.background='var(--amber-dim)';$('#badgeLearnings').style.color='var(--amber)';
|
|
2468
|
+
} else {
|
|
2469
|
+
$('#badgeLearnings').textContent='\u2714';
|
|
2470
|
+
$('#badgeLearnings').style.background='var(--green-dim)';$('#badgeLearnings').style.color='var(--green)';
|
|
2471
|
+
}
|
|
2472
|
+
renderLearnHero(data);
|
|
2473
|
+
renderLearnCards(data);
|
|
2474
|
+
renderLearnTrend(data.recentTrend||[]);
|
|
2475
|
+
renderLearnBottomRow(data);
|
|
2476
|
+
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
function rateColor(v){return v>=90?'var(--green)':v>=70?'var(--amber)':'var(--red)'}
|
|
2480
|
+
function rateClass(v){return v>=90?'good':v>=70?'warn':'bad'}
|
|
2481
|
+
function durFmt(ms){return ms<1000?Math.round(ms)+'ms':(ms/1000).toFixed(1)+'s'}
|
|
2482
|
+
|
|
2483
|
+
function renderLearnHero(d){
|
|
2484
|
+
var c=$('#learnHero');c.textContent='';
|
|
2485
|
+
var wrap=document.createElement('div');wrap.className='learn-hero';
|
|
2486
|
+
var passRate=d.overallPassRate||0;
|
|
2487
|
+
var ns='http://www.w3.org/2000/svg';
|
|
2488
|
+
var ringWrap=document.createElement('div');ringWrap.className='learn-hero-ring';
|
|
2489
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 36 36');
|
|
2490
|
+
var bgCircle=document.createElementNS(ns,'circle');bgCircle.setAttribute('cx','18');bgCircle.setAttribute('cy','18');bgCircle.setAttribute('r','15.9');bgCircle.className.baseVal='learn-hero-ring-bg';svg.appendChild(bgCircle);
|
|
2491
|
+
var fgCircle=document.createElementNS(ns,'circle');fgCircle.setAttribute('cx','18');fgCircle.setAttribute('cy','18');fgCircle.setAttribute('r','15.9');fgCircle.className.baseVal='learn-hero-ring-fg';
|
|
2492
|
+
var circ=2*Math.PI*15.9;fgCircle.setAttribute('stroke-dasharray',circ.toFixed(1));fgCircle.setAttribute('stroke-dashoffset',(circ*(1-passRate/100)).toFixed(1));fgCircle.setAttribute('stroke',rateColor(passRate));
|
|
2493
|
+
svg.appendChild(fgCircle);ringWrap.appendChild(svg);
|
|
2494
|
+
var pctEl=document.createElement('div');pctEl.className='learn-hero-pct';pctEl.style.color=rateColor(passRate);pctEl.textContent=passRate+'%';
|
|
2495
|
+
ringWrap.appendChild(pctEl);wrap.appendChild(ringWrap);
|
|
2496
|
+
|
|
2497
|
+
var stats=document.createElement('div');stats.className='learn-hero-stats';
|
|
2498
|
+
var badSels=d.unstableSelectors?d.unstableSelectors.length:0;
|
|
2499
|
+
var slowTests=d.failingPages?d.failingPages.length:0;
|
|
2500
|
+
var apiIssues=d.apiIssues?d.apiIssues.length:0;
|
|
2501
|
+
var topErr=d.topErrors&&d.topErrors.length>0?d.topErrors[0].occurrence_count:0;
|
|
2502
|
+
var flakyCount=d.flakyTests?d.flakyTests.length:0;
|
|
2503
|
+
var items=[
|
|
2504
|
+
{val:String(d.totalRuns),lbl:'Runs',color:'var(--accent)'},
|
|
2505
|
+
{val:String(d.totalTests),lbl:'Tests',color:'var(--accent)'},
|
|
2506
|
+
{val:durFmt(d.avgDurationMs||0),lbl:'Avg Duration',color:'var(--purple)'},
|
|
2507
|
+
{val:String(flakyCount),lbl:'Flaky',color:flakyCount>0?'var(--amber)':'var(--green)'},
|
|
2508
|
+
{val:String(badSels),lbl:'Bad Selectors',color:badSels>0?'var(--red)':'var(--green)'},
|
|
2509
|
+
{val:String(slowTests),lbl:'Slow Pages',color:slowTests>0?'var(--amber)':'var(--green)'},
|
|
2510
|
+
{val:String(apiIssues),lbl:'API Issues',color:apiIssues>0?'var(--red)':'var(--green)'},
|
|
2511
|
+
{val:String(topErr),lbl:'Top Error Hits',color:topErr>0?'var(--red)':'var(--green)'}
|
|
2512
|
+
];
|
|
2513
|
+
items.forEach(function(it){
|
|
2514
|
+
var statEl=document.createElement('div');statEl.className='learn-hero-stat';
|
|
2515
|
+
var valEl=document.createElement('div');valEl.className='learn-hero-stat-val';valEl.style.color=it.color;valEl.textContent=it.val;
|
|
2516
|
+
var lblEl=document.createElement('div');lblEl.className='learn-hero-stat-lbl';lblEl.textContent=it.lbl;
|
|
2517
|
+
statEl.appendChild(valEl);statEl.appendChild(lblEl);stats.appendChild(statEl);
|
|
2518
|
+
});
|
|
2519
|
+
wrap.appendChild(stats);c.appendChild(wrap);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
function makeLearnItem(label,sub,pct,valText,color){
|
|
2523
|
+
var item=document.createElement('div');item.className='learn-item';
|
|
2524
|
+
var barWrap=document.createElement('div');barWrap.className='learn-item-bar';
|
|
2525
|
+
var lblEl=document.createElement('div');lblEl.className='learn-item-label';
|
|
2526
|
+
var codeEl=document.createElement('code');codeEl.textContent=label;lblEl.appendChild(codeEl);
|
|
2527
|
+
barWrap.appendChild(lblEl);
|
|
2528
|
+
if(sub){var subEl=document.createElement('div');subEl.className='learn-item-sub';subEl.textContent=sub;barWrap.appendChild(subEl)}
|
|
2529
|
+
var bar=document.createElement('div');bar.className='learn-bar';
|
|
2530
|
+
var fill=document.createElement('div');fill.className='learn-bar-fill';fill.style.width=Math.min(pct,100)+'%';fill.style.background=color;
|
|
2531
|
+
bar.appendChild(fill);barWrap.appendChild(bar);
|
|
2532
|
+
item.appendChild(barWrap);
|
|
2533
|
+
var valEl=document.createElement('div');valEl.className='learn-item-val';valEl.style.color=color;valEl.textContent=valText;
|
|
2534
|
+
item.appendChild(valEl);
|
|
2535
|
+
return item;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
function makeLearnCard(icon,title,emptyMsg){
|
|
2539
|
+
var card=document.createElement('div');card.className='learn-card';
|
|
2540
|
+
var titleEl=document.createElement('div');titleEl.className='learn-card-title';
|
|
2541
|
+
var iconEl=document.createElement('span');iconEl.className='lc-icon';iconEl.textContent=icon;
|
|
2542
|
+
titleEl.appendChild(iconEl);titleEl.appendChild(document.createTextNode(title));
|
|
2543
|
+
card.appendChild(titleEl);
|
|
2544
|
+
card._empty=emptyMsg;
|
|
2545
|
+
return card;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
function renderLearnCards(d){
|
|
2549
|
+
var c=$('#learnCards');c.textContent='';
|
|
2550
|
+
|
|
2551
|
+
var selCard=makeLearnCard('\u26A0','Risky Selectors','No unstable selectors');
|
|
2552
|
+
var sels=d.unstableSelectors||[];
|
|
2553
|
+
if(!sels.length){var e1=document.createElement('div');e1.className='learn-card-empty';e1.textContent=selCard._empty;selCard.appendChild(e1)}
|
|
2554
|
+
else{sels.slice(0,5).forEach(function(s){
|
|
2555
|
+
var sel=s.selector.length>40?s.selector.slice(0,37)+'...':s.selector;
|
|
2556
|
+
selCard.appendChild(makeLearnItem(sel,s.action_type+' \u00B7 '+s.total_uses+' uses',parseFloat(s.fail_rate),s.fail_rate+'%',parseFloat(s.fail_rate)>30?'var(--red)':'var(--amber)'));
|
|
2557
|
+
})}
|
|
2558
|
+
c.appendChild(selCard);
|
|
2559
|
+
|
|
2560
|
+
var pageCard=makeLearnCard('\u23F1','Problem Pages','No failing pages');
|
|
2561
|
+
var pages=d.failingPages||[];
|
|
2562
|
+
if(!pages.length){var e2=document.createElement('div');e2.className='learn-card-empty';e2.textContent=pageCard._empty;pageCard.appendChild(e2)}
|
|
2563
|
+
else{pages.slice(0,5).forEach(function(p){
|
|
2564
|
+
pageCard.appendChild(makeLearnItem(p.url_path,p.total_visits+' visits \u00B7 '+p.console_errors+' console errs',parseFloat(p.fail_rate),p.fail_rate+'%',parseFloat(p.fail_rate)>30?'var(--red)':'var(--amber)'));
|
|
2565
|
+
})}
|
|
2566
|
+
c.appendChild(pageCard);
|
|
2567
|
+
|
|
2568
|
+
var flakyCard=makeLearnCard('\u223C','Flaky Tests','No flaky tests detected');
|
|
2569
|
+
var flaky=d.flakyTests||[];
|
|
2570
|
+
if(!flaky.length){var e3=document.createElement('div');e3.className='learn-card-empty';e3.textContent=flakyCard._empty;flakyCard.appendChild(e3)}
|
|
2571
|
+
else{flaky.slice(0,5).forEach(function(f){
|
|
2572
|
+
flakyCard.appendChild(makeLearnItem(f.test_name,'Attempt avg '+f.avg_attempts+' \u00B7 '+f.total_runs+' runs',parseFloat(f.flaky_rate),f.flaky_rate+'%',parseFloat(f.flaky_rate)>30?'var(--red)':'var(--amber)'));
|
|
2573
|
+
})}
|
|
2574
|
+
c.appendChild(flakyCard);
|
|
2575
|
+
|
|
2576
|
+
var apiCard=makeLearnCard('\u21C4','API Issues','No API issues');
|
|
2577
|
+
var apis=d.apiIssues||[];
|
|
2578
|
+
if(!apis.length){var e4=document.createElement('div');e4.className='learn-card-empty';e4.textContent=apiCard._empty;apiCard.appendChild(e4)}
|
|
2579
|
+
else{apis.slice(0,5).forEach(function(a){
|
|
2580
|
+
var ep=a.endpoint.length>40?a.endpoint.slice(0,37)+'...':a.endpoint;
|
|
2581
|
+
apiCard.appendChild(makeLearnItem(ep,a.total_calls+' calls \u00B7 '+durFmt(a.avg_duration_ms),parseFloat(a.error_rate),a.error_rate+'%',parseFloat(a.error_rate)>20?'var(--red)':'var(--amber)'));
|
|
2582
|
+
})}
|
|
2583
|
+
c.appendChild(apiCard);
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
function renderLearnTrend(trend){
|
|
2587
|
+
var container=$('#learnTrend');container.textContent='';
|
|
2588
|
+
if(!trend.length)return;
|
|
2589
|
+
var card=document.createElement('div');card.className='learn-card';
|
|
2590
|
+
var titleEl=document.createElement('div');titleEl.className='learn-card-title';
|
|
2591
|
+
var iconEl=document.createElement('span');iconEl.className='lc-icon';iconEl.textContent='\u2197';
|
|
2592
|
+
titleEl.appendChild(iconEl);titleEl.appendChild(document.createTextNode('Pass Rate Trend'));
|
|
2593
|
+
card.appendChild(titleEl);
|
|
2594
|
+
var chartDiv=document.createElement('div');chartDiv.style.cssText='height:80px;width:100%';
|
|
2595
|
+
var w=100/trend.length;var ns='http://www.w3.org/2000/svg';
|
|
2596
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');svg.style.cssText='width:100%;height:100%';
|
|
2597
|
+
var bg=document.createElementNS(ns,'rect');bg.setAttribute('x','0');bg.setAttribute('y','0');bg.setAttribute('width','100');bg.setAttribute('height','100');bg.setAttribute('fill','var(--surface2)');bg.setAttribute('rx','2');svg.appendChild(bg);
|
|
2598
|
+
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);
|
|
2599
|
+
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
2600
|
+
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);
|
|
2601
|
+
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);
|
|
2602
|
+
trend.forEach(function(t,i){
|
|
2603
|
+
var color=rateColor(t.pass_rate);
|
|
2604
|
+
var circle=document.createElementNS(ns,'circle');circle.setAttribute('cx',''+(i*w+w/2));circle.setAttribute('cy',''+(100-t.pass_rate));circle.setAttribute('r','2.5');circle.setAttribute('fill',color);
|
|
2605
|
+
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
2606
|
+
});
|
|
2607
|
+
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
2608
|
+
var dates=document.createElement('div');dates.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:4px';
|
|
2609
|
+
dates.appendChild(el('span',null,trend[0].date));dates.appendChild(el('span',null,trend[trend.length-1].date));
|
|
2610
|
+
card.appendChild(dates);container.appendChild(card);
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
function renderLearnBottomRow(d){
|
|
2614
|
+
var c=$('#learnBottom');c.textContent='';
|
|
2615
|
+
|
|
2616
|
+
var errCard=makeLearnCard('\u2718','Most Common Errors','No errors recorded');
|
|
2617
|
+
var errors=d.topErrors||[];
|
|
2618
|
+
if(!errors.length){var e1=document.createElement('div');e1.className='learn-card-empty';e1.textContent=errCard._empty;errCard.appendChild(e1)}
|
|
2619
|
+
else{errors.slice(0,5).forEach(function(e){
|
|
2620
|
+
var pat=e.pattern.length>45?e.pattern.slice(0,42)+'...':e.pattern;
|
|
2621
|
+
var maxCount=errors[0].occurrence_count||1;
|
|
2622
|
+
var pct=(e.occurrence_count/maxCount)*100;
|
|
2623
|
+
var verdictEl=document.createElement('div');verdictEl.className='learn-verdict '+rateClass(100-(pct));verdictEl.textContent=e.category.replace(/-/g,' ');
|
|
2624
|
+
var item=makeLearnItem(pat,(e.last_seen||'').split('T')[0]+' \u00B7 '+e.occurrence_count+'x',pct,e.occurrence_count+'x','var(--red)');
|
|
2625
|
+
item.insertBefore(verdictEl,item.lastChild);
|
|
2626
|
+
errCard.appendChild(item);
|
|
2627
|
+
})}
|
|
2628
|
+
c.appendChild(errCard);
|
|
2629
|
+
|
|
2630
|
+
var slowCard=makeLearnCard('\u23F3','Slowest Tests','No slow test data');
|
|
2631
|
+
var trend=d.recentTrend||[];
|
|
2632
|
+
var slowTests=[];
|
|
2633
|
+
if(d.flakyTests){
|
|
2634
|
+
d.flakyTests.forEach(function(f){
|
|
2635
|
+
if(f.avg_duration_ms&&f.avg_duration_ms>2000){slowTests.push({name:f.test_name,dur:f.avg_duration_ms})}
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
if(d.failingPages){
|
|
2639
|
+
d.failingPages.forEach(function(p){
|
|
2640
|
+
if(p.avg_load_time_ms&&p.avg_load_time_ms>3000){slowTests.push({name:p.url_path,dur:p.avg_load_time_ms})}
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
slowTests.sort(function(a,b){return b.dur-a.dur});
|
|
2644
|
+
if(!slowTests.length){var e2=document.createElement('div');e2.className='learn-card-empty';e2.textContent=slowCard._empty;slowCard.appendChild(e2)}
|
|
2645
|
+
else{
|
|
2646
|
+
var maxDur=slowTests[0].dur;
|
|
2647
|
+
slowTests.slice(0,5).forEach(function(t){
|
|
2648
|
+
slowCard.appendChild(makeLearnItem(t.name,'','',durFmt(t.dur),(t.dur/maxDur)*100,t.dur>5000?'var(--red)':'var(--amber)'));
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
c.appendChild(slowCard);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
2655
|
+
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
2656
|
+
|
|
2657
|
+
$('#btnExportLearnings').addEventListener('click',function(){
|
|
2658
|
+
var data=S.lastLearningsData;
|
|
2659
|
+
if(!data){showToast('No learnings data to export','error');return}
|
|
2660
|
+
var md='# E2E Learnings Report\n\n';
|
|
2661
|
+
md+='| Metric | Value |\n|--------|-------|\n';
|
|
2662
|
+
md+='| Total Runs | '+data.totalRuns+' |\n';
|
|
2663
|
+
md+='| Total Tests | '+data.totalTests+' |\n';
|
|
2664
|
+
md+='| Pass Rate | '+data.overallPassRate+'% |\n';
|
|
2665
|
+
md+='| Avg Duration | '+dur(data.avgDurationMs)+' |\n\n';
|
|
2666
|
+
if(data.flakyTests&&data.flakyTests.length){
|
|
2667
|
+
md+='## Flaky Tests\n\n| Test | Flaky Rate | Occurrences |\n|------|-----------|-------------|\n';
|
|
2668
|
+
data.flakyTests.forEach(function(f){md+='| '+f.test_name+' | '+f.flaky_rate+'% | '+f.flaky_count+' |\n'});md+='\n';
|
|
2669
|
+
}
|
|
2670
|
+
if(data.unstableSelectors&&data.unstableSelectors.length){
|
|
2671
|
+
md+='## Unstable Selectors\n\n| Selector | Action | Fail Rate |\n|----------|--------|-----------|\n';
|
|
2672
|
+
data.unstableSelectors.forEach(function(s){md+='| `'+s.selector+'` | '+s.action_type+' | '+s.fail_rate+'% |\n'});md+='\n';
|
|
2673
|
+
}
|
|
2674
|
+
downloadFile('learnings-report.md',md,'text/markdown');
|
|
2675
|
+
showToast('Learnings exported','success');
|
|
2676
|
+
});
|
|
2677
|
+
|
|
2678
|
+
/* ── Modal ── */
|
|
2679
|
+
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
2680
|
+
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
2681
|
+
|
|
2682
|
+
|
|
2683
|
+
/* ── view-live.js ── */
|
|
1538
2684
|
/* ══════════════════════════════════════════════════════════════════
|
|
1539
|
-
Live Execution
|
|
2685
|
+
Live Execution View
|
|
1540
2686
|
══════════════════════════════════════════════════════════════════ */
|
|
1541
|
-
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}renderLive()}
|
|
2687
|
+
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}S.screencastTest=null;renderLive()}
|
|
1542
2688
|
function dismissLiveRun(rid){delete S.liveRuns[rid];renderLive()}
|
|
1543
2689
|
$('#liveClearBtn').addEventListener('click',clearFinishedLiveRuns);
|
|
1544
2690
|
|
|
2691
|
+
// Screencast state
|
|
2692
|
+
S.screencastTest=null;
|
|
2693
|
+
|
|
2694
|
+
$('#screencastSelect').addEventListener('change',function(){
|
|
2695
|
+
S.screencastTest=this.value||null;
|
|
2696
|
+
var img=$('#screencastImg'),ph=$('#screencastPlaceholder');
|
|
2697
|
+
if(S.screencastTest){img.style.display='block';ph.style.display='none';img.src=''}
|
|
2698
|
+
else{img.style.display='none';ph.style.display='flex'}
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2701
|
+
function updateScreencastSelect(){
|
|
2702
|
+
var sel=$('#screencastSelect'),panel=$('#screencastPanel');
|
|
2703
|
+
var runningTests=[];
|
|
2704
|
+
for(var k in S.liveRuns){var r=S.liveRuns[k];for(var n in r.tests){if(n!=='__error'&&r.tests[n].status==='running')runningTests.push(n)}}
|
|
2705
|
+
// Show panel if any run is active
|
|
2706
|
+
var anyActive=false;for(var k2 in S.liveRuns)if(S.liveRuns[k2].on)anyActive=true;
|
|
2707
|
+
panel.style.display=anyActive?'':'none';
|
|
2708
|
+
// Rebuild options
|
|
2709
|
+
var prev=sel.value;
|
|
2710
|
+
while(sel.options.length>1)sel.remove(1);
|
|
2711
|
+
runningTests.forEach(function(n){var o=document.createElement('option');o.value=n;o.textContent=n;sel.appendChild(o)});
|
|
2712
|
+
// Auto-select first running test if nothing selected
|
|
2713
|
+
if(!S.screencastTest&&runningTests.length>0){S.screencastTest=runningTests[0];sel.value=S.screencastTest;$('#screencastImg').style.display='block';$('#screencastPlaceholder').style.display='none'}
|
|
2714
|
+
else if(S.screencastTest&&runningTests.indexOf(S.screencastTest)===-1){
|
|
2715
|
+
// Current test finished — pick next running or clear
|
|
2716
|
+
if(runningTests.length>0){S.screencastTest=runningTests[0];sel.value=S.screencastTest}
|
|
2717
|
+
else{S.screencastTest=null;sel.value='';$('#screencastImg').style.display='none';$('#screencastPlaceholder').style.display='flex';$('#screencastPlaceholder').textContent='No running tests'}
|
|
2718
|
+
}
|
|
2719
|
+
else{sel.value=S.screencastTest||''}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
1545
2722
|
function renderLive(){
|
|
1546
2723
|
var panel=$('#livePanel'),grid=$('#liveTests'),navLive=$('#navLive'),liveEmpty=$('#liveEmpty');
|
|
1547
2724
|
var runs=S.liveRuns;var runIds=Object.keys(runs);
|
|
@@ -1591,6 +2768,9 @@ function renderLive(){
|
|
|
1591
2768
|
dismissBtn
|
|
1592
2769
|
]));
|
|
1593
2770
|
|
|
2771
|
+
var poolDist=buildPoolDistribution(L.tests);
|
|
2772
|
+
if(poolDist)grid.appendChild(poolDist);
|
|
2773
|
+
|
|
1594
2774
|
var testGrid=el('div',{className:'lr-test-grid'});
|
|
1595
2775
|
Object.keys(L.tests).forEach(function(name){
|
|
1596
2776
|
if(name==='__error')return;
|
|
@@ -1609,12 +2789,13 @@ function renderLive(){
|
|
|
1609
2789
|
var durText=a.duration!=null?(a.duration<1000?a.duration+'ms':(a.duration/1000).toFixed(1)+'s'):'';
|
|
1610
2790
|
var retryBadge=null;
|
|
1611
2791
|
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)}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
el('span',{className:'step-
|
|
1615
|
-
el('span',{className:'step-
|
|
2792
|
+
var stepCls='lt-step'+(a.isPoolLog?' pool-log':'');
|
|
2793
|
+
stepsEl.appendChild(el('div',{className:stepCls},[
|
|
2794
|
+
el('span',{className:'step-icon '+(a.isPoolLog?'':a.success?'ok':'fail')},a.isPoolLog?'\uD83D\uDD17':a.success?'\u2714':'\u2718'),
|
|
2795
|
+
el('span',{className:'step-type'},a.isPoolLog?'pool':a.type),
|
|
2796
|
+
el('span',{className:'step-detail'},a.isPoolLog?a.narrative:detail),
|
|
1616
2797
|
retryBadge,
|
|
1617
|
-
el('span',{className:'step-dur'},durText)
|
|
2798
|
+
a.isPoolLog?null:el('span',{className:'step-dur'},durText)
|
|
1618
2799
|
]));
|
|
1619
2800
|
});
|
|
1620
2801
|
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...')]))}
|
|
@@ -1648,11 +2829,18 @@ function renderLive(){
|
|
|
1648
2829
|
ssEl=el('div',{className:'lt-screenshots'},[toggle,ssGridEl]);
|
|
1649
2830
|
}
|
|
1650
2831
|
|
|
2832
|
+
// Screencast focus indicator
|
|
2833
|
+
var scFocusBadge=null;
|
|
2834
|
+
if(t.status==='running'){
|
|
2835
|
+
var isFocused=S.screencastTest===name;
|
|
2836
|
+
scFocusBadge=el('span',{className:'sc-focus-badge'+(isFocused?' active':''),title:'Watch this test',onclick:function(e){e.stopPropagation();S.screencastTest=name;$('#screencastSelect').value=name;$('#screencastImg').style.display='block';$('#screencastPlaceholder').style.display='none';renderLive()}},'\uD83C\uDFA5');
|
|
2837
|
+
}
|
|
1651
2838
|
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
2839
|
+
var poolBadge=t.poolUrl?el('span',{className:'pool-badge'},t.poolUrl.replace('ws://','').replace('wss://','')):null;
|
|
1652
2840
|
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
1653
2841
|
el('div',{className:'lt-name'},[
|
|
1654
2842
|
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
1655
|
-
document.createTextNode(' '+name),serialBadge,summaryEl
|
|
2843
|
+
document.createTextNode(' '+name),scFocusBadge,serialBadge,poolBadge,summaryEl
|
|
1656
2844
|
]),
|
|
1657
2845
|
el('div',{className:'lt-meta'},meta),stepsEl
|
|
1658
2846
|
]);
|
|
@@ -1672,142 +2860,13 @@ function renderLive(){
|
|
|
1672
2860
|
});
|
|
1673
2861
|
grid.appendChild(testGrid);
|
|
1674
2862
|
});
|
|
2863
|
+
updateScreencastSelect();
|
|
1675
2864
|
}
|
|
1676
2865
|
|
|
1677
|
-
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
1678
|
-
|
|
1679
|
-
/* ── Modal ── */
|
|
1680
|
-
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
1681
|
-
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
1682
|
-
|
|
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
2866
|
|
|
2867
|
+
/* ── keyboard.js ── */
|
|
1809
2868
|
/* ══════════════════════════════════════════════════════════════════
|
|
1810
|
-
Keyboard Shortcuts
|
|
2869
|
+
Keyboard Shortcuts (Updated: 1=Watch, 2=Tests, 3=Runs, 4=Live)
|
|
1811
2870
|
══════════════════════════════════════════════════════════════════ */
|
|
1812
2871
|
document.addEventListener('keydown',function(e){
|
|
1813
2872
|
var tag=document.activeElement.tagName;
|
|
@@ -1815,6 +2874,7 @@ document.addEventListener('keydown',function(e){
|
|
|
1815
2874
|
if(e.key==='Escape'){
|
|
1816
2875
|
if($('#kbModal').classList.contains('open')){$('#kbModal').classList.remove('open');return}
|
|
1817
2876
|
if($('#modal').classList.contains('open')){$('#modal').classList.remove('open');return}
|
|
2877
|
+
if($('#suiteModalOverlay').classList.contains('open')){$('#suiteModalOverlay').classList.remove('open');return}
|
|
1818
2878
|
if(S.selectedRun!==null){
|
|
1819
2879
|
var expanded=document.querySelector('#runsBody tr.expanded');
|
|
1820
2880
|
if(expanded){
|
|
@@ -1827,11 +2887,13 @@ document.addEventListener('keydown',function(e){
|
|
|
1827
2887
|
return;
|
|
1828
2888
|
}
|
|
1829
2889
|
if(e.key==='?'){$('#kbModal').classList.toggle('open');return}
|
|
1830
|
-
var viewMap={'1':'
|
|
2890
|
+
var viewMap={'1':'watch','2':'tests','3':'runs','4':'live'};
|
|
1831
2891
|
if(viewMap[e.key]){showView(viewMap[e.key]);return}
|
|
1832
2892
|
if(e.key==='r'){
|
|
1833
|
-
if(S.view==='
|
|
1834
|
-
else if(S.view==='
|
|
2893
|
+
if(S.view==='watch')refreshWatch();
|
|
2894
|
+
else if(S.view==='tests'){refreshSuites();refreshVariables()}
|
|
2895
|
+
else if(S.view==='runs'){refreshRuns();refreshScreenshots();refreshLearnings()}
|
|
2896
|
+
else if(S.view==='live')renderLive();
|
|
1835
2897
|
return;
|
|
1836
2898
|
}
|
|
1837
2899
|
if(S.view==='runs'&&(e.key==='j'||e.key==='k')){
|
|
@@ -1850,9 +2912,12 @@ document.addEventListener('keydown',function(e){
|
|
|
1850
2912
|
});
|
|
1851
2913
|
$('#kbModal').addEventListener('click',function(e){if(e.target===$('#kbModal'))$('#kbModal').classList.remove('open')});
|
|
1852
2914
|
|
|
2915
|
+
|
|
2916
|
+
/* ── init.js ── */
|
|
1853
2917
|
/* ══════════════════════════════════════════════════════════════════
|
|
1854
|
-
Init
|
|
2918
|
+
Init — startup sequence
|
|
1855
2919
|
══════════════════════════════════════════════════════════════════ */
|
|
2920
|
+
initTabs();
|
|
1856
2921
|
connectWS();
|
|
1857
2922
|
refreshStatus();
|
|
1858
2923
|
refreshProjects();
|
|
@@ -1860,8 +2925,10 @@ refreshSuites();
|
|
|
1860
2925
|
refreshRuns();
|
|
1861
2926
|
refreshScreenshots();
|
|
1862
2927
|
refreshLearnings();
|
|
1863
|
-
|
|
2928
|
+
refreshVariables();
|
|
2929
|
+
startWatchPolling();
|
|
1864
2930
|
|
|
2931
|
+
})();
|
|
1865
2932
|
</script>
|
|
1866
2933
|
</body>
|
|
1867
2934
|
</html>
|