@matware/e2e-runner 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +21 -0
- package/.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/OPENCODE.md +166 -0
- package/README.md +581 -55
- package/agents/test-creator.md +54 -1
- package/agents/test-improver.md +37 -0
- package/bin/cli.js +408 -16
- 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 +324 -2
- package/src/ai-generate.js +58 -8
- package/src/config.js +143 -0
- package/src/dashboard.js +145 -13
- package/src/db.js +130 -2
- package/src/index.js +7 -6
- package/src/learner-sqlite.js +304 -0
- package/src/learner.js +8 -3
- package/src/mcp-tools.js +1121 -43
- package/src/module-resolver.js +37 -0
- package/src/narrate.js +37 -0
- package/src/pool-manager.js +223 -0
- package/src/reporter.js +82 -1
- package/src/runner.js +157 -28
- 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/watch.js +384 -0
- package/templates/build-dashboard.js +47 -6
- package/templates/dashboard/js/api.js +60 -0
- package/templates/dashboard/js/init.js +13 -0
- package/templates/dashboard/js/keyboard.js +46 -0
- package/templates/dashboard/js/state.js +40 -0
- package/templates/dashboard/js/toast.js +41 -0
- package/templates/dashboard/js/utils.js +196 -0
- package/templates/dashboard/js/view-live.js +143 -0
- package/templates/dashboard/js/view-runs.js +572 -0
- package/templates/dashboard/js/view-tests.js +294 -0
- package/templates/dashboard/js/view-watch.js +242 -0
- package/templates/dashboard/js/websocket.js +110 -0
- package/templates/dashboard/styles/base.css +69 -0
- package/templates/dashboard/styles/components.css +110 -0
- package/templates/dashboard/styles/view-live.css +74 -0
- package/templates/dashboard/styles/view-runs.css +207 -0
- package/templates/dashboard/styles/view-tests.css +96 -0
- package/templates/dashboard/styles/view-watch.css +53 -0
- package/templates/dashboard/template.html +165 -99
- package/templates/dashboard.html +1596 -541
- 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,24 @@ a{color:var(--accent);text-decoration:none}
|
|
|
51
53
|
.pool-info{font-size:11px;color:var(--text2);line-height:1.7}
|
|
52
54
|
.pool-info strong{color:var(--text)}
|
|
53
55
|
.ws-dot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:4px}
|
|
56
|
+
.pool-list{margin-top:6px;display:flex;flex-direction:column;gap:3px}
|
|
57
|
+
.pool-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--text2);font-family:var(--mono);padding:2px 0}
|
|
58
|
+
.pool-item .pool-dot{width:6px;height:6px;margin-right:0}
|
|
59
|
+
.pool-item strong{color:var(--text);font-weight:500}
|
|
60
|
+
.pool-item .pool-sessions{margin-left:auto;color:var(--text3);font-size:9px}
|
|
61
|
+
|
|
62
|
+
/* ── Sync Status ── */
|
|
63
|
+
.sync-status{padding:12px 16px;border-top:1px solid var(--border)}
|
|
64
|
+
.sync-status .sync-header{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text2)}
|
|
65
|
+
.sync-status .sync-mode{font-size:9px;padding:2px 6px;border-radius:3px;text-transform:uppercase;font-weight:600}
|
|
66
|
+
.sync-status .sync-mode.hub{background:var(--purple-dim);color:var(--purple)}
|
|
67
|
+
.sync-status .sync-mode.agent{background:var(--accent-dim);color:var(--accent)}
|
|
68
|
+
.sync-status .sync-mode.standalone{background:var(--surface3);color:var(--text3)}
|
|
69
|
+
.sync-status .sync-details{margin-top:6px;font-size:10px;color:var(--text3)}
|
|
70
|
+
.sync-status .sync-instances{margin-top:6px;display:flex;flex-wrap:wrap;gap:4px}
|
|
71
|
+
.sync-status .sync-inst{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;border-radius:10px;font-size:9px;background:var(--surface3);border:1px solid var(--border)}
|
|
72
|
+
.sync-status .sync-inst.online{border-color:var(--green);color:var(--green)}
|
|
73
|
+
.sync-status .sync-inst.offline{color:var(--text3)}
|
|
54
74
|
|
|
55
75
|
/* ── Main ── */
|
|
56
76
|
.main{margin-left:232px;flex:1;min-height:100vh;display:flex;flex-direction:column}
|
|
@@ -61,6 +81,18 @@ a{color:var(--accent);text-decoration:none}
|
|
|
61
81
|
.view.active{display:block}
|
|
62
82
|
#view-live.active{display:flex;flex-direction:column;flex:1;min-height:calc(100vh - 0px);padding:16px}
|
|
63
83
|
|
|
84
|
+
/* ── Responsive ── */
|
|
85
|
+
@media(max-width:768px){
|
|
86
|
+
.sidebar{width:60px}.sidebar-logo h1,.sidebar-section-label,.nav-item span:not(.icon):not(.badge),.pool-info,.sidebar select,.sidebar-logo .ver{display:none}
|
|
87
|
+
.nav-item{justify-content:center;padding:12px}.nav-item .icon{width:auto}
|
|
88
|
+
.main{margin-left:60px}
|
|
89
|
+
.suite-grid,.gallery,.module-grid{grid-template-columns:1fr}
|
|
90
|
+
.lr-test-grid{grid-template-columns:1fr}
|
|
91
|
+
.toast-container{right:12px;bottom:12px}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
/* ── components.css ── */
|
|
64
96
|
/* ── Buttons ── */
|
|
65
97
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;white-space:nowrap}
|
|
66
98
|
.btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
@@ -84,24 +116,6 @@ a{color:var(--accent);text-decoration:none}
|
|
|
84
116
|
.stat-val.accent{color:var(--accent)}
|
|
85
117
|
.stat-val.purple{color:var(--purple)}
|
|
86
118
|
|
|
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
119
|
/* ── Tables ── */
|
|
106
120
|
.tbl-wrap{overflow-x:auto}
|
|
107
121
|
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
@@ -110,32 +124,144 @@ td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
|
110
124
|
tbody tr{cursor:pointer;transition:background .1s}
|
|
111
125
|
tbody tr:hover td{background:var(--surface2)}
|
|
112
126
|
tbody tr.selected td{background:var(--accent-dim)}
|
|
127
|
+
|
|
128
|
+
/* ── Badges ── */
|
|
113
129
|
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
114
130
|
.badge.pass{background:var(--green-dim);color:var(--green)}
|
|
115
131
|
.badge.fail{background:var(--red-dim);color:var(--red)}
|
|
116
132
|
.badge.flaky{background:var(--amber-dim);color:var(--amber)}
|
|
117
133
|
.badge.run{background:var(--purple-dim);color:var(--purple)}
|
|
118
134
|
|
|
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}
|
|
135
|
+
/* ── Empty ── */
|
|
136
|
+
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
137
|
+
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
125
138
|
|
|
126
|
-
/* ──
|
|
127
|
-
.
|
|
128
|
-
.
|
|
129
|
-
.
|
|
130
|
-
.project-accordion.open>.project-accordion-header{border-radius:var(--r) var(--r) 0 0;border-bottom-color:transparent;background:var(--surface2)}
|
|
131
|
-
.project-accordion-chevron{font-size:10px;color:var(--text3);transition:transform .2s ease;flex-shrink:0;width:16px;text-align:center}
|
|
132
|
-
.project-accordion.open>.project-accordion-header .project-accordion-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
133
|
-
.project-accordion-name{font-family:var(--sans);font-size:13px;font-weight:600;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
134
|
-
.project-accordion-meta{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
135
|
-
.project-accordion-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--surface3);color:var(--text2)}
|
|
136
|
-
.project-accordion-body{overflow:hidden;max-height:0;transition:max-height .3s ease;border:1px solid var(--border);border-top:none;border-radius:0 0 var(--r) var(--r);background:var(--bg)}
|
|
137
|
-
.project-accordion.open>.project-accordion-body{max-height:5000px}
|
|
139
|
+
/* ── Modal ── */
|
|
140
|
+
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
141
|
+
.modal.open{display:flex}
|
|
142
|
+
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
138
143
|
|
|
144
|
+
/* ── Toast Notifications ── */
|
|
145
|
+
.toast-container{position:fixed;bottom:24px;right:24px;z-index:300;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}
|
|
146
|
+
.toast{padding:10px 16px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;color:#fff;pointer-events:auto;animation:toastIn .3s ease;min-width:200px;max-width:380px;box-shadow:0 8px 24px rgba(0,0,0,.4);display:flex;align-items:center;gap:8px}
|
|
147
|
+
.toast.success{background:var(--green);border:1px solid rgba(255,255,255,.15)}
|
|
148
|
+
.toast.error{background:var(--red);border:1px solid rgba(255,255,255,.15)}
|
|
149
|
+
.toast.info{background:var(--accent);border:1px solid rgba(255,255,255,.15)}
|
|
150
|
+
.toast.fade-out{animation:toastOut .3s ease forwards}
|
|
151
|
+
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}
|
|
152
|
+
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(24px)}}
|
|
153
|
+
.toast.clickable{cursor:pointer}
|
|
154
|
+
.toast.clickable:hover{filter:brightness(1.1)}
|
|
155
|
+
|
|
156
|
+
/* ── Copy Button ── */
|
|
157
|
+
.copy-btn{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;border-radius:4px;font-size:9px;font-family:var(--mono);font-weight:500;color:var(--text3);background:transparent;border:1px solid transparent;cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;flex-shrink:0}
|
|
158
|
+
.copy-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-dim)}
|
|
159
|
+
.copy-btn.copied{color:var(--green);border-color:var(--green);background:var(--green-dim)}
|
|
160
|
+
|
|
161
|
+
/* ── Screenshot Hash Badge ── */
|
|
162
|
+
.ss-hash{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:10px;font-family:var(--mono);font-size:9px;font-weight:500;background:var(--surface3);border:1px solid var(--border);color:var(--text2);cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;vertical-align:middle}
|
|
163
|
+
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
164
|
+
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
165
|
+
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
166
|
+
|
|
167
|
+
/* ── Trigger Source Badges ── */
|
|
168
|
+
.trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
169
|
+
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
170
|
+
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
171
|
+
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
172
|
+
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
173
|
+
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
174
|
+
|
|
175
|
+
/* ── Filter Bar ── */
|
|
176
|
+
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
177
|
+
.filter-btn{padding:5px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text2);font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .15s}
|
|
178
|
+
.filter-btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
179
|
+
.filter-btn.active{background:var(--accent-dim);border-color:var(--accent);color:var(--accent)}
|
|
180
|
+
.filter-bar input{padding:5px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px;max-width:200px}
|
|
181
|
+
.filter-bar input:focus{outline:none;border-color:var(--accent)}
|
|
182
|
+
.filter-bar input::placeholder{color:var(--text3)}
|
|
183
|
+
|
|
184
|
+
/* ── Inner Tabs ── */
|
|
185
|
+
.tab-bar{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:20px}
|
|
186
|
+
.tab-btn{padding:8px 16px;font-family:var(--mono);font-size:11px;font-weight:500;color:var(--text3);cursor:pointer;border:none;background:transparent;border-bottom:2px solid transparent;transition:all .15s}
|
|
187
|
+
.tab-btn:hover{color:var(--text2);background:var(--surface2)}
|
|
188
|
+
.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
189
|
+
.tab-pane{display:none}
|
|
190
|
+
.tab-pane.active{display:block}
|
|
191
|
+
|
|
192
|
+
/* ── Keyboard Shortcuts Modal ── */
|
|
193
|
+
.kb-modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:250;display:none;align-items:center;justify-content:center;padding:24px}
|
|
194
|
+
.kb-modal.open{display:flex}
|
|
195
|
+
.kb-modal-content{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:24px;max-width:420px;width:100%;max-height:80vh;overflow-y:auto}
|
|
196
|
+
.kb-modal-content h2{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
|
197
|
+
.kb-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
198
|
+
.kb-row:last-child{border-bottom:none}
|
|
199
|
+
.kb-key{display:inline-flex;align-items:center;justify-content:center;min-width:24px;padding:2px 8px;border-radius:4px;background:var(--surface3);border:1px solid var(--border);font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent)}
|
|
200
|
+
.kb-desc{font-size:12px;color:var(--text2)}
|
|
201
|
+
|
|
202
|
+
/* ── Serial / Pool Badges ── */
|
|
203
|
+
.serial-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:8px;font-size:9px;font-weight:600;background:var(--amber-dim);color:var(--amber);vertical-align:middle;margin-left:4px}
|
|
204
|
+
.pool-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:8px;font-size:9px;font-weight:600;background:rgba(99,102,241,.15);color:#818cf8;vertical-align:middle;margin-left:4px;font-family:var(--mono);letter-spacing:-.3px}
|
|
205
|
+
.pool-badge::before{content:'\1F517';font-size:8px}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
/* ── view-watch.css ── */
|
|
209
|
+
/* ── Watch View: Project Cards ── */
|
|
210
|
+
.watch-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-bottom:24px}
|
|
211
|
+
.watch-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;transition:border-color .2s,box-shadow .2s}
|
|
212
|
+
.watch-card:hover{border-color:var(--border-hi);box-shadow:0 2px 12px rgba(0,0,0,.25)}
|
|
213
|
+
.watch-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
|
214
|
+
.watch-card-name{font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
215
|
+
.watch-card-icons{display:flex;gap:6px;flex-shrink:0;margin-left:8px}
|
|
216
|
+
.watch-card-icons .btn{padding:3px 8px;font-size:10px}
|
|
217
|
+
|
|
218
|
+
/* ── Sparkline ── */
|
|
219
|
+
.watch-sparkline{height:40px;margin-bottom:10px}
|
|
220
|
+
.watch-sparkline svg{width:100%;height:100%;display:block}
|
|
221
|
+
|
|
222
|
+
/* ── Card Footer ── */
|
|
223
|
+
.watch-card-footer{display:flex;align-items:center;justify-content:space-between;font-size:11px}
|
|
224
|
+
.watch-card-status{display:flex;align-items:center;gap:6px}
|
|
225
|
+
.watch-card-status .status-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
226
|
+
.watch-card-status .status-dot.green{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
227
|
+
.watch-card-status .status-dot.red{background:var(--red);box-shadow:0 0 6px var(--red)}
|
|
228
|
+
.watch-card-status .status-dot.amber{background:var(--amber);box-shadow:0 0 6px var(--amber)}
|
|
229
|
+
.watch-card-status .status-dot.dim{background:var(--text3)}
|
|
230
|
+
.watch-card-rate{font-weight:600}
|
|
231
|
+
.watch-card-rate.green{color:var(--green)}
|
|
232
|
+
.watch-card-rate.amber{color:var(--amber)}
|
|
233
|
+
.watch-card-rate.red{color:var(--red)}
|
|
234
|
+
|
|
235
|
+
.watch-card-meta{display:flex;flex-direction:column;gap:4px;margin-top:10px;font-size:10px;color:var(--text3)}
|
|
236
|
+
.watch-card-meta span{display:flex;align-items:center;gap:6px}
|
|
237
|
+
.watch-card-countdown{color:var(--accent);font-weight:500;font-variant-numeric:tabular-nums}
|
|
238
|
+
.watch-card-commit{font-family:var(--mono);color:var(--text3)}
|
|
239
|
+
|
|
240
|
+
/* ── Event Log ── */
|
|
241
|
+
.watch-event-log{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);overflow:hidden}
|
|
242
|
+
.watch-event-log-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);background:var(--surface2)}
|
|
243
|
+
.watch-event-log-header .title{font-family:var(--sans);font-size:13px;font-weight:600}
|
|
244
|
+
.watch-event-log-body{max-height:400px;overflow-y:auto}
|
|
245
|
+
.watch-event-row{display:grid;grid-template-columns:120px 130px minmax(80px,1fr) 46px 74px 40px 52px 60px;align-items:center;gap:0;padding:7px 16px;border-bottom:1px solid var(--border);font-size:11px;transition:background .1s}
|
|
246
|
+
.watch-event-row:last-child{border-bottom:none}
|
|
247
|
+
.watch-event-row:hover{background:var(--surface2)}
|
|
248
|
+
.watch-event-row.we-header{font-size:9px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.5px;padding:6px 16px;background:var(--surface2);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:1}
|
|
249
|
+
.watch-event-row.we-header:hover{background:var(--surface2)}
|
|
250
|
+
.watch-event-time{color:var(--text3);font-variant-numeric:tabular-nums;font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
251
|
+
.watch-event-project{color:var(--text);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
252
|
+
.watch-event-suite{color:var(--accent);font-family:var(--mono);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px}
|
|
253
|
+
.watch-event-result{justify-self:center}
|
|
254
|
+
.watch-event-counts{font-family:var(--mono);font-size:10px;color:var(--text2);white-space:nowrap;text-align:center}
|
|
255
|
+
.watch-event-counts .we-counts-ok{color:var(--green)}
|
|
256
|
+
.watch-event-rate{font-weight:600;color:var(--text2);font-variant-numeric:tabular-nums;text-align:right}
|
|
257
|
+
.watch-event-duration{color:var(--text3);font-family:var(--mono);font-size:10px;text-align:right}
|
|
258
|
+
.we-trigger{color:var(--text3);font-size:9px;padding:1px 6px;border-radius:8px;background:var(--surface3);white-space:nowrap;text-align:center;justify-self:end}
|
|
259
|
+
|
|
260
|
+
/* ── Watch Table (legacy) ── */
|
|
261
|
+
.watch-jobs-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
/* ── view-tests.css ── */
|
|
139
265
|
/* ── Suite Cards ── */
|
|
140
266
|
.suite-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;padding:16px}
|
|
141
267
|
.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 +324,49 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
198
324
|
.suite-modal-expect{padding:10px 24px 10px 48px;background:var(--green-dim);border-bottom:1px solid var(--border);font-size:11px;color:var(--green);display:flex;align-items:flex-start;gap:8px}
|
|
199
325
|
.suite-modal-expect-label{font-weight:600;flex-shrink:0}
|
|
200
326
|
|
|
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)}
|
|
327
|
+
/* ── Project Accordion ── */
|
|
328
|
+
.project-accordion{margin-bottom:2px}
|
|
329
|
+
.project-accordion-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);cursor:pointer;transition:all .15s;user-select:none}
|
|
330
|
+
.project-accordion-header:hover{background:var(--surface2);border-color:var(--border-hi)}
|
|
331
|
+
.project-accordion.open>.project-accordion-header{border-radius:var(--r) var(--r) 0 0;border-bottom-color:transparent;background:var(--surface2)}
|
|
332
|
+
.project-accordion-chevron{font-size:10px;color:var(--text3);transition:transform .2s ease;flex-shrink:0;width:16px;text-align:center}
|
|
333
|
+
.project-accordion.open>.project-accordion-header .project-accordion-chevron{transform:rotate(90deg);color:var(--accent)}
|
|
334
|
+
.project-accordion-name{font-family:var(--sans);font-size:13px;font-weight:600;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
335
|
+
.project-accordion-meta{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
336
|
+
.project-accordion-badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;background:var(--surface3);color:var(--text2)}
|
|
337
|
+
.project-accordion-body{overflow:hidden;max-height:0;transition:max-height .3s ease;border:1px solid var(--border);border-top:none;border-radius:0 0 var(--r) var(--r);background:var(--bg)}
|
|
338
|
+
.project-accordion.open>.project-accordion-body{max-height:5000px}
|
|
265
339
|
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
.
|
|
269
|
-
|
|
340
|
+
/* ── Module Cards ── */
|
|
341
|
+
.module-section-title{font-family:var(--sans);font-size:14px;font-weight:600;margin:24px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);color:var(--text2);display:flex;align-items:center;gap:8px}
|
|
342
|
+
.module-section-title .mod-icon{color:var(--purple)}
|
|
343
|
+
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
|
344
|
+
.module-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;border-left:3px solid var(--purple);transition:border-color .15s}
|
|
345
|
+
.module-card:hover{border-color:var(--border-hi);border-left-color:var(--purple)}
|
|
346
|
+
.module-card-name{font-weight:600;font-size:13px;color:var(--purple);margin-bottom:4px}
|
|
347
|
+
.module-card-desc{font-size:11px;color:var(--text2);margin-bottom:8px}
|
|
348
|
+
.module-card-meta{font-size:10px;color:var(--text3);display:flex;gap:12px}
|
|
349
|
+
.module-card-params{list-style:none;font-size:10px;color:var(--text2);margin-top:6px}
|
|
350
|
+
.module-card-params li{padding:2px 0}
|
|
351
|
+
.module-card-params li::before{content:'$';color:var(--purple);margin-right:4px}
|
|
352
|
+
|
|
353
|
+
/* ── Variables ── */
|
|
354
|
+
.var-table{width:100%;border-collapse:collapse;font-size:12px}
|
|
355
|
+
.var-table th{text-align:left;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
356
|
+
.var-table td{padding:8px 12px;border-bottom:1px solid var(--border)}
|
|
357
|
+
.var-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:11px}
|
|
358
|
+
.var-add-form{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;margin-bottom:16px}
|
|
359
|
+
.var-add-form input,.var-add-form select{padding:6px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
360
|
+
.var-add-form input:focus,.var-add-form select:focus{outline:none;border-color:var(--accent)}
|
|
270
361
|
|
|
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}
|
|
362
|
+
|
|
363
|
+
/* ── view-runs.css ── */
|
|
364
|
+
/* ── Trend Chart ── */
|
|
365
|
+
.chart{display:flex;align-items:flex-end;gap:3px;height:60px}
|
|
366
|
+
.chart-bar{flex:1;min-width:3px;max-width:16px;border-radius:2px 2px 0 0;cursor:pointer;position:relative;transition:opacity .15s}
|
|
367
|
+
.chart-bar:hover{opacity:.75}
|
|
368
|
+
.chart-bar .tip{display:none;position:absolute;bottom:calc(100% + 4px);left:50%;transform:translateX(-50%);background:var(--surface3);border:1px solid var(--border);padding:4px 8px;border-radius:4px;font-size:10px;white-space:nowrap;z-index:10;pointer-events:none}
|
|
369
|
+
.chart-bar:hover .tip{display:block}
|
|
289
370
|
|
|
290
371
|
/* ── Inline Run Detail ── */
|
|
291
372
|
.run-detail-row td{padding:0!important;border-bottom:2px solid var(--purple)}
|
|
@@ -312,6 +393,8 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
312
393
|
.rd-test-body{padding:16px}
|
|
313
394
|
.rd-retries{font-size:11px;color:var(--amber);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
314
395
|
.rd-error-msg{font-size:12px;color:var(--red);padding:10px 14px;background:var(--red-dim);border-radius:var(--r);margin-bottom:12px;word-break:break-all;border-left:3px solid var(--red);line-height:1.5;position:relative;padding-right:60px}
|
|
396
|
+
.rd-error-msg .copy-btn{position:absolute;top:8px;right:8px;opacity:0}
|
|
397
|
+
.rd-error-msg:hover .copy-btn{opacity:1}
|
|
315
398
|
.rd-shots{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
|
|
316
399
|
.rd-shot{width:140px;border-radius:6px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:all .2s;background:var(--surface2)}
|
|
317
400
|
.rd-shot:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 16px rgba(0,0,0,.35)}
|
|
@@ -326,6 +409,8 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
326
409
|
.rd-log-item{font-size:11px;padding:4px 10px;border-left:2px solid var(--border);margin-bottom:2px;color:var(--text2);word-break:break-all;line-height:1.5}
|
|
327
410
|
.rd-log-item.error{border-left-color:var(--red);color:var(--red)}
|
|
328
411
|
.rd-log-item.warning,.rd-log-item.warn{border-left-color:var(--amber);color:var(--amber)}
|
|
412
|
+
|
|
413
|
+
/* ── Network Panel ── */
|
|
329
414
|
.rd-net-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
330
415
|
.rd-net-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
331
416
|
.rd-net-head:hover{background:var(--surface3)}
|
|
@@ -359,10 +444,19 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
359
444
|
.rd-net-status.s2xx{color:var(--green)}
|
|
360
445
|
.rd-net-status.s3xx{color:var(--amber)}
|
|
361
446
|
.rd-net-status.s4xx,.rd-net-status.s5xx{color:var(--red)}
|
|
447
|
+
.rd-net-op{flex-shrink:0;padding:1px 7px;border-radius:3px;font-size:9px;font-weight:600;background:var(--purple-dim,rgba(168,85,247,.12));color:var(--purple,#a855f7);font-family:var(--sans);letter-spacing:.01em;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
362
448
|
.rd-net-url{flex:1;min-width:0;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
363
449
|
.rd-net-dur{width:60px;flex-shrink:0;text-align:right;color:var(--text3);font-variant-numeric:tabular-nums}
|
|
364
450
|
.rd-net-detail{display:none;border-bottom:1px solid var(--border);background:var(--bg);overflow:hidden}
|
|
365
451
|
.rd-net-row.open+.rd-net-detail{display:block}
|
|
452
|
+
.rd-net-row .copy-btn{opacity:0;padding:1px 6px}
|
|
453
|
+
.rd-net-row:hover .copy-btn{opacity:1}
|
|
454
|
+
.rd-net-body .rd-log-item{padding:6px 14px;margin-bottom:0;border-left:3px solid var(--border);border-bottom:1px solid var(--border);font-size:11px}
|
|
455
|
+
.rd-net-body .rd-log-item:last-child{border-bottom:none}
|
|
456
|
+
.rd-net-body .rd-log-item.error{border-left-color:var(--red)}
|
|
457
|
+
.rd-net-body .rd-log-item.warn,.rd-net-body .rd-log-item.warning{border-left-color:var(--amber)}
|
|
458
|
+
|
|
459
|
+
/* ── Network Detail Sections ── */
|
|
366
460
|
.rd-nd-section{border-bottom:1px solid var(--border)}
|
|
367
461
|
.rd-nd-section:last-child{border-bottom:none}
|
|
368
462
|
.rd-nd-toggle{display:flex;align-items:center;gap:8px;padding:8px 16px;cursor:pointer;user-select:none;transition:background .15s;font-size:10px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:.06em}
|
|
@@ -379,87 +473,12 @@ tbody tr.selected td{background:var(--accent-dim)}
|
|
|
379
473
|
.rd-hdr-key{padding:3px 12px 3px 0;color:var(--accent);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
380
474
|
.rd-hdr-val{padding:3px 0;color:var(--text2);border-bottom:1px solid var(--border);word-break:break-all}
|
|
381
475
|
.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)}
|
|
476
|
+
|
|
393
477
|
tr.expanded td{background:var(--surface2)!important}
|
|
394
478
|
tr.expanded td:first-child{position:relative}
|
|
395
479
|
tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--purple)}
|
|
396
480
|
|
|
397
|
-
/* ──
|
|
398
|
-
.ss-hash{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:10px;font-family:var(--mono);font-size:9px;font-weight:500;background:var(--surface3);border:1px solid var(--border);color:var(--text2);cursor:pointer;transition:all .15s;user-select:none;white-space:nowrap;vertical-align:middle}
|
|
399
|
-
.ss-hash:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}
|
|
400
|
-
.ss-hash.copied{border-color:var(--green);color:var(--green);background:var(--green-dim)}
|
|
401
|
-
.ss-hash .ss-icon{font-size:10px;line-height:1}
|
|
402
|
-
|
|
403
|
-
/* ── Trigger Source Badges ── */
|
|
404
|
-
.trigger-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600;font-family:var(--mono);white-space:nowrap}
|
|
405
|
-
.trigger-badge.src-dashboard{background:rgba(127,140,162,.10);color:var(--text2)}
|
|
406
|
-
.trigger-badge.src-mcp{background:var(--purple-dim);color:var(--purple)}
|
|
407
|
-
.trigger-badge.src-cli{background:var(--accent-dim);color:var(--accent)}
|
|
408
|
-
.trigger-badge.src-unknown{background:rgba(70,75,98,.15);color:var(--text3)}
|
|
409
|
-
.trigger-badge .trig-icon{font-size:11px;line-height:1}
|
|
410
|
-
|
|
411
|
-
/* ── Empty ── */
|
|
412
|
-
.empty{text-align:center;padding:48px 24px;color:var(--text3)}
|
|
413
|
-
.empty-icon{font-size:36px;margin-bottom:8px;opacity:.5}
|
|
414
|
-
|
|
415
|
-
/* ── Modal ── */
|
|
416
|
-
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:200;display:none;align-items:center;justify-content:center;padding:24px;cursor:pointer}
|
|
417
|
-
.modal.open{display:flex}
|
|
418
|
-
.modal img{max-width:100%;max-height:90vh;border-radius:var(--r);cursor:default}
|
|
419
|
-
|
|
420
|
-
/* ── Toast Notifications (NEW) ── */
|
|
421
|
-
.toast-container{position:fixed;bottom:24px;right:24px;z-index:300;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}
|
|
422
|
-
.toast{padding:10px 16px;border-radius:var(--r);font-family:var(--mono);font-size:11px;font-weight:500;color:#fff;pointer-events:auto;animation:toastIn .3s ease;min-width:200px;max-width:380px;box-shadow:0 8px 24px rgba(0,0,0,.4);display:flex;align-items:center;gap:8px}
|
|
423
|
-
.toast.success{background:var(--green);border:1px solid rgba(255,255,255,.15)}
|
|
424
|
-
.toast.error{background:var(--red);border:1px solid rgba(255,255,255,.15)}
|
|
425
|
-
.toast.info{background:var(--accent);border:1px solid rgba(255,255,255,.15)}
|
|
426
|
-
.toast.fade-out{animation:toastOut .3s ease forwards}
|
|
427
|
-
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}
|
|
428
|
-
@keyframes toastOut{from{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(24px)}}
|
|
429
|
-
|
|
430
|
-
/* ── Filter Bar (NEW) ── */
|
|
431
|
-
.filter-bar{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
|
|
432
|
-
.filter-btn{padding:5px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text2);font-family:var(--mono);font-size:11px;cursor:pointer;transition:all .15s}
|
|
433
|
-
.filter-btn:hover{background:var(--surface3);border-color:var(--border-hi)}
|
|
434
|
-
.filter-btn.active{background:var(--accent-dim);border-color:var(--accent);color:var(--accent)}
|
|
435
|
-
.filter-bar input{padding:5px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px;max-width:200px}
|
|
436
|
-
.filter-bar input:focus{outline:none;border-color:var(--accent)}
|
|
437
|
-
.filter-bar input::placeholder{color:var(--text3)}
|
|
438
|
-
|
|
439
|
-
/* ── Module Cards (NEW) ── */
|
|
440
|
-
.module-section-title{font-family:var(--sans);font-size:14px;font-weight:600;margin:24px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);color:var(--text2);display:flex;align-items:center;gap:8px}
|
|
441
|
-
.module-section-title .mod-icon{color:var(--purple)}
|
|
442
|
-
.module-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}
|
|
443
|
-
.module-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:16px;border-left:3px solid var(--purple);transition:border-color .15s}
|
|
444
|
-
.module-card:hover{border-color:var(--border-hi);border-left-color:var(--purple)}
|
|
445
|
-
.module-card-name{font-weight:600;font-size:13px;color:var(--purple);margin-bottom:4px}
|
|
446
|
-
.module-card-desc{font-size:11px;color:var(--text2);margin-bottom:8px}
|
|
447
|
-
.module-card-meta{font-size:10px;color:var(--text3);display:flex;gap:12px}
|
|
448
|
-
.module-card-params{list-style:none;font-size:10px;color:var(--text2);margin-top:6px}
|
|
449
|
-
.module-card-params li{padding:2px 0}
|
|
450
|
-
.module-card-params li::before{content:'$';color:var(--purple);margin-right:4px}
|
|
451
|
-
|
|
452
|
-
/* ── Keyboard Shortcuts Modal (NEW) ── */
|
|
453
|
-
.kb-modal{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:250;display:none;align-items:center;justify-content:center;padding:24px}
|
|
454
|
-
.kb-modal.open{display:flex}
|
|
455
|
-
.kb-modal-content{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:24px;max-width:420px;width:100%;max-height:80vh;overflow-y:auto}
|
|
456
|
-
.kb-modal-content h2{font-family:var(--sans);font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)}
|
|
457
|
-
.kb-row{display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
458
|
-
.kb-row:last-child{border-bottom:none}
|
|
459
|
-
.kb-key{display:inline-flex;align-items:center;justify-content:center;min-width:24px;padding:2px 8px;border-radius:4px;background:var(--surface3);border:1px solid var(--border);font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent)}
|
|
460
|
-
.kb-desc{font-size:12px;color:var(--text2)}
|
|
461
|
-
|
|
462
|
-
/* ── Actions Panel in Run Detail (NEW) ── */
|
|
481
|
+
/* ── Actions Panel in Run Detail ── */
|
|
463
482
|
.rd-actions-panel{margin-top:4px;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--surface)}
|
|
464
483
|
.rd-actions-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface2);cursor:pointer;user-select:none;transition:background .15s}
|
|
465
484
|
.rd-actions-head:hover{background:var(--surface3)}
|
|
@@ -468,18 +487,164 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
468
487
|
.rd-actions-body{display:none;max-height:500px;overflow-y:auto;padding:8px 14px}
|
|
469
488
|
.rd-actions-head.open~.rd-actions-body{display:block}
|
|
470
489
|
|
|
471
|
-
/* ──
|
|
472
|
-
.
|
|
490
|
+
/* ── Run Detail Insights ── */
|
|
491
|
+
.rd-insights{margin-bottom:16px;display:flex;flex-direction:column;gap:6px}
|
|
492
|
+
.rd-insights:empty{display:none}
|
|
493
|
+
.rd-ins-health{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r);font-size:12px}
|
|
494
|
+
.rd-ins-rate{font-size:18px;font-weight:700}
|
|
495
|
+
.rd-ins-rate.green{color:var(--green)}
|
|
496
|
+
.rd-ins-rate.amber{color:var(--amber)}
|
|
497
|
+
.rd-ins-rate.red{color:var(--red)}
|
|
498
|
+
.rd-ins-trend{font-size:11px;color:var(--text2)}
|
|
499
|
+
.rd-ins-trend.green{color:var(--green)}
|
|
500
|
+
.rd-ins-trend.red{color:var(--red)}
|
|
501
|
+
.rd-ins-tag{padding:2px 8px;border-radius:10px;font-size:10px;font-weight:600}
|
|
502
|
+
.rd-ins-tag.amber{background:var(--amber-dim);color:var(--amber)}
|
|
503
|
+
.rd-ins-tag.red{background:var(--red-dim);color:var(--red)}
|
|
504
|
+
.rd-ins-item{display:flex;align-items:flex-start;gap:8px;padding:6px 12px;border-radius:var(--r);font-size:11px;color:var(--text2);border-left:3px solid var(--border)}
|
|
505
|
+
.rd-ins-item.red{border-left-color:var(--red);background:var(--red-dim)}
|
|
506
|
+
.rd-ins-item.green{border-left-color:var(--green);background:var(--green-dim)}
|
|
507
|
+
.rd-ins-item.amber{border-left-color:var(--amber);background:var(--amber-dim)}
|
|
508
|
+
.rd-ins-icon{font-size:12px;flex-shrink:0;width:14px;text-align:center}
|
|
509
|
+
|
|
510
|
+
/* ── Health Banner ── */
|
|
511
|
+
.health-banner{display:flex;align-items:center;gap:1px;background:var(--border);border-radius:var(--r);overflow:hidden;margin-bottom:20px}
|
|
512
|
+
.health-banner:empty{display:none}
|
|
513
|
+
.hb-item{flex:1;background:var(--surface);padding:12px 16px;text-align:center;min-width:0;transition:background .15s}
|
|
514
|
+
.hb-item:hover{background:var(--surface2)}
|
|
515
|
+
.hb-val{font-size:20px;font-weight:700;margin-bottom:2px}
|
|
516
|
+
.hb-val.green{color:var(--green)}
|
|
517
|
+
.hb-val.amber{color:var(--amber)}
|
|
518
|
+
.hb-val.red{color:var(--red)}
|
|
519
|
+
.hb-val.accent{color:var(--accent)}
|
|
520
|
+
.hb-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
521
|
+
.hb-trend{font-size:10px;margin-top:2px}
|
|
522
|
+
.hb-trend.green{color:var(--green)}
|
|
523
|
+
.hb-trend.red{color:var(--red)}
|
|
524
|
+
.hb-trend.dim{color:var(--text3)}
|
|
525
|
+
.hb-link{background:var(--surface);padding:12px 16px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s;min-width:100px}
|
|
526
|
+
.hb-link:hover{background:var(--accent-dim)}
|
|
527
|
+
.hb-link span{font-size:11px;color:var(--accent);font-weight:600}
|
|
473
528
|
|
|
474
|
-
/* ──
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
529
|
+
/* ── Screenshots ── */
|
|
530
|
+
.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
|
531
|
+
.gallery-item{background:var(--surface2);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;cursor:pointer;transition:border-color .15s}
|
|
532
|
+
.gallery-item:hover{border-color:var(--accent)}
|
|
533
|
+
.gallery-item img{width:100%;height:150px;object-fit:cover;display:block}
|
|
534
|
+
.gallery-item .cap{padding:6px 10px;font-size:10px;color:var(--text2);display:flex;align-items:center;gap:4px}
|
|
535
|
+
.gallery-item .cap .cap-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
|
|
536
|
+
.gallery-item .cap .ss-hash{flex-shrink:0}
|
|
537
|
+
.ss-search{display:flex;gap:8px;margin-bottom:16px;align-items:center}
|
|
538
|
+
.ss-search input{flex:1;max-width:320px;padding:8px 12px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px}
|
|
539
|
+
.ss-search input:focus{outline:none;border-color:var(--accent)}
|
|
540
|
+
.ss-search input::placeholder{color:var(--text3)}
|
|
541
|
+
.ss-search button{padding:8px 16px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:12px;cursor:pointer;transition:background .15s,border-color .15s}
|
|
542
|
+
.ss-search button:hover{background:var(--surface3);border-color:var(--accent)}
|
|
543
|
+
.ss-search-result{margin-bottom:20px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r)}
|
|
544
|
+
.ss-search-result img{max-width:100%;max-height:500px;border-radius:var(--r);cursor:pointer;display:block;margin-top:8px}
|
|
545
|
+
.ss-search-result .ss-result-label{font-size:11px;color:var(--text2);display:flex;align-items:center;gap:6px}
|
|
546
|
+
.ss-search-error{font-size:11px;color:var(--red);margin-bottom:12px}
|
|
547
|
+
|
|
548
|
+
/* ── Learnings ── */
|
|
549
|
+
.learn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;margin-bottom:20px}
|
|
550
|
+
.learn-stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:14px;text-align:center}
|
|
551
|
+
.learn-stat-val{font-size:24px;font-weight:700;margin-bottom:2px}
|
|
552
|
+
.learn-stat-lbl{font-size:9px;color:var(--text3);text-transform:uppercase;letter-spacing:.1em}
|
|
553
|
+
.learn-section{margin-bottom:20px}
|
|
554
|
+
.learn-section-title{font-family:var(--sans);font-size:13px;font-weight:600;margin-bottom:10px;color:var(--text)}
|
|
555
|
+
.learn-table{width:100%;border-collapse:collapse;font-size:11px}
|
|
556
|
+
.learn-table th{text-align:left;font-size:9px;font-weight:600;color:var(--text3);letter-spacing:.08em;text-transform:uppercase;padding:6px 10px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}
|
|
557
|
+
.learn-table th:hover{color:var(--text2)}
|
|
558
|
+
.learn-table th.sorted::after{content:' \25B2';font-size:8px}
|
|
559
|
+
.learn-table th.sorted.desc::after{content:' \25BC'}
|
|
560
|
+
.learn-table td{padding:6px 10px;border-bottom:1px solid var(--border);color:var(--text2)}
|
|
561
|
+
.learn-table td code{background:var(--surface3);padding:1px 5px;border-radius:3px;font-size:10px;color:var(--text)}
|
|
562
|
+
.learn-table tbody tr:hover td{background:var(--surface2);color:var(--text)}
|
|
563
|
+
.learn-trend-chart{width:100%;height:100px;margin-bottom:20px}
|
|
564
|
+
.learn-trend-chart svg{width:100%;height:100%}
|
|
565
|
+
|
|
566
|
+
/* ── Pool Distribution ── */
|
|
567
|
+
.pool-dist{display:flex;align-items:stretch;gap:0;border-radius:6px;overflow:hidden;height:22px;margin:8px 0;font-size:10px;font-weight:600;font-family:var(--mono)}
|
|
568
|
+
.pool-dist-seg{display:flex;align-items:center;justify-content:center;gap:4px;padding:0 8px;color:#fff;white-space:nowrap;min-width:40px;transition:flex .3s}
|
|
569
|
+
.pool-dist-legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin:4px 0 8px;font-size:10px;font-family:var(--mono);color:var(--text2)}
|
|
570
|
+
.pool-dist-legend span::before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:4px;vertical-align:middle}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
/* ── view-live.css ── */
|
|
574
|
+
/* ── Live Execution ── */
|
|
575
|
+
.live-panel{display:none;background:var(--surface);border:1px solid var(--purple);border-radius:var(--r);overflow:hidden;animation:fadeSlide .3s ease;flex-direction:column}
|
|
576
|
+
.live-panel.active{display:flex;flex:1;min-height:0}
|
|
577
|
+
@keyframes fadeSlide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
|
578
|
+
.live-header{padding:14px 16px;display:flex;align-items:center;gap:16px;border-bottom:1px solid var(--border);background:var(--purple-dim)}
|
|
579
|
+
.live-header .label{font-weight:600;color:var(--purple);font-size:12px;display:flex;align-items:center;gap:8px}
|
|
580
|
+
.live-header .label .dot{width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
581
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
582
|
+
.live-project{display:flex;align-items:center;padding:2px 10px;background:rgba(255,255,255,.05);border-radius:4px;border:1px solid var(--border)}
|
|
583
|
+
.live-stats{display:flex;gap:16px;font-size:11px;color:var(--text2);margin-left:auto}
|
|
584
|
+
.live-stats span strong{color:var(--text)}
|
|
585
|
+
.live-progress{height:3px;background:var(--surface3)}
|
|
586
|
+
.live-progress-fill{height:100%;background:var(--purple);transition:width .4s;border-radius:0 2px 2px 0}
|
|
587
|
+
.live-tests{padding:12px 16px;display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;min-height:0}
|
|
588
|
+
.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)}
|
|
589
|
+
.live-test.running{border-left-color:var(--purple)}
|
|
590
|
+
.live-test.passed{border-left-color:var(--green)}
|
|
591
|
+
.live-test.failed{border-left-color:var(--red)}
|
|
592
|
+
.live-test.collapsed{cursor:pointer;padding:6px 12px}
|
|
593
|
+
.live-test.collapsed:hover{background:var(--surface3)}
|
|
594
|
+
.live-test.collapsed .lt-meta,.live-test.collapsed .lt-actions,.live-test.collapsed .lt-screenshots{display:none}
|
|
595
|
+
.live-test.collapsed .lt-name{margin-bottom:0}
|
|
596
|
+
.live-test.collapsed .lt-summary{display:flex}
|
|
597
|
+
.live-test .lt-name{font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
|
|
598
|
+
.live-test .lt-summary{display:none;align-items:center;gap:8px;margin-left:auto;font-size:10px;color:var(--text3);font-family:var(--mono)}
|
|
599
|
+
.live-test .lt-summary .lt-dur{color:var(--text2)}
|
|
600
|
+
.live-test .lt-summary .lt-expand{color:var(--purple);font-size:9px;opacity:.6}
|
|
601
|
+
.live-test .lt-meta{color:var(--text2);font-size:10px;margin-bottom:6px}
|
|
602
|
+
.live-test .lt-icon{font-size:12px}
|
|
603
|
+
.lt-actions{overflow-y:auto;border-top:1px solid var(--border);padding-top:6px;margin-top:4px}
|
|
604
|
+
.lt-step{display:flex;align-items:flex-start;gap:6px;padding:2px 0;font-size:10px;font-family:var(--mono);line-height:1.4}
|
|
605
|
+
.lt-step .step-icon{flex-shrink:0;width:14px;text-align:center}
|
|
606
|
+
.lt-step .step-icon.ok{color:var(--green)}
|
|
607
|
+
.lt-step .step-icon.fail{color:var(--red)}
|
|
608
|
+
.lt-step .step-icon.run{color:var(--purple)}
|
|
609
|
+
.lt-step .step-type{color:var(--purple);font-weight:600;flex-shrink:0}
|
|
610
|
+
.lt-step .step-detail{color:var(--text2);flex:1;min-width:0;white-space:pre-wrap;word-break:break-word}
|
|
611
|
+
.lt-step .step-dur{color:var(--text3);flex-shrink:0;margin-left:auto}
|
|
612
|
+
.lt-step.pool-log{background:rgba(99,102,241,.06);border-left:2px solid rgba(99,102,241,.3);padding-left:8px}
|
|
613
|
+
.lt-step.pool-log .step-icon{color:#818cf8}
|
|
614
|
+
.lt-step.pool-log .step-type{color:#818cf8;font-weight:600}
|
|
615
|
+
.lt-screenshots{border-top:1px solid var(--border);margin-top:6px;padding-top:6px}
|
|
616
|
+
.lt-screenshots-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:10px;color:var(--text3);font-family:var(--mono);padding:2px 0;user-select:none}
|
|
617
|
+
.lt-screenshots-toggle:hover{color:var(--text)}
|
|
618
|
+
.lt-screenshots-toggle .ss-arrow{transition:transform .2s;font-size:8px}
|
|
619
|
+
.lt-screenshots-toggle.open .ss-arrow{transform:rotate(90deg)}
|
|
620
|
+
.lt-screenshots-grid{display:none;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:6px;padding-top:6px}
|
|
621
|
+
.lt-screenshots-toggle.open+.lt-screenshots-grid{display:grid}
|
|
622
|
+
.lt-ss-thumb{position:relative;border-radius:4px;overflow:hidden;border:1px solid var(--border);cursor:pointer;aspect-ratio:16/10;background:var(--surface2)}
|
|
623
|
+
.lt-ss-thumb img{width:100%;height:100%;object-fit:cover;display:block}
|
|
624
|
+
.lt-ss-thumb:hover{border-color:var(--purple);box-shadow:0 0 0 1px var(--purple)}
|
|
625
|
+
.lt-ss-label{font-size:8px;color:var(--text3);font-family:var(--mono);text-align:center;padding:2px 2px 0;display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap}
|
|
626
|
+
|
|
627
|
+
/* ── Live Run Sections ── */
|
|
628
|
+
.lr-section-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;margin:6px 0 2px;border-radius:6px;font-family:var(--mono);font-size:12px;font-weight:700;border-left:3px solid var(--purple)}
|
|
629
|
+
.lr-section-header.running{border-color:var(--purple);background:rgba(139,92,246,.08);color:var(--purple)}
|
|
630
|
+
.lr-section-header.pass{border-color:var(--green);background:rgba(52,211,153,.08);color:var(--green)}
|
|
631
|
+
.lr-section-header.fail{border-color:var(--red);background:rgba(248,113,113,.08);color:var(--red)}
|
|
632
|
+
.lr-section-stats{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--text2);font-weight:400}
|
|
633
|
+
.lr-project-name{letter-spacing:.5px}
|
|
634
|
+
.lr-test-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px;padding:4px 0 8px}
|
|
635
|
+
.live-done{background:var(--green-dim);color:var(--green);text-align:center;padding:10px;font-weight:600;font-size:12px}
|
|
636
|
+
.live-done.has-failures{background:var(--red-dim);color:var(--red)}
|
|
637
|
+
.live-close{padding:4px 10px;font-size:10px;background:transparent;border:1px solid var(--border);border-radius:var(--r);color:var(--text2);cursor:pointer}
|
|
638
|
+
.live-close:hover{color:var(--text);border-color:var(--border-hi)}
|
|
639
|
+
.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}
|
|
640
|
+
.live-clear-btn:hover{color:var(--text);border-color:var(--border-hi);background:var(--surface3)}
|
|
641
|
+
.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}
|
|
642
|
+
.lr-dismiss:hover{color:var(--red);border-color:rgba(239,68,68,.3);background:var(--red-dim)}
|
|
643
|
+
|
|
644
|
+
.live-nav-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--purple);animation:pulse 1.5s infinite}
|
|
645
|
+
.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}
|
|
646
|
+
.spinner-small{display:inline-block;width:8px;height:8px;border:1.5px solid var(--border);border-top-color:var(--purple);border-radius:50%;animation:spin .6s linear infinite;vertical-align:middle}
|
|
647
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
483
648
|
|
|
484
649
|
</style>
|
|
485
650
|
</head>
|
|
@@ -501,20 +666,20 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
501
666
|
<div class="sidebar-section">
|
|
502
667
|
<div class="sidebar-section-label">Navigation</div>
|
|
503
668
|
</div>
|
|
504
|
-
<div class="nav-item" data-view="
|
|
505
|
-
<i class="icon"
|
|
669
|
+
<div class="nav-item active" data-view="watch">
|
|
670
|
+
<i class="icon">⏲</i><span>Watch</span>
|
|
506
671
|
</div>
|
|
507
|
-
<div class="nav-item
|
|
508
|
-
<i class="icon">▷</i><span>
|
|
672
|
+
<div class="nav-item" data-view="tests">
|
|
673
|
+
<i class="icon">▷</i><span>Tests</span><span class="badge" id="badgeSuites">-</span>
|
|
509
674
|
</div>
|
|
510
675
|
<div class="nav-item" data-view="runs">
|
|
511
676
|
<i class="icon">☰</i><span>Runs</span><span class="badge" id="badgeRuns">-</span>
|
|
512
677
|
</div>
|
|
513
|
-
<div class="nav-item" data-view="
|
|
514
|
-
<i class="icon"
|
|
678
|
+
<div class="nav-item" data-view="live" id="navLive" style="display:none">
|
|
679
|
+
<i class="icon"><span class="live-nav-dot"></span></i><span>Live</span><span class="badge" id="liveBadge" style="background:var(--purple-dim);color:var(--purple)">0</span>
|
|
515
680
|
</div>
|
|
516
|
-
<div class="nav-item" data-view="
|
|
517
|
-
<i class="icon">&#
|
|
681
|
+
<div class="nav-item" data-view="instances" id="navInstances" style="display:none">
|
|
682
|
+
<i class="icon">◉</i><span>Instances</span><span class="badge" id="badgeInstances">-</span>
|
|
518
683
|
</div>
|
|
519
684
|
|
|
520
685
|
<div class="pool-status" id="poolStatus">
|
|
@@ -523,16 +688,214 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
523
688
|
<strong>Pool</strong> <span id="poolLabel">--</span>
|
|
524
689
|
</div>
|
|
525
690
|
<div class="pool-info">Sessions: <strong id="poolSessions">-/-</strong></div>
|
|
691
|
+
<div class="pool-list" id="poolList" style="display:none"></div>
|
|
526
692
|
<div class="pool-info" style="margin-top:6px">
|
|
527
693
|
<span class="ws-dot" id="wsDot" style="background:var(--red)"></span>
|
|
528
694
|
<span id="wsLabel" style="font-size:10px;color:var(--text3)">ws: connecting</span>
|
|
529
695
|
</div>
|
|
530
696
|
</div>
|
|
697
|
+
|
|
698
|
+
<!-- Sync Status -->
|
|
699
|
+
<div class="sync-status" id="syncStatus" style="display:none">
|
|
700
|
+
<div class="sync-header">
|
|
701
|
+
<span class="pool-dot" id="syncDot"></span>
|
|
702
|
+
<strong>Sync</strong>
|
|
703
|
+
<span class="sync-mode" id="syncMode">--</span>
|
|
704
|
+
</div>
|
|
705
|
+
<div class="sync-details" id="syncDetails"></div>
|
|
706
|
+
<div class="sync-instances" id="syncInstances"></div>
|
|
707
|
+
</div>
|
|
531
708
|
</aside>
|
|
532
709
|
|
|
533
710
|
<div class="main">
|
|
534
711
|
|
|
535
|
-
<!--
|
|
712
|
+
<!-- ════════════════ Watch View (default) ════════════════ -->
|
|
713
|
+
<div class="view active" id="view-watch">
|
|
714
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
715
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Watch</div>
|
|
716
|
+
</div>
|
|
717
|
+
<div class="watch-grid" id="watchCards"></div>
|
|
718
|
+
<div class="watch-event-log">
|
|
719
|
+
<div class="watch-event-log-header">
|
|
720
|
+
<span class="title">Recent Runs</span>
|
|
721
|
+
</div>
|
|
722
|
+
<div class="watch-event-log-body" id="watchEventLog"></div>
|
|
723
|
+
</div>
|
|
724
|
+
<div class="empty" id="watchEmpty" style="display:none">
|
|
725
|
+
<div class="empty-icon">⏲</div>
|
|
726
|
+
<p>No projects registered yet. Run some tests to see project cards here.</p>
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
730
|
+
<!-- ════════════════ Tests View (inner tabs: Suites / Modules / Variables) ════════════════ -->
|
|
731
|
+
<div class="view" id="view-tests">
|
|
732
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
733
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Tests</div>
|
|
734
|
+
<div style="display:flex;gap:8px">
|
|
735
|
+
<button class="btn sm primary" id="btnRunAll">▷ Run All</button>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="tab-bar">
|
|
739
|
+
<button class="tab-btn active" data-tab="testsTabSuites">Suites</button>
|
|
740
|
+
<button class="tab-btn" data-tab="testsTabModules">Modules</button>
|
|
741
|
+
<button class="tab-btn" data-tab="testsTabVariables">Variables</button>
|
|
742
|
+
</div>
|
|
743
|
+
<!-- Suites tab -->
|
|
744
|
+
<div class="tab-pane active" id="testsTabSuites">
|
|
745
|
+
<div id="suiteAccordionContainer"></div>
|
|
746
|
+
<div class="suite-grid" id="suiteGrid"></div>
|
|
747
|
+
<div class="empty" id="suitesEmpty" style="display:none">
|
|
748
|
+
<div class="empty-icon">▷</div>
|
|
749
|
+
<p>No test suites found.</p>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
<!-- Modules tab -->
|
|
753
|
+
<div class="tab-pane" id="testsTabModules">
|
|
754
|
+
<div id="moduleSection"></div>
|
|
755
|
+
</div>
|
|
756
|
+
<!-- Variables tab -->
|
|
757
|
+
<div class="tab-pane" id="testsTabVariables">
|
|
758
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
759
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Variables</div>
|
|
760
|
+
<button class="btn sm primary" id="btnAddVar">+ Add Variable</button>
|
|
761
|
+
</div>
|
|
762
|
+
<div id="varAddForm" style="display:none"></div>
|
|
763
|
+
<div id="variablesContainer"></div>
|
|
764
|
+
<div class="empty" id="variablesEmpty" style="display:none">
|
|
765
|
+
<div class="empty-icon">⚙</div>
|
|
766
|
+
<p>No variables set. Add variables to use <code>{{var.KEY}}</code> in your tests.</p>
|
|
767
|
+
</div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<!-- ════════════════ Runs View (inner tabs: History / Screenshots / Learnings) ════════════════ -->
|
|
772
|
+
<div class="view" id="view-runs">
|
|
773
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
774
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Runs</div>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="tab-bar">
|
|
777
|
+
<button class="tab-btn active" data-tab="runsTabHistory">History</button>
|
|
778
|
+
<button class="tab-btn" data-tab="runsTabScreenshots">Screenshots<span class="badge" id="badgeScreenshots" style="margin-left:6px">-</span></button>
|
|
779
|
+
<button class="tab-btn" data-tab="runsTabLearnings" id="runsTabLearnings">Learnings<span class="badge" id="badgeLearnings" style="margin-left:6px">-</span></button>
|
|
780
|
+
</div>
|
|
781
|
+
<!-- History tab -->
|
|
782
|
+
<div class="tab-pane active" id="runsTabHistory">
|
|
783
|
+
<div class="health-banner" id="runsHealthBanner"></div>
|
|
784
|
+
<div class="card">
|
|
785
|
+
<div class="card-label">Pass Rate Trend</div>
|
|
786
|
+
<div class="chart" id="trendChart"></div>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="filter-bar" id="filterBar">
|
|
789
|
+
<button class="filter-btn active" data-filter="all">All</button>
|
|
790
|
+
<button class="filter-btn" data-filter="pass">Pass</button>
|
|
791
|
+
<button class="filter-btn" data-filter="fail">Fail</button>
|
|
792
|
+
<button class="filter-btn" data-filter="mixed">Mixed</button>
|
|
793
|
+
<input type="text" id="runSearchInput" placeholder="Search suite..." spellcheck="false">
|
|
794
|
+
</div>
|
|
795
|
+
<div class="card" style="padding:0">
|
|
796
|
+
<div class="tbl-wrap">
|
|
797
|
+
<table>
|
|
798
|
+
<thead id="runsHead"><tr></tr></thead>
|
|
799
|
+
<tbody id="runsBody"></tbody>
|
|
800
|
+
</table>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
<div class="empty" id="runsEmpty" style="display:none">
|
|
804
|
+
<div class="empty-icon">☰</div>
|
|
805
|
+
<p>No runs recorded yet.</p>
|
|
806
|
+
</div>
|
|
807
|
+
</div>
|
|
808
|
+
<!-- Screenshots tab -->
|
|
809
|
+
<div class="tab-pane" id="runsTabScreenshots">
|
|
810
|
+
<div class="ss-search">
|
|
811
|
+
<input type="text" id="ssHashInput" placeholder="Search by hash (e.g. ss:a3f2b1c9)" spellcheck="false">
|
|
812
|
+
<button id="ssHashBtn">Search</button>
|
|
813
|
+
</div>
|
|
814
|
+
<div id="ssSearchResult"></div>
|
|
815
|
+
<div class="gallery" id="screenshotGallery"></div>
|
|
816
|
+
<div class="empty" id="screenshotsEmpty" style="display:none">
|
|
817
|
+
<div class="empty-icon">▣</div>
|
|
818
|
+
<p>Select a project to view screenshots.</p>
|
|
819
|
+
</div>
|
|
820
|
+
</div>
|
|
821
|
+
<!-- Learnings tab -->
|
|
822
|
+
<div class="tab-pane" id="runsTabLearnings">
|
|
823
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
|
824
|
+
<div style="font-family:var(--sans);font-size:14px;font-weight:600;color:var(--text2)">Learnings</div>
|
|
825
|
+
<div style="display:flex;gap:8px;align-items:center">
|
|
826
|
+
<select id="learningsDays" style="padding:5px 8px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px">
|
|
827
|
+
<option value="7">7 days</option>
|
|
828
|
+
<option value="14">14 days</option>
|
|
829
|
+
<option value="30" selected>30 days</option>
|
|
830
|
+
<option value="90">90 days</option>
|
|
831
|
+
</select>
|
|
832
|
+
<button class="btn sm" id="btnExportLearnings">Export MD</button>
|
|
833
|
+
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
<div id="learningsOverview"></div>
|
|
837
|
+
<div id="learningsTrend"></div>
|
|
838
|
+
<div id="learningsFlaky"></div>
|
|
839
|
+
<div id="learningsSelectors"></div>
|
|
840
|
+
<div id="learningsPages"></div>
|
|
841
|
+
<div id="learningsApis"></div>
|
|
842
|
+
<div id="learningsErrors"></div>
|
|
843
|
+
<div class="empty" id="learningsEmpty" style="display:none">
|
|
844
|
+
<div class="empty-icon">★</div>
|
|
845
|
+
<p>No learnings data yet. Run some tests to start building knowledge.</p>
|
|
846
|
+
</div>
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<!-- ════════════════ Instances View (Hub Mode) ════════════════ -->
|
|
851
|
+
<div class="view" id="view-instances">
|
|
852
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
853
|
+
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Connected Instances</div>
|
|
854
|
+
<div style="display:flex;gap:8px">
|
|
855
|
+
<select id="instanceFilter" style="padding:6px 10px;border-radius:var(--r);border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--mono);font-size:11px">
|
|
856
|
+
<option value="all">All Status</option>
|
|
857
|
+
<option value="active">Active</option>
|
|
858
|
+
<option value="pending">Pending</option>
|
|
859
|
+
<option value="suspended">Suspended</option>
|
|
860
|
+
</select>
|
|
861
|
+
<button class="btn sm" id="refreshInstances">Refresh</button>
|
|
862
|
+
</div>
|
|
863
|
+
</div>
|
|
864
|
+
|
|
865
|
+
<div class="stats" id="instanceStats">
|
|
866
|
+
<div class="stat-block"><div class="stat-val accent" id="statInstancesTotal">-</div><div class="stat-lbl">Total</div></div>
|
|
867
|
+
<div class="stat-block"><div class="stat-val green" id="statInstancesOnline">-</div><div class="stat-lbl">Online</div></div>
|
|
868
|
+
<div class="stat-block"><div class="stat-val purple" id="statInstancesActive">-</div><div class="stat-lbl">Active</div></div>
|
|
869
|
+
<div class="stat-block"><div class="stat-val" id="statInstancesPending" style="color:var(--amber)">-</div><div class="stat-lbl">Pending</div></div>
|
|
870
|
+
</div>
|
|
871
|
+
|
|
872
|
+
<div class="card" style="padding:0">
|
|
873
|
+
<div class="tbl-wrap">
|
|
874
|
+
<table>
|
|
875
|
+
<thead>
|
|
876
|
+
<tr>
|
|
877
|
+
<th>Status</th>
|
|
878
|
+
<th>Instance ID</th>
|
|
879
|
+
<th>Display Name</th>
|
|
880
|
+
<th>Role</th>
|
|
881
|
+
<th>Environment</th>
|
|
882
|
+
<th>Last Seen</th>
|
|
883
|
+
<th>Actions</th>
|
|
884
|
+
</tr>
|
|
885
|
+
</thead>
|
|
886
|
+
<tbody id="instancesBody"></tbody>
|
|
887
|
+
</table>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
|
|
891
|
+
<div class="empty" id="instancesEmpty" style="display:none">
|
|
892
|
+
<div class="empty-icon">◉</div>
|
|
893
|
+
<p>No instances registered yet.</p>
|
|
894
|
+
<p style="margin-top:8px;font-size:11px;color:var(--text3)">Use <code>npx e2e-runner sync add-instance</code> to register agents.</p>
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
|
|
898
|
+
<!-- ════════════════ Live View ════════════════ -->
|
|
536
899
|
<div class="view" id="view-live">
|
|
537
900
|
<div class="live-panel active" id="livePanel">
|
|
538
901
|
<div class="live-header">
|
|
@@ -555,110 +918,40 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
555
918
|
</div>
|
|
556
919
|
<div class="empty" id="liveEmpty">
|
|
557
920
|
<div class="empty-icon" style="font-size:48px;opacity:.3">●</div>
|
|
558
|
-
<p>No tests running. Start a test from the
|
|
921
|
+
<p>No tests running. Start a test from the Tests view or another console.</p>
|
|
559
922
|
<p style="margin-top:8px;font-size:11px;color:var(--text3)">This view activates automatically when tests are detected.</p>
|
|
560
923
|
</div>
|
|
561
924
|
</div>
|
|
562
925
|
|
|
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>
|
|
576
|
-
|
|
577
|
-
<!-- Runs View -->
|
|
578
|
-
<div class="view" id="view-runs">
|
|
579
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
580
|
-
<div style="font-family:var(--sans);font-size:16px;font-weight:600">Run History</div>
|
|
581
|
-
</div>
|
|
582
|
-
<div class="card">
|
|
583
|
-
<div class="card-label">Pass Rate Trend</div>
|
|
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>
|
|
599
|
-
</div>
|
|
600
|
-
</div>
|
|
601
|
-
<div class="empty" id="runsEmpty" style="display:none">
|
|
602
|
-
<div class="empty-icon">☰</div>
|
|
603
|
-
<p>No runs recorded yet.</p>
|
|
604
|
-
</div>
|
|
605
|
-
</div>
|
|
926
|
+
</div>
|
|
606
927
|
|
|
607
|
-
|
|
608
|
-
<div class="
|
|
609
|
-
<div
|
|
610
|
-
<div
|
|
611
|
-
<div
|
|
612
|
-
<
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
<button class="btn sm" id="btnExportLearnings">Export MD</button>
|
|
619
|
-
<button class="btn sm" id="btnRefreshLearnings">Refresh</button>
|
|
928
|
+
<div class="suite-modal-overlay" id="suiteModalOverlay">
|
|
929
|
+
<div class="suite-modal" id="suiteModal">
|
|
930
|
+
<div class="suite-modal-header">
|
|
931
|
+
<div class="suite-card-icon">▷</div>
|
|
932
|
+
<div class="suite-modal-title">
|
|
933
|
+
<h2 id="suiteModalName"></h2>
|
|
934
|
+
<span id="suiteModalFile"></span>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="suite-modal-actions">
|
|
937
|
+
<button class="btn sm primary" id="suiteModalRun">▷ Run</button>
|
|
938
|
+
<button class="suite-modal-close" id="suiteModalClose">×</button>
|
|
620
939
|
</div>
|
|
621
940
|
</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>
|
|
941
|
+
<div class="suite-modal-body" id="suiteModalBody">
|
|
942
|
+
<div class="suite-modal-loading">Loading...</div>
|
|
647
943
|
</div>
|
|
648
944
|
</div>
|
|
649
|
-
|
|
650
945
|
</div>
|
|
651
|
-
|
|
652
946
|
<div class="modal" id="modal"><img id="modalImg" src="" alt=""></div>
|
|
653
947
|
<div class="toast-container" id="toastContainer"></div>
|
|
654
948
|
<div class="kb-modal" id="kbModal">
|
|
655
949
|
<div class="kb-modal-content">
|
|
656
950
|
<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>
|
|
951
|
+
<div class="kb-row"><span class="kb-key">1</span><span class="kb-desc">Watch view</span></div>
|
|
952
|
+
<div class="kb-row"><span class="kb-key">2</span><span class="kb-desc">Tests view</span></div>
|
|
953
|
+
<div class="kb-row"><span class="kb-key">3</span><span class="kb-desc">Runs view</span></div>
|
|
954
|
+
<div class="kb-row"><span class="kb-key">4</span><span class="kb-desc">Live view</span></div>
|
|
662
955
|
<div class="kb-row"><span class="kb-key">j / k</span><span class="kb-desc">Navigate runs (next / previous)</span></div>
|
|
663
956
|
<div class="kb-row"><span class="kb-key">Enter</span><span class="kb-desc">Expand / collapse selected run</span></div>
|
|
664
957
|
<div class="kb-row"><span class="kb-key">Esc</span><span class="kb-desc">Close modal / collapse run</span></div>
|
|
@@ -670,6 +963,8 @@ tr.expanded td:first-child::before{content:'';position:absolute;left:0;top:0;bot
|
|
|
670
963
|
<script>
|
|
671
964
|
(function(){
|
|
672
965
|
'use strict';
|
|
966
|
+
/* ── utils.js ── */
|
|
967
|
+
/* ── DOM Helpers ── */
|
|
673
968
|
var $=function(s){return document.querySelector(s)};
|
|
674
969
|
var $$=function(s){return document.querySelectorAll(s)};
|
|
675
970
|
|
|
@@ -740,20 +1035,37 @@ function buildNdSection(title,contentEl,count,copyText){
|
|
|
740
1035
|
return el('div',{className:'rd-nd-section'},[toggle,contentWrap]);
|
|
741
1036
|
}
|
|
742
1037
|
|
|
1038
|
+
function gqlOp(n){
|
|
1039
|
+
if(n.requestBody){
|
|
1040
|
+
try{
|
|
1041
|
+
var b=JSON.parse(n.requestBody);
|
|
1042
|
+
if(b.operationName)return b.operationName;
|
|
1043
|
+
if(b.query){var m=b.query.match(/^(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/);if(m)return m[1]}
|
|
1044
|
+
}catch(e){}
|
|
1045
|
+
}
|
|
1046
|
+
if(n.url){
|
|
1047
|
+
try{var u=new URL(n.url,location.href);var op=u.searchParams.get('operationName');if(op)return op}catch(e){}
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
743
1052
|
function buildNetRow(n){
|
|
744
1053
|
var mCls='rd-net-method '+(n.method||'GET').toLowerCase();
|
|
745
1054
|
var sCode=n.status||0;
|
|
746
1055
|
var sCls='rd-net-status '+(sCode<300?'s2xx':sCode<400?'s3xx':sCode<500?'s4xx':'s5xx');
|
|
747
1056
|
var hasDetail=n.requestBody||n.responseBody||n.requestHeaders||n.responseHeaders;
|
|
748
1057
|
var rowCls='rd-net-row'+(sCode>=400?' has-error':'');
|
|
749
|
-
var
|
|
1058
|
+
var opName=gqlOp(n);
|
|
1059
|
+
var children=[
|
|
750
1060
|
el('span',{className:'rd-net-expand'},hasDetail?'\u25B6':''),
|
|
751
1061
|
el('span',{className:mCls},n.method||'GET'),
|
|
752
|
-
el('span',{className:sCls},String(sCode))
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1062
|
+
el('span',{className:sCls},String(sCode))
|
|
1063
|
+
];
|
|
1064
|
+
if(opName)children.push(el('span',{className:'rd-net-op'},opName));
|
|
1065
|
+
children.push(el('span',{className:'rd-net-url'},n.url||''));
|
|
1066
|
+
children.push(makeCopyBtn(n.url||''));
|
|
1067
|
+
children.push(el('span',{className:'rd-net-dur'},dur(n.duration)));
|
|
1068
|
+
var row=el('div',{className:rowCls},children);
|
|
757
1069
|
var detail=null;
|
|
758
1070
|
if(hasDetail){
|
|
759
1071
|
var sections=[];
|
|
@@ -816,11 +1128,89 @@ function createTriggerBadge(source){
|
|
|
816
1128
|
return badge;
|
|
817
1129
|
}
|
|
818
1130
|
|
|
819
|
-
/*
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1131
|
+
/* ── Pool Distribution Summary ── */
|
|
1132
|
+
var POOL_COLORS=['#6366f1','#22d3ee','#f59e0b','#10b981','#ef4444','#8b5cf6','#ec4899','#14b8a6'];
|
|
1133
|
+
function buildPoolDistribution(tests){
|
|
1134
|
+
var pools={};var total=0;
|
|
1135
|
+
Object.keys(tests).forEach(function(n){
|
|
1136
|
+
if(n==='__error')return;var t=tests[n];
|
|
1137
|
+
if(!t.poolUrl)return;
|
|
1138
|
+
var label=t.poolUrl.replace('ws://','').replace('wss://','');
|
|
1139
|
+
if(!pools[label])pools[label]={count:0,passed:0,failed:0};
|
|
1140
|
+
pools[label].count++;total++;
|
|
1141
|
+
if(t.status==='passed'||t.success)pools[label].passed++;
|
|
1142
|
+
if(t.status==='failed'||t.success===false)pools[label].failed++;
|
|
1143
|
+
});
|
|
1144
|
+
var keys=Object.keys(pools);
|
|
1145
|
+
if(keys.length<2)return null;
|
|
1146
|
+
var bar=el('div',{className:'pool-dist'});
|
|
1147
|
+
var legend=el('div',{className:'pool-dist-legend'});
|
|
1148
|
+
keys.forEach(function(k,i){
|
|
1149
|
+
var pct=Math.round(pools[k].count/total*100);
|
|
1150
|
+
var color=POOL_COLORS[i%POOL_COLORS.length];
|
|
1151
|
+
var seg=el('div',{className:'pool-dist-seg'});
|
|
1152
|
+
seg.style.flex=pools[k].count;seg.style.background=color;
|
|
1153
|
+
seg.textContent=k+' ('+pools[k].count+')';
|
|
1154
|
+
bar.appendChild(seg);
|
|
1155
|
+
var lg=el('span',{},k+': '+pools[k].count+' tests ('+pct+'%)');
|
|
1156
|
+
lg.style.cssText='display:inline-flex;align-items:center;gap:4px';
|
|
1157
|
+
var dot=el('span',{});dot.style.cssText='width:8px;height:8px;border-radius:2px;background:'+color+';flex-shrink:0';
|
|
1158
|
+
lg.insertBefore(dot,lg.firstChild);
|
|
1159
|
+
legend.appendChild(lg);
|
|
1160
|
+
});
|
|
1161
|
+
return el('div',{style:'padding:4px 12px'},[bar,legend]);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
/* ── state.js ── */
|
|
1166
|
+
/* ── Global State ── */
|
|
1167
|
+
var S={
|
|
1168
|
+
ws:null,project:null,view:'watch',selectedRun:null,
|
|
1169
|
+
liveRuns:{},liveCollapsed:new Set(),liveSSOpen:new Set(),
|
|
1170
|
+
runFilter:{status:'all',search:''},
|
|
1171
|
+
lastLearningsData:null,
|
|
1172
|
+
highlightedRunIdx:-1
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
/* ── Navigation ── */
|
|
1176
|
+
$$('.nav-item').forEach(function(n){
|
|
1177
|
+
n.addEventListener('click',function(){
|
|
1178
|
+
showView(n.dataset.view);
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
function showView(v){
|
|
1182
|
+
S.view=v;
|
|
1183
|
+
$$('.nav-item').forEach(function(n){n.classList.toggle('active',n.dataset.view===v)});
|
|
1184
|
+
$$('.view').forEach(function(x){x.classList.remove('active')});
|
|
1185
|
+
var viewEl=$('#view-'+v);
|
|
1186
|
+
if(viewEl)viewEl.classList.add('active');
|
|
1187
|
+
if(v==='watch'&&typeof startWatchPolling==='function')startWatchPolling();
|
|
1188
|
+
else if(typeof stopWatchPolling==='function')stopWatchPolling();
|
|
1189
|
+
if(v==='instances'&&typeof refreshInstances==='function')refreshInstances();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* ── Inner Tabs ── */
|
|
1193
|
+
function initTabs(){
|
|
1194
|
+
$$('.tab-bar').forEach(function(bar){
|
|
1195
|
+
var container=bar.parentElement;
|
|
1196
|
+
bar.querySelectorAll('.tab-btn').forEach(function(btn){
|
|
1197
|
+
btn.addEventListener('click',function(){
|
|
1198
|
+
bar.querySelectorAll('.tab-btn').forEach(function(b){b.classList.remove('active')});
|
|
1199
|
+
btn.classList.add('active');
|
|
1200
|
+
container.querySelectorAll('.tab-pane').forEach(function(p){p.classList.remove('active')});
|
|
1201
|
+
var pane=container.querySelector('#'+btn.dataset.tab);
|
|
1202
|
+
if(pane)pane.classList.add('active');
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
/* ── toast.js ── */
|
|
1210
|
+
/* ── Toast Notifications ── */
|
|
1211
|
+
function showToast(message,type,timeout){
|
|
823
1212
|
type=type||'info';
|
|
1213
|
+
timeout=timeout||5000;
|
|
824
1214
|
var container=$('#toastContainer');
|
|
825
1215
|
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
826
1216
|
var t=el('div',{className:'toast '+type},[
|
|
@@ -831,12 +1221,24 @@ function showToast(message,type){
|
|
|
831
1221
|
setTimeout(function(){
|
|
832
1222
|
t.classList.add('fade-out');
|
|
833
1223
|
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
834
|
-
},
|
|
1224
|
+
},timeout);
|
|
835
1225
|
}
|
|
836
1226
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1227
|
+
function showEnrichedToast(message,type){
|
|
1228
|
+
var container=$('#toastContainer');
|
|
1229
|
+
var icons={success:'\u2714',error:'\u2718',info:'\u2139'};
|
|
1230
|
+
var t=el('div',{className:'toast clickable '+type,onclick:function(){showView('runs');var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
1231
|
+
el('span',null,icons[type]||''),
|
|
1232
|
+
el('span',null,message)
|
|
1233
|
+
]);
|
|
1234
|
+
container.appendChild(t);
|
|
1235
|
+
setTimeout(function(){
|
|
1236
|
+
t.classList.add('fade-out');
|
|
1237
|
+
setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300);
|
|
1238
|
+
},7000);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/* ── Download helper ── */
|
|
840
1242
|
function downloadFile(filename,content,mimeType){
|
|
841
1243
|
var blob=new Blob([content],{type:mimeType||'text/plain'});
|
|
842
1244
|
var url=URL.createObjectURL(blob);
|
|
@@ -847,35 +1249,235 @@ function downloadFile(filename,content,mimeType){
|
|
|
847
1249
|
URL.revokeObjectURL(url);
|
|
848
1250
|
}
|
|
849
1251
|
|
|
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
1252
|
|
|
859
|
-
/* ──
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1253
|
+
/* ── api.js ── */
|
|
1254
|
+
/* ── API & Pool ── */
|
|
1255
|
+
function api(p){return fetch(p).then(function(r){return r.json()})}
|
|
1256
|
+
function triggerRun(suite,projectId){
|
|
1257
|
+
if(anyLiveRunning())return;
|
|
1258
|
+
var body={};
|
|
1259
|
+
if(suite)body.suite=suite;
|
|
1260
|
+
if(projectId)body.projectId=projectId;
|
|
1261
|
+
else if(S.project)body.projectId=S.project;
|
|
1262
|
+
fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function renderPool(d){
|
|
1266
|
+
if(!d)return;
|
|
1267
|
+
var poolList=$('#poolList');
|
|
1268
|
+
if(d.pools&&d.pools.length>1){
|
|
1269
|
+
var anyAvail=d.availableCount>0;
|
|
1270
|
+
$('#poolDot').className='pool-dot '+(anyAvail?'on':'off');
|
|
1271
|
+
$('#poolLabel').textContent=anyAvail?d.availableCount+'/'+d.totalPools+' ready':'all busy';
|
|
1272
|
+
$('#poolSessions').textContent=(d.totalRunning||0)+'/'+(d.totalMaxConcurrent||0);
|
|
1273
|
+
poolList.textContent='';poolList.style.display='';
|
|
1274
|
+
d.pools.forEach(function(p){
|
|
1275
|
+
var label=(p.url||'').replace('ws://','').replace('wss://','');
|
|
1276
|
+
var ok=!p.error&&p.available;
|
|
1277
|
+
var dot=el('span',{className:'pool-dot '+(ok?'on':'off')});
|
|
1278
|
+
var name=el('strong',{},label);
|
|
1279
|
+
var status=el('span',{},p.error?'offline':p.available?'ready':'busy');
|
|
1280
|
+
var sess=el('span',{className:'pool-sessions'},(p.running||0)+'/'+(p.maxConcurrent||0));
|
|
1281
|
+
poolList.appendChild(el('div',{className:'pool-item'},[dot,name,status,sess]));
|
|
1282
|
+
});
|
|
1283
|
+
}else if(d.pools&&d.pools.length===1){
|
|
1284
|
+
var p=d.pools[0];
|
|
1285
|
+
$('#poolDot').className='pool-dot '+(p.error||!p.available?'off':'on');
|
|
1286
|
+
$('#poolLabel').textContent=p.error?'offline':p.available?'ready':'busy';
|
|
1287
|
+
$('#poolSessions').textContent=(p.running||0)+'/'+(p.maxConcurrent||0);
|
|
1288
|
+
poolList.style.display='none';
|
|
1289
|
+
}else{
|
|
1290
|
+
$('#poolDot').className='pool-dot '+(d.error||!d.available?'off':'on');
|
|
1291
|
+
$('#poolLabel').textContent=d.error?'offline':d.available?'ready':'busy';
|
|
1292
|
+
$('#poolSessions').textContent=(d.running||0)+'/'+(d.maxConcurrent||0);
|
|
1293
|
+
poolList.style.display='none';
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function refreshStatus(){
|
|
1297
|
+
api('/api/status').then(function(d){
|
|
1298
|
+
renderPool(d.pool);
|
|
1299
|
+
// Check if sync is enabled and update UI
|
|
1300
|
+
if(d.config && d.config.sync){
|
|
1301
|
+
renderSyncStatus(d.config.sync);
|
|
1302
|
+
}
|
|
1303
|
+
}).catch(function(){});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/* ── Sync ── */
|
|
1307
|
+
var syncMode = null;
|
|
1308
|
+
|
|
1309
|
+
function renderSyncStatus(sync){
|
|
1310
|
+
var status=$('#syncStatus');
|
|
1311
|
+
var dot=$('#syncDot');
|
|
1312
|
+
var mode=$('#syncMode');
|
|
1313
|
+
var details=$('#syncDetails');
|
|
1314
|
+
var instances=$('#syncInstances');
|
|
1315
|
+
|
|
1316
|
+
if(!sync || sync.mode === 'standalone'){
|
|
1317
|
+
status.style.display='none';
|
|
1318
|
+
$('#navInstances').style.display='none';
|
|
1319
|
+
syncMode = null;
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
status.style.display='block';
|
|
1324
|
+
syncMode = sync.mode;
|
|
1325
|
+
|
|
1326
|
+
mode.textContent = sync.mode;
|
|
1327
|
+
mode.className = 'sync-mode ' + sync.mode;
|
|
1328
|
+
|
|
1329
|
+
if(sync.mode === 'hub'){
|
|
1330
|
+
dot.className = 'pool-dot on';
|
|
1331
|
+
dot.style.background = 'var(--purple)';
|
|
1332
|
+
details.textContent = 'Accepting agent connections';
|
|
1333
|
+
$('#navInstances').style.display = 'flex';
|
|
1334
|
+
refreshInstances();
|
|
1335
|
+
} else if(sync.mode === 'agent'){
|
|
1336
|
+
var hubUrl = sync.agent && sync.agent.hubUrl;
|
|
1337
|
+
if(hubUrl){
|
|
1338
|
+
dot.className = 'pool-dot on';
|
|
1339
|
+
dot.style.background = 'var(--accent)';
|
|
1340
|
+
details.innerHTML = 'Hub: <strong>' + hubUrl + '</strong>';
|
|
1341
|
+
} else {
|
|
1342
|
+
dot.className = 'pool-dot off';
|
|
1343
|
+
details.textContent = 'Not connected';
|
|
1344
|
+
}
|
|
1345
|
+
$('#navInstances').style.display = 'none';
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function refreshInstances(){
|
|
1350
|
+
if(syncMode !== 'hub') return;
|
|
1351
|
+
|
|
1352
|
+
api('/api/sync/instances').then(function(d){
|
|
1353
|
+
var instances = d.instances || [];
|
|
1354
|
+
var online = 0;
|
|
1355
|
+
var active = 0;
|
|
1356
|
+
var pending = 0;
|
|
1357
|
+
var now = Date.now();
|
|
1358
|
+
|
|
1359
|
+
instances.forEach(function(inst){
|
|
1360
|
+
if(inst.status === 'active') active++;
|
|
1361
|
+
if(inst.status === 'pending') pending++;
|
|
1362
|
+
if(inst.lastSeen){
|
|
1363
|
+
var lastSeen = new Date(inst.lastSeen + 'Z').getTime();
|
|
1364
|
+
if(now - lastSeen < 5 * 60 * 1000) online++;
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
$('#statInstancesTotal').textContent = instances.length;
|
|
1369
|
+
$('#statInstancesOnline').textContent = online;
|
|
1370
|
+
$('#statInstancesActive').textContent = active;
|
|
1371
|
+
$('#statInstancesPending').textContent = pending;
|
|
1372
|
+
$('#badgeInstances').textContent = online + '/' + instances.length;
|
|
1373
|
+
|
|
1374
|
+
var tbody = $('#instancesBody');
|
|
1375
|
+
tbody.innerHTML = '';
|
|
1376
|
+
|
|
1377
|
+
if(instances.length === 0){
|
|
1378
|
+
$('#instancesEmpty').style.display = 'block';
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
$('#instancesEmpty').style.display = 'none';
|
|
1382
|
+
|
|
1383
|
+
instances.forEach(function(inst){
|
|
1384
|
+
var isOnline = inst.lastSeen && (now - new Date(inst.lastSeen + 'Z').getTime() < 5 * 60 * 1000);
|
|
1385
|
+
var statusClass = inst.status === 'active' ? 'pass' : inst.status === 'pending' ? 'flaky' : 'fail';
|
|
1386
|
+
|
|
1387
|
+
var tr = el('tr', null, [
|
|
1388
|
+
el('td', null, [
|
|
1389
|
+
el('span', {className: 'pool-dot ' + (isOnline ? 'on' : 'off'), style: 'margin-right:6px'}),
|
|
1390
|
+
el('span', {className: 'badge ' + statusClass}, inst.status)
|
|
1391
|
+
]),
|
|
1392
|
+
el('td', {style: 'font-family:var(--mono)'}, inst.instanceId),
|
|
1393
|
+
el('td', null, inst.displayName),
|
|
1394
|
+
el('td', null, inst.role),
|
|
1395
|
+
el('td', null, inst.environment || '-'),
|
|
1396
|
+
el('td', {style: 'color:var(--text3);font-size:11px'}, inst.lastSeen ? fdate(inst.lastSeen) : 'never'),
|
|
1397
|
+
el('td', null, [
|
|
1398
|
+
inst.status === 'pending' ? el('button', {className: 'btn sm', onclick: function(e){
|
|
1399
|
+
e.stopPropagation();
|
|
1400
|
+
approveInstance(inst.instanceId);
|
|
1401
|
+
}}, 'Approve') : null,
|
|
1402
|
+
inst.status === 'active' ? el('button', {className: 'btn sm danger', onclick: function(e){
|
|
1403
|
+
e.stopPropagation();
|
|
1404
|
+
revokeInstance(inst.instanceId);
|
|
1405
|
+
}}, 'Suspend') : null
|
|
1406
|
+
])
|
|
1407
|
+
]);
|
|
1408
|
+
tbody.appendChild(tr);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
// Update sync status sidebar with online instances
|
|
1412
|
+
var syncInst = $('#syncInstances');
|
|
1413
|
+
syncInst.innerHTML = '';
|
|
1414
|
+
instances.slice(0, 5).forEach(function(inst){
|
|
1415
|
+
var isOnline = inst.lastSeen && (now - new Date(inst.lastSeen + 'Z').getTime() < 5 * 60 * 1000);
|
|
1416
|
+
var span = el('span', {className: 'sync-inst ' + (isOnline ? 'online' : 'offline')}, [
|
|
1417
|
+
el('span', {className: 'pool-dot ' + (isOnline ? 'on' : 'off'), style: 'width:5px;height:5px'}),
|
|
1418
|
+
document.createTextNode(inst.instanceId.slice(0, 12))
|
|
1419
|
+
]);
|
|
1420
|
+
syncInst.appendChild(span);
|
|
1421
|
+
});
|
|
1422
|
+
if(instances.length > 5){
|
|
1423
|
+
syncInst.appendChild(el('span', {style: 'font-size:9px;color:var(--text3)'}, '+' + (instances.length - 5) + ' more'));
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
}).catch(function(err){
|
|
1427
|
+
console.error('Failed to load instances:', err);
|
|
867
1428
|
});
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function approveInstance(instanceId){
|
|
1432
|
+
api('/api/sync/instances/' + instanceId, {
|
|
1433
|
+
method: 'PATCH',
|
|
1434
|
+
body: JSON.stringify({status: 'active'})
|
|
1435
|
+
}).then(function(){
|
|
1436
|
+
showToast('Instance approved', 'success');
|
|
1437
|
+
refreshInstances();
|
|
1438
|
+
}).catch(function(err){
|
|
1439
|
+
showToast('Failed to approve: ' + err.message, 'error');
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function revokeInstance(instanceId){
|
|
1444
|
+
api('/api/sync/instances/' + instanceId, {
|
|
1445
|
+
method: 'PATCH',
|
|
1446
|
+
body: JSON.stringify({status: 'suspended'})
|
|
1447
|
+
}).then(function(){
|
|
1448
|
+
showToast('Instance suspended', 'success');
|
|
1449
|
+
refreshInstances();
|
|
1450
|
+
}).catch(function(err){
|
|
1451
|
+
showToast('Failed to suspend: ' + err.message, 'error');
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
$('#refreshInstances').addEventListener('click', refreshInstances);
|
|
1456
|
+
$('#instanceFilter').addEventListener('change', function(){
|
|
1457
|
+
// For now just refresh - could add client-side filtering
|
|
1458
|
+
refreshInstances();
|
|
868
1459
|
});
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1460
|
+
|
|
1461
|
+
/* ── Projects ── */
|
|
1462
|
+
function refreshProjects(){
|
|
1463
|
+
api('/api/db/projects').then(function(projects){
|
|
1464
|
+
var sel=$('#projectSelect'),prev=sel.value;
|
|
1465
|
+
while(sel.options.length>1)sel.remove(1);
|
|
1466
|
+
if(Array.isArray(projects))projects.forEach(function(p){
|
|
1467
|
+
var o=document.createElement('option');o.value=p.id;o.textContent=p.name;sel.appendChild(o);
|
|
1468
|
+
});
|
|
1469
|
+
sel.value=prev||'';
|
|
1470
|
+
}).catch(function(){});
|
|
874
1471
|
}
|
|
1472
|
+
$('#projectSelect').addEventListener('change',function(){
|
|
1473
|
+
S.project=this.value?parseInt(this.value,10):null;
|
|
1474
|
+
S.selectedRun=null;
|
|
1475
|
+
refreshRuns();refreshSuites();refreshScreenshots();refreshLearnings();refreshWatch();
|
|
1476
|
+
});
|
|
875
1477
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1478
|
+
|
|
1479
|
+
/* ── websocket.js ── */
|
|
1480
|
+
/* ── WebSocket ── */
|
|
879
1481
|
function connectWS(){
|
|
880
1482
|
var proto=location.protocol==='https:'?'wss:':'ws:';
|
|
881
1483
|
S.ws=new WebSocket(proto+'//'+location.host);
|
|
@@ -929,6 +1531,11 @@ function handleWS(m){
|
|
|
929
1531
|
r2.active=m.activeCount;
|
|
930
1532
|
r2.tests[m.name]={status:'running',actions:0,totalActions:0,error:null,actionLog:[],screenshots:[],serial:m.serial||false};
|
|
931
1533
|
renderLive();break;
|
|
1534
|
+
case 'test:pool':
|
|
1535
|
+
var rp=getLiveRun(m);if(!rp||!rp.tests[m.name])break;
|
|
1536
|
+
rp.tests[m.name].poolUrl=m.poolUrl||null;
|
|
1537
|
+
rp.tests[m.name].actionLog.unshift({type:'pool',narrative:'\uD83D\uDD17 '+m.name+' \u2192 '+(m.poolUrl||'').replace('ws://','').replace('wss://',''),success:true,duration:null,isPoolLog:true});
|
|
1538
|
+
renderLive();break;
|
|
932
1539
|
case 'test:action':
|
|
933
1540
|
var r3=getLiveRun(m);if(!r3||!r3.tests[m.name])break;
|
|
934
1541
|
var t=r3.tests[m.name];
|
|
@@ -950,66 +1557,286 @@ function handleWS(m){
|
|
|
950
1557
|
if(m.screenshots&&m.screenshots.length)r5.tests[m.name].screenshots=m.screenshots;
|
|
951
1558
|
if(m.errorScreenshot)r5.tests[m.name].errorScreenshot=m.errorScreenshot;
|
|
952
1559
|
if(m.networkLogs&&m.networkLogs.length)r5.tests[m.name].networkLogs=m.networkLogs;
|
|
1560
|
+
if(m.poolUrl)r5.tests[m.name].poolUrl=m.poolUrl;
|
|
953
1561
|
}
|
|
954
1562
|
r5.active=Math.max(0,r5.active-1);
|
|
955
1563
|
renderLive();break;
|
|
956
1564
|
case 'run:complete':
|
|
957
1565
|
var r6=getLiveRun(m);if(r6){r6.on=false;r6.done=true;r6.active=0}
|
|
958
1566
|
var summary=m.summary||{};
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1567
|
+
var baseMsg='Run complete: '+(summary.failed>0?summary.failed+' failed':'all '+(summary.total||0)+' passed');
|
|
1568
|
+
var baseType=summary.failed>0?'error':'success';
|
|
1569
|
+
var healthUrl=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
1570
|
+
fetch(healthUrl).then(function(r){return r.json()}).then(function(h){
|
|
1571
|
+
if(h&&h.passRate!==undefined){
|
|
1572
|
+
var extra='. Pass rate: '+h.passRate+'%';
|
|
1573
|
+
if(h.passRateTrend==='declining')extra+=' (declining, '+h.trendDelta+'%)';
|
|
1574
|
+
else if(h.passRateTrend==='improving')extra+=' (improving, +'+h.trendDelta+'%)';
|
|
1575
|
+
if(h.flakyCount>0)extra+='. '+h.flakyCount+' flaky test(s)';
|
|
1576
|
+
showEnrichedToast(baseMsg+extra,baseType);
|
|
1577
|
+
} else {
|
|
1578
|
+
showToast(baseMsg,baseType);
|
|
1579
|
+
}
|
|
1580
|
+
}).catch(function(){showToast(baseMsg,baseType)});
|
|
1581
|
+
renderLive();refreshRuns();refreshProjects();refreshWatch();break;
|
|
962
1582
|
case 'run:error':
|
|
963
1583
|
var r7=getLiveRun(m);if(r7){r7.on=false;r7.done=true;r7.tests.__error={status:'failed',error:m.error}}
|
|
964
1584
|
showToast('Run error: '+m.error,'error');
|
|
965
1585
|
renderLive();break;
|
|
966
1586
|
case 'db:updated':
|
|
967
|
-
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();break;
|
|
1587
|
+
refreshRuns();refreshProjects();refreshScreenshots();refreshLearnings();refreshWatch();break;
|
|
968
1588
|
}
|
|
969
1589
|
}
|
|
970
1590
|
|
|
1591
|
+
|
|
1592
|
+
/* ── view-watch.js ── */
|
|
971
1593
|
/* ══════════════════════════════════════════════════════════════════
|
|
972
|
-
|
|
1594
|
+
Watch View — Project Cards + Sparklines + Event Log
|
|
973
1595
|
══════════════════════════════════════════════════════════════════ */
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1596
|
+
var _watchInterval=null;
|
|
1597
|
+
var _countdownInterval=null;
|
|
1598
|
+
var _watchData=null;
|
|
1599
|
+
|
|
1600
|
+
function refreshWatch(){
|
|
1601
|
+
// Fetch projects overview (sparklines)
|
|
1602
|
+
api('/api/db/projects/overview').then(function(projects){
|
|
1603
|
+
if(!Array.isArray(projects)||!projects.length){
|
|
1604
|
+
$('#watchCards').textContent='';
|
|
1605
|
+
$('#watchEmpty').style.display='block';
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
$('#watchEmpty').style.display='none';
|
|
1609
|
+
_watchData=projects;
|
|
1610
|
+
renderWatchCards(projects);
|
|
1611
|
+
}).catch(function(){
|
|
1612
|
+
// Fallback: use regular projects list
|
|
1613
|
+
api('/api/db/projects').then(function(projects){
|
|
1614
|
+
if(!Array.isArray(projects)||!projects.length){$('#watchEmpty').style.display='block';return}
|
|
1615
|
+
$('#watchEmpty').style.display='none';
|
|
1616
|
+
_watchData=projects.map(function(p){return Object.assign({},p,{sparkline:[]})});
|
|
1617
|
+
renderWatchCards(_watchData);
|
|
1618
|
+
}).catch(function(){});
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
// Fetch event log (recent runs)
|
|
1622
|
+
var runsUrl=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
1623
|
+
api(runsUrl).then(function(runs){
|
|
1624
|
+
renderEventLog(runs);
|
|
1625
|
+
}).catch(function(){});
|
|
1626
|
+
|
|
1627
|
+
// Fetch watch jobs status for countdown
|
|
1628
|
+
fetch('/api/watch/status').then(function(r){
|
|
1629
|
+
if(!r.ok)throw new Error('not running');
|
|
1630
|
+
return r.json();
|
|
1631
|
+
}).then(function(jobs){
|
|
1632
|
+
applyWatchJobData(jobs);
|
|
1633
|
+
}).catch(function(){
|
|
1634
|
+
// Watch engine not running — that's fine, cards still show
|
|
1635
|
+
});
|
|
982
1636
|
}
|
|
983
1637
|
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1638
|
+
function renderWatchCards(projects){
|
|
1639
|
+
var container=$('#watchCards');
|
|
1640
|
+
container.textContent='';
|
|
1641
|
+
|
|
1642
|
+
projects.forEach(function(p){
|
|
1643
|
+
var sparkline=p.sparkline||[];
|
|
1644
|
+
var lastRate=sparkline.length?sparkline[sparkline.length-1]:null;
|
|
1645
|
+
var rateColor=lastRate===null?'dim':lastRate>=90?'green':lastRate>=70?'amber':'red';
|
|
1646
|
+
var dotColor=rateColor;
|
|
1647
|
+
|
|
1648
|
+
var sparkEl=el('div',{className:'watch-sparkline'});
|
|
1649
|
+
if(sparkline.length>=2){
|
|
1650
|
+
sparkEl.appendChild(buildSparkline(sparkline));
|
|
1651
|
+
} else {
|
|
1652
|
+
sparkEl.style.cssText='height:40px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:10px';
|
|
1653
|
+
sparkEl.textContent=sparkline.length?'1 run':'No runs yet';
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
var triggerBtn=el('button',{className:'btn sm',onclick:function(e){e.stopPropagation();triggerRun(null,p.id)}},'\u25B6');
|
|
1657
|
+
var detailBtn=el('button',{className:'btn sm',onclick:function(e){
|
|
1658
|
+
e.stopPropagation();
|
|
1659
|
+
S.project=p.id;$('#projectSelect').value=p.id;
|
|
1660
|
+
showView('runs');
|
|
1661
|
+
refreshRuns();refreshSuites();
|
|
1662
|
+
}},'\uD83D\uDD0D');
|
|
1663
|
+
|
|
1664
|
+
var card=el('div',{className:'watch-card',id:'watch-card-'+p.id},[
|
|
1665
|
+
el('div',{className:'watch-card-header'},[
|
|
1666
|
+
el('div',{className:'watch-card-name'},p.name),
|
|
1667
|
+
el('div',{className:'watch-card-icons'},[triggerBtn,detailBtn])
|
|
1668
|
+
]),
|
|
1669
|
+
sparkEl,
|
|
1670
|
+
el('div',{className:'watch-card-footer'},[
|
|
1671
|
+
el('div',{className:'watch-card-status'},[
|
|
1672
|
+
el('span',{className:'status-dot '+dotColor}),
|
|
1673
|
+
el('span',{className:'watch-card-rate '+rateColor},lastRate!==null?lastRate+'%':'—')
|
|
1674
|
+
]),
|
|
1675
|
+
el('span',{style:'color:var(--text3);font-size:10px'},p.runCount?p.runCount+' runs':'')
|
|
1676
|
+
]),
|
|
1677
|
+
el('div',{className:'watch-card-meta'},[
|
|
1678
|
+
el('span',{className:'watch-card-countdown',id:'watch-countdown-'+p.id},''),
|
|
1679
|
+
p.lastCommit?el('span',{className:'watch-card-commit'},'\u{1F4CB} '+p.lastCommit.slice(0,8)):null
|
|
1680
|
+
])
|
|
1681
|
+
]);
|
|
1682
|
+
|
|
1683
|
+
container.appendChild(card);
|
|
1684
|
+
});
|
|
989
1685
|
}
|
|
990
|
-
function refreshStatus(){api('/api/status').then(function(d){renderPool(d.pool)}).catch(function(){})}
|
|
991
1686
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1687
|
+
function buildSparkline(data){
|
|
1688
|
+
var ns='http://www.w3.org/2000/svg';
|
|
1689
|
+
var svg=document.createElementNS(ns,'svg');
|
|
1690
|
+
svg.setAttribute('viewBox','0 0 200 40');
|
|
1691
|
+
svg.setAttribute('preserveAspectRatio','none');
|
|
1692
|
+
|
|
1693
|
+
var n=data.length;
|
|
1694
|
+
var w=200/(n-1||1);
|
|
1695
|
+
var pts=data.map(function(v,i){return (i*w)+','+(40-v*0.4)}).join(' ');
|
|
1696
|
+
|
|
1697
|
+
// Gradient fill
|
|
1698
|
+
var poly=document.createElementNS(ns,'polygon');
|
|
1699
|
+
poly.setAttribute('points','0,40 '+pts+' '+((n-1)*w)+',40');
|
|
1700
|
+
poly.setAttribute('fill','var(--accent-dim)');
|
|
1701
|
+
svg.appendChild(poly);
|
|
1702
|
+
|
|
1703
|
+
// Line
|
|
1704
|
+
var pl=document.createElementNS(ns,'polyline');
|
|
1705
|
+
pl.setAttribute('points',pts);
|
|
1706
|
+
pl.setAttribute('fill','none');
|
|
1707
|
+
pl.setAttribute('stroke','var(--accent)');
|
|
1708
|
+
pl.setAttribute('stroke-width','1.5');
|
|
1709
|
+
svg.appendChild(pl);
|
|
1710
|
+
|
|
1711
|
+
// End dot
|
|
1712
|
+
if(n>0){
|
|
1713
|
+
var lastVal=data[n-1];
|
|
1714
|
+
var dotColor=lastVal>=90?'var(--green)':lastVal>=70?'var(--amber)':'var(--red)';
|
|
1715
|
+
var circle=document.createElementNS(ns,'circle');
|
|
1716
|
+
circle.setAttribute('cx',''+(n-1)*w);
|
|
1717
|
+
circle.setAttribute('cy',''+(40-lastVal*0.4));
|
|
1718
|
+
circle.setAttribute('r','3');
|
|
1719
|
+
circle.setAttribute('fill',dotColor);
|
|
1720
|
+
svg.appendChild(circle);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
return svg;
|
|
1004
1724
|
}
|
|
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
1725
|
|
|
1726
|
+
function applyWatchJobData(jobs){
|
|
1727
|
+
if(!jobs||!jobs.length)return;
|
|
1728
|
+
jobs.forEach(function(j){
|
|
1729
|
+
// Find matching card by project name
|
|
1730
|
+
if(!_watchData)return;
|
|
1731
|
+
var match=_watchData.find(function(p){return p.name===j.name||p.cwd===j.cwd});
|
|
1732
|
+
if(!match)return;
|
|
1733
|
+
var cdEl=$('#watch-countdown-'+match.id);
|
|
1734
|
+
if(cdEl&&j.nextRunAt){
|
|
1735
|
+
cdEl.dataset.nextRunAt=j.nextRunAt;
|
|
1736
|
+
updateCountdown(cdEl);
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
startCountdownTimer();
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
function startCountdownTimer(){
|
|
1743
|
+
if(_countdownInterval)return;
|
|
1744
|
+
_countdownInterval=setInterval(function(){
|
|
1745
|
+
$$('.watch-card-countdown[data-next-run-at]').forEach(updateCountdown);
|
|
1746
|
+
},1000);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function updateCountdown(cdEl){
|
|
1750
|
+
var next=cdEl.dataset.nextRunAt;
|
|
1751
|
+
if(!next){cdEl.textContent='';return}
|
|
1752
|
+
var diff=new Date(next)-Date.now();
|
|
1753
|
+
if(diff<=0){cdEl.textContent='\u23F1 Running...';return}
|
|
1754
|
+
var m=Math.floor(diff/60000);
|
|
1755
|
+
var s=Math.floor((diff%60000)/1000);
|
|
1756
|
+
cdEl.textContent='\u23F1 Next: '+m+'m '+String(s).padStart(2,'0')+'s';
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function renderEventLog(runs){
|
|
1760
|
+
var container=$('#watchEventLog');
|
|
1761
|
+
if(!container)return;
|
|
1762
|
+
container.textContent='';
|
|
1763
|
+
|
|
1764
|
+
if(!Array.isArray(runs)||!runs.length){
|
|
1765
|
+
container.appendChild(el('div',{style:'padding:16px;text-align:center;color:var(--text3);font-size:11px'},'No runs recorded yet.'));
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Column header row
|
|
1770
|
+
container.appendChild(el('div',{className:'watch-event-row we-header'},[
|
|
1771
|
+
el('span',null,'Time'),
|
|
1772
|
+
el('span',null,'Project'),
|
|
1773
|
+
el('span',null,'Suite'),
|
|
1774
|
+
el('span',{style:'justify-self:center'},'Status'),
|
|
1775
|
+
el('span',{style:'text-align:center'},'Tests'),
|
|
1776
|
+
el('span',{style:'text-align:right'},'Rate'),
|
|
1777
|
+
el('span',{style:'text-align:right'},'Duration'),
|
|
1778
|
+
el('span',{style:'text-align:right'},'Source')
|
|
1779
|
+
]));
|
|
1780
|
+
|
|
1781
|
+
var recent=runs.slice(0,30);
|
|
1782
|
+
recent.forEach(function(r){
|
|
1783
|
+
var rate=parseFloat(r.pass_rate)||0;
|
|
1784
|
+
var badgeCls=r.failed>0?'fail':'pass';
|
|
1785
|
+
var badgeText=r.failed>0?'FAIL':'PASS';
|
|
1786
|
+
|
|
1787
|
+
// Test counts: "5/5" or "3/5 (2 fail)"
|
|
1788
|
+
var countsText=r.passed+'/'+r.total;
|
|
1789
|
+
var countsParts=[el('span',{className:'we-counts-ok'},String(r.passed))];
|
|
1790
|
+
countsParts.push(document.createTextNode('/'+r.total));
|
|
1791
|
+
if(r.failed>0){
|
|
1792
|
+
countsParts.push(document.createTextNode(' ('));
|
|
1793
|
+
countsParts.push(el('span',{style:'color:var(--red)'},r.failed+' fail'));
|
|
1794
|
+
countsParts.push(document.createTextNode(')'));
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// Trigger badge
|
|
1798
|
+
var triggerIcon={'cli':'\u2318','dashboard':'\uD83D\uDCBB','mcp':'\u2699','watch':'\u23F1','api':'\u26A1'};
|
|
1799
|
+
var trigSrc=r.triggered_by||'cli';
|
|
1800
|
+
var trigEl=el('span',{className:'we-trigger',title:'Triggered by: '+trigSrc},(triggerIcon[trigSrc]||'\u2318')+' '+trigSrc);
|
|
1801
|
+
|
|
1802
|
+
var row=el('div',{className:'watch-event-row',style:'cursor:pointer'},[
|
|
1803
|
+
el('span',{className:'watch-event-time'},fdate(r.generated_at)),
|
|
1804
|
+
el('span',{className:'watch-event-project'},r.project_name||'—'),
|
|
1805
|
+
el('span',{className:'watch-event-suite'},r.suite_name||'all'),
|
|
1806
|
+
el('span',{className:'watch-event-result'},[el('span',{className:'badge '+badgeCls},badgeText)]),
|
|
1807
|
+
el('span',{className:'watch-event-counts'},countsParts),
|
|
1808
|
+
el('span',{className:'watch-event-rate'},rate>0?rate.toFixed(0)+'%':'—'),
|
|
1809
|
+
el('span',{className:'watch-event-duration'},r.duration?dur(r.duration):'—'),
|
|
1810
|
+
trigEl
|
|
1811
|
+
]);
|
|
1812
|
+
|
|
1813
|
+
// Click to navigate to run detail
|
|
1814
|
+
(function(run){
|
|
1815
|
+
row.addEventListener('click',function(){
|
|
1816
|
+
S.project=run.project_id;$('#projectSelect').value=run.project_id;
|
|
1817
|
+
showView('runs');
|
|
1818
|
+
refreshRuns();
|
|
1819
|
+
});
|
|
1820
|
+
})(r);
|
|
1821
|
+
|
|
1822
|
+
container.appendChild(row);
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function startWatchPolling(){
|
|
1827
|
+
if(_watchInterval)return;
|
|
1828
|
+
refreshWatch();
|
|
1829
|
+
_watchInterval=setInterval(refreshWatch,10000);
|
|
1830
|
+
}
|
|
1831
|
+
function stopWatchPolling(){
|
|
1832
|
+
if(_watchInterval){clearInterval(_watchInterval);_watchInterval=null}
|
|
1833
|
+
if(_countdownInterval){clearInterval(_countdownInterval);_countdownInterval=null}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
|
|
1837
|
+
/* ── view-tests.js ── */
|
|
1011
1838
|
/* ══════════════════════════════════════════════════════════════════
|
|
1012
|
-
Suites
|
|
1839
|
+
Tests View — Suites + Modules + Variables (inner tabs)
|
|
1013
1840
|
══════════════════════════════════════════════════════════════════ */
|
|
1014
1841
|
function refreshSuites(){
|
|
1015
1842
|
var grid=$('#suiteGrid'),empty=$('#suitesEmpty'),accordion=$('#suiteAccordionContainer');
|
|
@@ -1067,15 +1894,75 @@ function renderProjectAccordion(container,project,suites){
|
|
|
1067
1894
|
]);
|
|
1068
1895
|
|
|
1069
1896
|
var wrapper=el('div',{className:'project-accordion'},[header,body]);
|
|
1070
|
-
|
|
1071
|
-
header.addEventListener('click',function(){
|
|
1072
|
-
wrapper.classList.toggle('open');
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1897
|
+
header.addEventListener('click',function(){wrapper.classList.toggle('open')});
|
|
1075
1898
|
container.appendChild(wrapper);
|
|
1076
1899
|
}
|
|
1077
1900
|
|
|
1901
|
+
/* ── Suite Modal ── */
|
|
1078
1902
|
var _suiteCache={};
|
|
1903
|
+
|
|
1904
|
+
function openSuiteModal(suiteName,projectId){
|
|
1905
|
+
var overlay=$('#suiteModalOverlay');
|
|
1906
|
+
var body=$('#suiteModalBody');
|
|
1907
|
+
$('#suiteModalName').textContent=suiteName;
|
|
1908
|
+
$('#suiteModalFile').textContent=suiteName+'.json';
|
|
1909
|
+
body.textContent='';
|
|
1910
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'Loading\u2026'));
|
|
1911
|
+
overlay.classList.add('open');
|
|
1912
|
+
|
|
1913
|
+
$('#suiteModalRun').onclick=function(){triggerRun(suiteName,projectId)};
|
|
1914
|
+
$('#suiteModalClose').onclick=function(){overlay.classList.remove('open')};
|
|
1915
|
+
overlay.addEventListener('click',function(e){if(e.target===overlay)overlay.classList.remove('open')});
|
|
1916
|
+
|
|
1917
|
+
var cacheKey=projectId+'::'+suiteName;
|
|
1918
|
+
var p=_suiteCache[cacheKey]||api('/api/db/projects/'+projectId+'/suites/'+encodeURIComponent(suiteName));
|
|
1919
|
+
_suiteCache[cacheKey]=p;
|
|
1920
|
+
p.then(function(data){
|
|
1921
|
+
body.textContent='';
|
|
1922
|
+
if(!data||!data.tests||!data.tests.length){
|
|
1923
|
+
body.appendChild(el('div',{className:'suite-modal-loading'},'No tests found'));
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
data.tests.forEach(function(test){
|
|
1927
|
+
var actionsDiv=el('div',{className:'suite-modal-test-actions'});
|
|
1928
|
+
(test.actions||[]).forEach(function(a,i){
|
|
1929
|
+
var detailContent;
|
|
1930
|
+
if(a.selector&&(a.value||a.text)){
|
|
1931
|
+
detailContent=[el('span',{className:'step-sel'},a.selector),el('span',{className:'step-arrow'},'\u2192'),el('span',{className:'step-val'},a.text||a.value)];
|
|
1932
|
+
} else {
|
|
1933
|
+
detailContent=a.selector||a.value||a.text||'';
|
|
1934
|
+
}
|
|
1935
|
+
actionsDiv.appendChild(el('div',{className:'suite-modal-step'},[
|
|
1936
|
+
el('span',{className:'suite-modal-step-num'},String(i+1)),
|
|
1937
|
+
el('span',{className:'suite-modal-step-type'},a.type),
|
|
1938
|
+
el('span',{className:'suite-modal-step-detail'},detailContent)
|
|
1939
|
+
]));
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
var header=el('div',{className:'suite-modal-test-header'},[
|
|
1943
|
+
el('span',{className:'suite-modal-test-chevron'},'\u25B6'),
|
|
1944
|
+
el('span',{className:'suite-modal-test-name'},test.name),
|
|
1945
|
+
el('span',{className:'suite-modal-test-badge'},(test.actions||[]).length+' actions')
|
|
1946
|
+
]);
|
|
1947
|
+
|
|
1948
|
+
var testEl=el('div',{className:'suite-modal-test'},[header,actionsDiv]);
|
|
1949
|
+
if(test.expect){
|
|
1950
|
+
var expectText=Array.isArray(test.expect)?test.expect.join(', '):test.expect;
|
|
1951
|
+
var expectEl=el('div',{className:'suite-modal-expect'},[
|
|
1952
|
+
el('span',{className:'suite-modal-expect-label'},'Expect:'),
|
|
1953
|
+
document.createTextNode(expectText)
|
|
1954
|
+
]);
|
|
1955
|
+
testEl.insertBefore(expectEl,actionsDiv);
|
|
1956
|
+
}
|
|
1957
|
+
header.addEventListener('click',function(){testEl.classList.toggle('open')});
|
|
1958
|
+
body.appendChild(testEl);
|
|
1959
|
+
});
|
|
1960
|
+
}).catch(function(){
|
|
1961
|
+
body.textContent='';
|
|
1962
|
+
body.appendChild(el('div',{className:'suite-modal-loading',style:'color:var(--red)'},'Failed to load suite'));
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1079
1966
|
function renderSuiteCards(container,suites,projectId){
|
|
1080
1967
|
suites.forEach(function(s){
|
|
1081
1968
|
var tests=el('ul',{className:'suite-card-tests'});
|
|
@@ -1177,9 +2064,79 @@ function renderModules(container,modules){
|
|
|
1177
2064
|
container.appendChild(grid);
|
|
1178
2065
|
}
|
|
1179
2066
|
|
|
2067
|
+
/* ── Variables ── */
|
|
2068
|
+
function refreshVariables(){
|
|
2069
|
+
var container=$('#variablesContainer'),empty=$('#variablesEmpty');
|
|
2070
|
+
container.textContent='';
|
|
2071
|
+
if(!S.project){empty.style.display='block';empty.querySelector('p').textContent='Select a project to manage variables.';return}
|
|
2072
|
+
api('/api/db/projects/'+S.project+'/variables').then(function(vars){
|
|
2073
|
+
if(!Array.isArray(vars)||!vars.length){empty.style.display='block';empty.querySelector('p').textContent='No variables set. Add variables to use {{var.KEY}} in your tests.';return}
|
|
2074
|
+
empty.style.display='none';
|
|
2075
|
+
renderVariables(vars);
|
|
2076
|
+
}).catch(function(){empty.style.display='block'});
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function renderVariables(vars){
|
|
2080
|
+
var container=$('#variablesContainer');
|
|
2081
|
+
var tbl=el('table',{className:'var-table'});
|
|
2082
|
+
var thead=document.createElement('thead');
|
|
2083
|
+
var hr=document.createElement('tr');
|
|
2084
|
+
['Key','Value','Scope','Actions'].forEach(function(h){hr.appendChild(el('th',null,h))});
|
|
2085
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
2086
|
+
var tbody=document.createElement('tbody');
|
|
2087
|
+
vars.forEach(function(v){
|
|
2088
|
+
var tr=document.createElement('tr');
|
|
2089
|
+
tr.appendChild(el('td',null,[el('code',null,v.key)]));
|
|
2090
|
+
tr.appendChild(el('td',{style:'max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'},v.is_secret?'\u2022\u2022\u2022\u2022\u2022\u2022':v.value));
|
|
2091
|
+
tr.appendChild(el('td',{style:'color:var(--text3)'},v.scope||'project'));
|
|
2092
|
+
var delBtn=el('button',{className:'btn sm danger',onclick:function(){
|
|
2093
|
+
if(!confirm('Delete variable "'+v.key+'"?'))return;
|
|
2094
|
+
fetch('/api/db/projects/'+S.project+'/variables/'+encodeURIComponent(v.key),{method:'DELETE'}).then(function(){refreshVariables();showToast('Variable deleted','success')}).catch(function(){showToast('Delete failed','error')});
|
|
2095
|
+
}},'\u2715');
|
|
2096
|
+
tr.appendChild(el('td',null,[delBtn]));
|
|
2097
|
+
tbody.appendChild(tr);
|
|
2098
|
+
});
|
|
2099
|
+
tbl.appendChild(tbody);
|
|
2100
|
+
container.appendChild(tbl);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/* ── Variable Add Form ── */
|
|
2104
|
+
$('#btnAddVar').addEventListener('click',function(){
|
|
2105
|
+
var form=$('#varAddForm');
|
|
2106
|
+
if(form.style.display==='none'){
|
|
2107
|
+
form.style.display='';
|
|
2108
|
+
form.textContent='';
|
|
2109
|
+
var keyInput=el('input',{type:'text',placeholder:'KEY',style:'margin-right:8px;width:120px'});
|
|
2110
|
+
var valInput=el('input',{type:'text',placeholder:'Value',style:'margin-right:8px;width:200px'});
|
|
2111
|
+
var secretCheck=el('input',{type:'checkbox',style:'margin-right:4px'});
|
|
2112
|
+
var saveBtn=el('button',{className:'btn sm primary',onclick:function(){
|
|
2113
|
+
var k=keyInput.value.trim(),v=valInput.value;
|
|
2114
|
+
if(!k){showToast('Key is required','error');return}
|
|
2115
|
+
if(!S.project){showToast('Select a project first','error');return}
|
|
2116
|
+
fetch('/api/db/projects/'+S.project+'/variables',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:k,value:v,is_secret:secretCheck.checked})}).then(function(r){return r.json()}).then(function(){
|
|
2117
|
+
form.style.display='none';refreshVariables();showToast('Variable saved','success');
|
|
2118
|
+
}).catch(function(){showToast('Save failed','error')});
|
|
2119
|
+
}},'Save');
|
|
2120
|
+
var cancelBtn=el('button',{className:'btn sm',onclick:function(){form.style.display='none'}},'Cancel');
|
|
2121
|
+
form.appendChild(el('div',{className:'var-add-form',style:'display:flex;align-items:center;gap:8px;flex-wrap:wrap'},[
|
|
2122
|
+
keyInput,valInput,
|
|
2123
|
+
el('label',{style:'font-size:11px;color:var(--text2);display:flex;align-items:center;gap:4px'},[secretCheck,document.createTextNode('Secret')]),
|
|
2124
|
+
saveBtn,cancelBtn
|
|
2125
|
+
]));
|
|
2126
|
+
} else {
|
|
2127
|
+
form.style.display='none';
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
$('#btnRunAll').addEventListener('click',function(){triggerRun()});
|
|
2132
|
+
|
|
2133
|
+
|
|
2134
|
+
/* ── view-runs.js ── */
|
|
1180
2135
|
/* ══════════════════════════════════════════════════════════════════
|
|
1181
|
-
Runs
|
|
2136
|
+
Runs View — History + Screenshots + Learnings (inner tabs)
|
|
1182
2137
|
══════════════════════════════════════════════════════════════════ */
|
|
2138
|
+
|
|
2139
|
+
/* ── Filters ── */
|
|
1183
2140
|
$$('.filter-btn').forEach(function(btn){
|
|
1184
2141
|
btn.addEventListener('click',function(){
|
|
1185
2142
|
$$('.filter-btn').forEach(function(b){b.classList.remove('active')});
|
|
@@ -1214,7 +2171,44 @@ function applyRunFilters(){
|
|
|
1214
2171
|
});
|
|
1215
2172
|
}
|
|
1216
2173
|
|
|
2174
|
+
function renderRunsHealthBanner(){
|
|
2175
|
+
var banner=$('#runsHealthBanner');
|
|
2176
|
+
banner.textContent='';
|
|
2177
|
+
var url=S.project?'/api/db/projects/'+S.project+'/health':'/api/db/health';
|
|
2178
|
+
fetch(url).then(function(r){return r.json()}).then(function(h){
|
|
2179
|
+
if(!h||!h.passRate)return;
|
|
2180
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2181
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2182
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'dim';
|
|
2183
|
+
var deltaStr=h.trendDelta!==0?(h.trendDelta>0?'+':'')+h.trendDelta+'%':'';
|
|
2184
|
+
|
|
2185
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2186
|
+
el('div',{className:'hb-val '+rateColor},h.passRate+'%'),
|
|
2187
|
+
el('div',{className:'hb-lbl'},'Pass Rate'),
|
|
2188
|
+
el('div',{className:'hb-trend '+trendCls},trendIcon+' '+h.passRateTrend+(deltaStr?' ('+deltaStr+')':''))
|
|
2189
|
+
]));
|
|
2190
|
+
if(h.flakyCount>0){
|
|
2191
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2192
|
+
el('div',{className:'hb-val amber'},String(h.flakyCount)),
|
|
2193
|
+
el('div',{className:'hb-lbl'},'Flaky Tests')
|
|
2194
|
+
]));
|
|
2195
|
+
}
|
|
2196
|
+
if(h.topErrorPattern){
|
|
2197
|
+
var cat=h.topErrorPattern.category||h.topErrorPattern.pattern||'unknown';
|
|
2198
|
+
var pat=cat.replace(/-/g,' ').replace(/\b\w/g,function(c){return c.toUpperCase()});
|
|
2199
|
+
banner.appendChild(el('div',{className:'hb-item'},[
|
|
2200
|
+
el('div',{className:'hb-val red',style:'font-size:13px'},pat),
|
|
2201
|
+
el('div',{className:'hb-lbl'},'Top Error ('+h.topErrorPattern.count+'x)')
|
|
2202
|
+
]));
|
|
2203
|
+
}
|
|
2204
|
+
banner.appendChild(el('div',{className:'hb-link',onclick:function(){var lb=$('#runsTabLearnings');if(lb)lb.click()}},[
|
|
2205
|
+
el('span',null,'\u2192 View Learnings')
|
|
2206
|
+
]));
|
|
2207
|
+
}).catch(function(){});
|
|
2208
|
+
}
|
|
2209
|
+
|
|
1217
2210
|
function refreshRuns(){
|
|
2211
|
+
renderRunsHealthBanner();
|
|
1218
2212
|
var url=S.project?'/api/db/projects/'+S.project+'/runs':'/api/db/runs';
|
|
1219
2213
|
api(url).then(function(rows){
|
|
1220
2214
|
var chart=$('#trendChart'),body=$('#runsBody'),empty=$('#runsEmpty'),head=$('#runsHead');
|
|
@@ -1325,9 +2319,7 @@ function toggleDetail(id,clickedTr,colSpan){
|
|
|
1325
2319
|
loadDetailInline(id,detailTr);
|
|
1326
2320
|
}
|
|
1327
2321
|
|
|
1328
|
-
/*
|
|
1329
|
-
Run Detail (+ Action Narratives, Retry badges, Export)
|
|
1330
|
-
══════════════════════════════════════════════════════════════════ */
|
|
2322
|
+
/* ── Run Detail ── */
|
|
1331
2323
|
function loadDetailInline(id,detailTr){
|
|
1332
2324
|
api('/api/db/runs/'+id).then(function(d){
|
|
1333
2325
|
if(d.error)return;
|
|
@@ -1356,6 +2348,43 @@ function loadDetailInline(id,detailTr){
|
|
|
1356
2348
|
]);
|
|
1357
2349
|
inner.appendChild(summ);
|
|
1358
2350
|
|
|
2351
|
+
// Insights
|
|
2352
|
+
var insightsContainer=el('div',{className:'rd-insights'});
|
|
2353
|
+
inner.appendChild(insightsContainer);
|
|
2354
|
+
fetch('/api/db/runs/'+id+'/insights').then(function(r){return r.json()}).then(function(ins){
|
|
2355
|
+
if(!ins||ins.error)return;
|
|
2356
|
+
var items=[];
|
|
2357
|
+
var h=ins.health;
|
|
2358
|
+
if(h){
|
|
2359
|
+
var rateColor=h.passRate>=90?'green':h.passRate>=70?'amber':'red';
|
|
2360
|
+
var trendIcon=h.passRateTrend==='improving'?'\u25B2':h.passRateTrend==='declining'?'\u25BC':'=';
|
|
2361
|
+
var trendCls=h.passRateTrend==='improving'?'green':h.passRateTrend==='declining'?'red':'';
|
|
2362
|
+
items.push(el('div',{className:'rd-ins-health'},[
|
|
2363
|
+
el('span',{className:'rd-ins-rate '+rateColor},h.passRate+'%'),
|
|
2364
|
+
el('span',{className:'rd-ins-trend '+trendCls},trendIcon+' '+h.passRateTrend),
|
|
2365
|
+
h.flakyCount>0?el('span',{className:'rd-ins-tag amber'},h.flakyCount+' flaky'):null,
|
|
2366
|
+
h.unstableSelectorCount>0?el('span',{className:'rd-ins-tag red'},h.unstableSelectorCount+' unstable sel.'):null
|
|
2367
|
+
]));
|
|
2368
|
+
}
|
|
2369
|
+
var insights=ins.insights||[];
|
|
2370
|
+
insights.forEach(function(i){
|
|
2371
|
+
var icon=i.type==='new-failure'?'\u2718':i.type==='recovered'?'\u2714':i.type==='flaky'?'\u223C':'!';
|
|
2372
|
+
var cls=i.type==='new-failure'?'red':i.type==='recovered'?'green':i.type==='flaky'?'amber':'';
|
|
2373
|
+
items.push(el('div',{className:'rd-ins-item '+cls},[
|
|
2374
|
+
el('span',{className:'rd-ins-icon'},icon),
|
|
2375
|
+
el('span',null,i.message)
|
|
2376
|
+
]));
|
|
2377
|
+
});
|
|
2378
|
+
if(items.length>0){items.forEach(function(it){insightsContainer.appendChild(it)})}
|
|
2379
|
+
else{insightsContainer.style.display='none'}
|
|
2380
|
+
}).catch(function(){insightsContainer.style.display='none'});
|
|
2381
|
+
|
|
2382
|
+
// Pool distribution bar
|
|
2383
|
+
var histPoolTests={};
|
|
2384
|
+
results.forEach(function(r){if(!r.poolUrl)return;histPoolTests[r.name]={poolUrl:r.poolUrl,success:r.success}});
|
|
2385
|
+
var histPoolDist=buildPoolDistribution(histPoolTests);
|
|
2386
|
+
if(histPoolDist)inner.appendChild(histPoolDist);
|
|
2387
|
+
|
|
1359
2388
|
results.forEach(function(r){
|
|
1360
2389
|
var d2=r.durationMs?dur(r.durationMs):r.endTime&&r.startTime?dur(new Date(r.endTime)-new Date(r.startTime)):'-';
|
|
1361
2390
|
var flaky=r.success&&r.attempt>1;
|
|
@@ -1365,7 +2394,8 @@ function loadDetailInline(id,detailTr){
|
|
|
1365
2394
|
badges.appendChild(el('span',{className:'badge '+(r.success?'pass':'fail')},r.success?'PASS':'FAIL'));
|
|
1366
2395
|
if(flaky)badges.appendChild(el('span',{className:'badge flaky'},'FLAKY'));
|
|
1367
2396
|
|
|
1368
|
-
var
|
|
2397
|
+
var poolEl=r.poolUrl?el('span',{className:'pool-badge'},r.poolUrl.replace('ws://','').replace('wss://','')) :null;
|
|
2398
|
+
var head=el('div',{className:'rd-test-head'},[badges,el('div',{className:'rd-test-name'},[document.createTextNode(r.name),poolEl]),el('div',{className:'rd-test-dur'},d2)]);
|
|
1369
2399
|
var body=el('div',{className:'rd-test-body'});
|
|
1370
2400
|
|
|
1371
2401
|
if(r.maxAttempts>1){body.appendChild(el('div',{className:'rd-retries'},'Attempt '+r.attempt+' of '+r.maxAttempts))}
|
|
@@ -1487,10 +2517,8 @@ function loadDetailInline(id,detailTr){
|
|
|
1487
2517
|
if(inner)inner.textContent='Failed to load run detail';
|
|
1488
2518
|
});
|
|
1489
2519
|
}
|
|
1490
|
-
|
|
1491
|
-
/*
|
|
1492
|
-
Screenshots
|
|
1493
|
-
══════════════════════════════════════════════════════════════════ */
|
|
2520
|
+
|
|
2521
|
+
/* ── Screenshots ── */
|
|
1494
2522
|
function refreshScreenshots(){
|
|
1495
2523
|
var gal=$('#screenshotGallery'),empty=$('#screenshotsEmpty');
|
|
1496
2524
|
gal.textContent='';
|
|
@@ -1535,8 +2563,152 @@ function searchByHash(){
|
|
|
1535
2563
|
$('#ssHashBtn').addEventListener('click',searchByHash);
|
|
1536
2564
|
$('#ssHashInput').addEventListener('keydown',function(e){if(e.key==='Enter')searchByHash()});
|
|
1537
2565
|
|
|
2566
|
+
/* ── Learnings ── */
|
|
2567
|
+
function refreshLearnings(){
|
|
2568
|
+
var days=$('#learningsDays').value||30;
|
|
2569
|
+
var url=S.project?'/api/db/projects/'+S.project+'/learnings?days='+days:'/api/db/learnings?days='+days;
|
|
2570
|
+
fetch(url).then(function(r){return r.json()}).then(function(data){
|
|
2571
|
+
if(!data||data.totalRuns===0){
|
|
2572
|
+
$('#learningsEmpty').style.display='block';
|
|
2573
|
+
$('#learningsOverview').textContent='';$('#learningsTrend').textContent='';
|
|
2574
|
+
$('#learningsFlaky').textContent='';$('#learningsSelectors').textContent='';
|
|
2575
|
+
$('#learningsPages').textContent='';$('#learningsApis').textContent='';
|
|
2576
|
+
$('#learningsErrors').textContent='';
|
|
2577
|
+
$('#badgeLearnings').textContent='-';
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
$('#learningsEmpty').style.display='none';
|
|
2581
|
+
S.lastLearningsData=data;
|
|
2582
|
+
var flakyCount=data.flakyTests?data.flakyTests.length:0;
|
|
2583
|
+
var passRate=data.overallPassRate||0;
|
|
2584
|
+
var declining=data.recentTrend&&Array.isArray(data.recentTrend.data||data.recentTrend)&&(function(){
|
|
2585
|
+
var td=data.recentTrend.data||data.recentTrend;
|
|
2586
|
+
if(td.length<2)return false;
|
|
2587
|
+
var last=td[td.length-1].pass_rate;
|
|
2588
|
+
var prior=td.slice(0,-1).reduce(function(s,t){return s+t.pass_rate},0)/(td.length-1);
|
|
2589
|
+
return last-prior<-2;
|
|
2590
|
+
})();
|
|
2591
|
+
if(passRate<70){
|
|
2592
|
+
$('#badgeLearnings').textContent='\u26A0';
|
|
2593
|
+
$('#badgeLearnings').style.background='var(--red-dim)';$('#badgeLearnings').style.color='var(--red)';
|
|
2594
|
+
} else if(flakyCount>0||declining){
|
|
2595
|
+
$('#badgeLearnings').textContent=flakyCount>0?flakyCount:(declining?'\u25BC':'\u2714');
|
|
2596
|
+
$('#badgeLearnings').style.background='var(--amber-dim)';$('#badgeLearnings').style.color='var(--amber)';
|
|
2597
|
+
} else {
|
|
2598
|
+
$('#badgeLearnings').textContent='\u2714';
|
|
2599
|
+
$('#badgeLearnings').style.background='var(--green-dim)';$('#badgeLearnings').style.color='var(--green)';
|
|
2600
|
+
}
|
|
2601
|
+
renderLearnOverview(data);
|
|
2602
|
+
renderLearnTrend(data.recentTrend||[]);
|
|
2603
|
+
renderLearnFlaky(data.flakyTests||[]);
|
|
2604
|
+
renderLearnSelectors(data.unstableSelectors||[]);
|
|
2605
|
+
renderLearnPages(data.failingPages||[]);
|
|
2606
|
+
renderLearnApis(data.apiIssues||[]);
|
|
2607
|
+
renderLearnErrors(data.topErrors||[]);
|
|
2608
|
+
}).catch(function(){$('#learningsEmpty').style.display='block'});
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
function renderLearnOverview(d){
|
|
2612
|
+
var container=$('#learningsOverview');container.textContent='';
|
|
2613
|
+
var grid=document.createElement('div');grid.className='learn-grid';
|
|
2614
|
+
[{val:d.totalRuns,lbl:'Runs',cls:'accent'},{val:d.totalTests,lbl:'Tests',cls:'accent'},
|
|
2615
|
+
{val:d.overallPassRate+'%',lbl:'Pass Rate',cls:d.overallPassRate>=90?'green':d.overallPassRate>=70?'':'red'},
|
|
2616
|
+
{val:d.avgDurationMs<1000?d.avgDurationMs+'ms':(d.avgDurationMs/1000).toFixed(1)+'s',lbl:'Avg Duration',cls:'purple'},
|
|
2617
|
+
{val:(d.flakyTests?d.flakyTests.length:0),lbl:'Flaky Tests',cls:d.flakyTests&&d.flakyTests.length>0?'red':'green'},
|
|
2618
|
+
{val:(d.unstableSelectors?d.unstableSelectors.length:0),lbl:'Unstable Selectors',cls:d.unstableSelectors&&d.unstableSelectors.length>0?'red':'green'}
|
|
2619
|
+
].forEach(function(item){
|
|
2620
|
+
var stat=document.createElement('div');stat.className='learn-stat';
|
|
2621
|
+
var valEl=document.createElement('div');valEl.className='learn-stat-val '+item.cls;valEl.textContent=item.val;
|
|
2622
|
+
var lblEl=document.createElement('div');lblEl.className='learn-stat-lbl';lblEl.textContent=item.lbl;
|
|
2623
|
+
stat.appendChild(valEl);stat.appendChild(lblEl);grid.appendChild(stat);
|
|
2624
|
+
});
|
|
2625
|
+
container.appendChild(grid);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
function renderLearnTrend(trend){
|
|
2629
|
+
var container=$('#learningsTrend');container.textContent='';
|
|
2630
|
+
if(!trend.length)return;
|
|
2631
|
+
var card=document.createElement('div');card.className='card';
|
|
2632
|
+
var label=document.createElement('div');label.className='card-label';label.textContent='Pass Rate Trend (7 days)';card.appendChild(label);
|
|
2633
|
+
var chartDiv=document.createElement('div');chartDiv.className='learn-trend-chart';
|
|
2634
|
+
var w=100/trend.length;var ns='http://www.w3.org/2000/svg';
|
|
2635
|
+
var svg=document.createElementNS(ns,'svg');svg.setAttribute('viewBox','0 0 100 100');svg.setAttribute('preserveAspectRatio','none');
|
|
2636
|
+
var bg=document.createElementNS(ns,'rect');bg.setAttribute('x','0');bg.setAttribute('y','0');bg.setAttribute('width','100');bg.setAttribute('height','100');bg.setAttribute('fill','var(--surface2)');bg.setAttribute('rx','2');svg.appendChild(bg);
|
|
2637
|
+
var gridLine=document.createElementNS(ns,'line');gridLine.setAttribute('x1','0');gridLine.setAttribute('y1','50');gridLine.setAttribute('x2','100');gridLine.setAttribute('y2','50');gridLine.setAttribute('stroke','var(--border)');gridLine.setAttribute('stroke-width','0.3');gridLine.setAttribute('stroke-dasharray','2,2');svg.appendChild(gridLine);
|
|
2638
|
+
var pts=trend.map(function(t,i){return(i*w+w/2)+','+(100-t.pass_rate)}).join(' ');
|
|
2639
|
+
var poly=document.createElementNS(ns,'polygon');poly.setAttribute('points',(0*w+w/2)+',100 '+pts+' '+((trend.length-1)*w+w/2)+',100');poly.setAttribute('fill','var(--accent-dim)');svg.appendChild(poly);
|
|
2640
|
+
var pl=document.createElementNS(ns,'polyline');pl.setAttribute('points',pts);pl.setAttribute('fill','none');pl.setAttribute('stroke','var(--accent)');pl.setAttribute('stroke-width','1.5');svg.appendChild(pl);
|
|
2641
|
+
trend.forEach(function(t,i){
|
|
2642
|
+
var circle=document.createElementNS(ns,'circle');circle.setAttribute('cx',''+(i*w+w/2));circle.setAttribute('cy',''+(100-t.pass_rate));circle.setAttribute('r','2');circle.setAttribute('fill','var(--accent)');
|
|
2643
|
+
var title=document.createElementNS(ns,'title');title.textContent=t.date+': '+t.pass_rate+'% ('+t.total_tests+' tests)';circle.appendChild(title);svg.appendChild(circle);
|
|
2644
|
+
});
|
|
2645
|
+
chartDiv.appendChild(svg);card.appendChild(chartDiv);
|
|
2646
|
+
var dates=document.createElement('div');dates.style.cssText='display:flex;justify-content:space-between;font-size:10px;color:var(--text3);margin-top:4px';
|
|
2647
|
+
dates.appendChild(el('span',null,trend[0].date));dates.appendChild(el('span',null,trend[trend.length-1].date));
|
|
2648
|
+
card.appendChild(dates);container.appendChild(card);
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
function buildLearnTable(title,headers,rows){
|
|
2652
|
+
var card=document.createElement('div');card.className='card learn-section';
|
|
2653
|
+
var h=document.createElement('div');h.className='learn-section-title';h.textContent=title;card.appendChild(h);
|
|
2654
|
+
var wrap=document.createElement('div');wrap.className='tbl-wrap';
|
|
2655
|
+
var tbl=document.createElement('table');tbl.className='learn-table';
|
|
2656
|
+
var thead=document.createElement('thead');var hr=document.createElement('tr');
|
|
2657
|
+
headers.forEach(function(hdr){var th=document.createElement('th');th.textContent=hdr;hr.appendChild(th)});
|
|
2658
|
+
thead.appendChild(hr);tbl.appendChild(thead);
|
|
2659
|
+
var tbody=document.createElement('tbody');
|
|
2660
|
+
rows.forEach(function(cells){
|
|
2661
|
+
var tr=document.createElement('tr');
|
|
2662
|
+
cells.forEach(function(cell){
|
|
2663
|
+
var td=document.createElement('td');
|
|
2664
|
+
if(cell.code){var code=document.createElement('code');code.textContent=cell.code;td.appendChild(code)}
|
|
2665
|
+
else if(cell.badge){var span=document.createElement('span');span.className='badge '+cell.cls;span.textContent=cell.badge;td.appendChild(span)}
|
|
2666
|
+
else{td.textContent=cell.text!==undefined&&cell.text!==null?cell.text:(typeof cell==='object'?'-':cell)}
|
|
2667
|
+
tr.appendChild(td);
|
|
2668
|
+
});
|
|
2669
|
+
tbody.appendChild(tr);
|
|
2670
|
+
});
|
|
2671
|
+
tbl.appendChild(tbody);wrap.appendChild(tbl);card.appendChild(wrap);return card;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function renderLearnFlaky(flaky){var c=$('#learningsFlaky');c.textContent='';if(!flaky.length)return;c.appendChild(buildLearnTable('Flaky Tests',['Test','Flaky Rate','Occurrences','Total Runs','Last Flaky','Avg Attempts'],flaky.map(function(f){return[{code:f.test_name},{badge:f.flaky_rate+'%',cls:f.flaky_rate>30?'fail':'flaky'},{text:f.flaky_count},{text:f.total_runs},{text:(f.last_flaky||'-').split('T')[0]},{text:f.avg_attempts}]})))}
|
|
2675
|
+
function renderLearnSelectors(sels){var c=$('#learningsSelectors');c.textContent='';if(!sels.length)return;c.appendChild(buildLearnTable('Unstable Selectors',['Selector','Action','Fail Rate','Uses','Tests','Page'],sels.map(function(s){var sel=s.selector.length>45?s.selector.slice(0,42)+'...':s.selector;return[{code:sel},{text:s.action_type},{badge:s.fail_rate+'%',cls:s.fail_rate>30?'fail':'flaky'},{text:s.total_uses},{text:s.used_by_tests},{text:s.page_url||'-'}]})))}
|
|
2676
|
+
function renderLearnPages(pages){var c=$('#learningsPages');c.textContent='';if(!pages.length)return;c.appendChild(buildLearnTable('Failing Pages',['Page','Fail Rate','Visits','Console Errors','Network Errors'],pages.map(function(p){return[{code:p.url_path},{badge:p.fail_rate+'%',cls:p.fail_rate>30?'fail':'flaky'},{text:p.total_visits},{text:p.console_errors},{text:p.network_errors}]})))}
|
|
2677
|
+
function renderLearnApis(apis){var c=$('#learningsApis');c.textContent='';if(!apis.length)return;c.appendChild(buildLearnTable('API Issues',['Endpoint','Error Rate','Calls','Avg Duration','Status Codes'],apis.map(function(a){var ep=a.endpoint.length>45?a.endpoint.slice(0,42)+'...':a.endpoint;var d=a.avg_duration_ms<1000?Math.round(a.avg_duration_ms)+'ms':(a.avg_duration_ms/1000).toFixed(1)+'s';return[{code:ep},{badge:a.error_rate+'%',cls:a.error_rate>20?'fail':'flaky'},{text:a.total_calls},{text:d},{text:a.status_codes||'-'}]})))}
|
|
2678
|
+
function renderLearnErrors(errors){var c=$('#learningsErrors');c.textContent='';if(!errors.length)return;c.appendChild(buildLearnTable('Error Patterns',['Pattern','Category','Count','First Seen','Last Seen','Example Test'],errors.map(function(e){var pat=e.pattern.length>50?e.pattern.slice(0,47)+'...':e.pattern;return[{text:pat},{badge:e.category,cls:'run'},{text:e.occurrence_count},{text:(e.first_seen||'-').split('T')[0]},{text:(e.last_seen||'-').split('T')[0]},{code:e.example_test||'-'}]})))}
|
|
2679
|
+
|
|
2680
|
+
$('#btnRefreshLearnings').addEventListener('click',refreshLearnings);
|
|
2681
|
+
$('#learningsDays').addEventListener('change',refreshLearnings);
|
|
2682
|
+
|
|
2683
|
+
$('#btnExportLearnings').addEventListener('click',function(){
|
|
2684
|
+
var data=S.lastLearningsData;
|
|
2685
|
+
if(!data){showToast('No learnings data to export','error');return}
|
|
2686
|
+
var md='# E2E Learnings Report\n\n';
|
|
2687
|
+
md+='| Metric | Value |\n|--------|-------|\n';
|
|
2688
|
+
md+='| Total Runs | '+data.totalRuns+' |\n';
|
|
2689
|
+
md+='| Total Tests | '+data.totalTests+' |\n';
|
|
2690
|
+
md+='| Pass Rate | '+data.overallPassRate+'% |\n';
|
|
2691
|
+
md+='| Avg Duration | '+dur(data.avgDurationMs)+' |\n\n';
|
|
2692
|
+
if(data.flakyTests&&data.flakyTests.length){
|
|
2693
|
+
md+='## Flaky Tests\n\n| Test | Flaky Rate | Occurrences |\n|------|-----------|-------------|\n';
|
|
2694
|
+
data.flakyTests.forEach(function(f){md+='| '+f.test_name+' | '+f.flaky_rate+'% | '+f.flaky_count+' |\n'});md+='\n';
|
|
2695
|
+
}
|
|
2696
|
+
if(data.unstableSelectors&&data.unstableSelectors.length){
|
|
2697
|
+
md+='## Unstable Selectors\n\n| Selector | Action | Fail Rate |\n|----------|--------|-----------|\n';
|
|
2698
|
+
data.unstableSelectors.forEach(function(s){md+='| `'+s.selector+'` | '+s.action_type+' | '+s.fail_rate+'% |\n'});md+='\n';
|
|
2699
|
+
}
|
|
2700
|
+
downloadFile('learnings-report.md',md,'text/markdown');
|
|
2701
|
+
showToast('Learnings exported','success');
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
/* ── Modal ── */
|
|
2705
|
+
function openModal(src){$('#modalImg').src=src;$('#modal').classList.add('open')}
|
|
2706
|
+
$('#modal').addEventListener('click',function(){$('#modal').classList.remove('open')});
|
|
2707
|
+
|
|
2708
|
+
|
|
2709
|
+
/* ── view-live.js ── */
|
|
1538
2710
|
/* ══════════════════════════════════════════════════════════════════
|
|
1539
|
-
Live Execution
|
|
2711
|
+
Live Execution View
|
|
1540
2712
|
══════════════════════════════════════════════════════════════════ */
|
|
1541
2713
|
function clearFinishedLiveRuns(){for(var k in S.liveRuns){if(S.liveRuns[k].done||!S.liveRuns[k].on)delete S.liveRuns[k]}renderLive()}
|
|
1542
2714
|
function dismissLiveRun(rid){delete S.liveRuns[rid];renderLive()}
|
|
@@ -1591,6 +2763,9 @@ function renderLive(){
|
|
|
1591
2763
|
dismissBtn
|
|
1592
2764
|
]));
|
|
1593
2765
|
|
|
2766
|
+
var poolDist=buildPoolDistribution(L.tests);
|
|
2767
|
+
if(poolDist)grid.appendChild(poolDist);
|
|
2768
|
+
|
|
1594
2769
|
var testGrid=el('div',{className:'lr-test-grid'});
|
|
1595
2770
|
Object.keys(L.tests).forEach(function(name){
|
|
1596
2771
|
if(name==='__error')return;
|
|
@@ -1609,12 +2784,13 @@ function renderLive(){
|
|
|
1609
2784
|
var durText=a.duration!=null?(a.duration<1000?a.duration+'ms':(a.duration/1000).toFixed(1)+'s'):'';
|
|
1610
2785
|
var retryBadge=null;
|
|
1611
2786
|
if(a.actionRetries&&a.actionRetries>0){retryBadge=el('span',{className:'badge flaky',style:'font-size:9px;padding:1px 5px;margin-left:4px'},'\u21BB x'+a.actionRetries)}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
el('span',{className:'step-
|
|
1615
|
-
el('span',{className:'step-
|
|
2787
|
+
var stepCls='lt-step'+(a.isPoolLog?' pool-log':'');
|
|
2788
|
+
stepsEl.appendChild(el('div',{className:stepCls},[
|
|
2789
|
+
el('span',{className:'step-icon '+(a.isPoolLog?'':a.success?'ok':'fail')},a.isPoolLog?'\uD83D\uDD17':a.success?'\u2714':'\u2718'),
|
|
2790
|
+
el('span',{className:'step-type'},a.isPoolLog?'pool':a.type),
|
|
2791
|
+
el('span',{className:'step-detail'},a.isPoolLog?a.narrative:detail),
|
|
1616
2792
|
retryBadge,
|
|
1617
|
-
el('span',{className:'step-dur'},durText)
|
|
2793
|
+
a.isPoolLog?null:el('span',{className:'step-dur'},durText)
|
|
1618
2794
|
]));
|
|
1619
2795
|
});
|
|
1620
2796
|
if(t.status==='running'&&t.actions<t.totalActions){stepsEl.appendChild(el('div',{className:'lt-step'},[el('span',{className:'step-icon run spinner-small'}),el('span',{className:'step-type',style:'opacity:.6'},'waiting...')]))}
|
|
@@ -1649,10 +2825,11 @@ function renderLive(){
|
|
|
1649
2825
|
}
|
|
1650
2826
|
|
|
1651
2827
|
var serialBadge=t.serial?el('span',{className:'serial-badge'},'Serial'):null;
|
|
2828
|
+
var poolBadge=t.poolUrl?el('span',{className:'pool-badge'},t.poolUrl.replace('ws://','').replace('wss://','')):null;
|
|
1652
2829
|
var card=el('div',{className:'live-test '+t.status+(isCollapsed?' collapsed':'')},[
|
|
1653
2830
|
el('div',{className:'lt-name'},[
|
|
1654
2831
|
t.status==='running'?el('span',{className:'spinner'}):el('span',{className:'lt-icon',style:iconColor},iconText),
|
|
1655
|
-
document.createTextNode(' '+name),serialBadge,summaryEl
|
|
2832
|
+
document.createTextNode(' '+name),serialBadge,poolBadge,summaryEl
|
|
1656
2833
|
]),
|
|
1657
2834
|
el('div',{className:'lt-meta'},meta),stepsEl
|
|
1658
2835
|
]);
|
|
@@ -1674,140 +2851,10 @@ function renderLive(){
|
|
|
1674
2851
|
});
|
|
1675
2852
|
}
|
|
1676
2853
|
|
|
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
2854
|
|
|
2855
|
+
/* ── keyboard.js ── */
|
|
1809
2856
|
/* ══════════════════════════════════════════════════════════════════
|
|
1810
|
-
Keyboard Shortcuts
|
|
2857
|
+
Keyboard Shortcuts (Updated: 1=Watch, 2=Tests, 3=Runs, 4=Live)
|
|
1811
2858
|
══════════════════════════════════════════════════════════════════ */
|
|
1812
2859
|
document.addEventListener('keydown',function(e){
|
|
1813
2860
|
var tag=document.activeElement.tagName;
|
|
@@ -1815,6 +2862,7 @@ document.addEventListener('keydown',function(e){
|
|
|
1815
2862
|
if(e.key==='Escape'){
|
|
1816
2863
|
if($('#kbModal').classList.contains('open')){$('#kbModal').classList.remove('open');return}
|
|
1817
2864
|
if($('#modal').classList.contains('open')){$('#modal').classList.remove('open');return}
|
|
2865
|
+
if($('#suiteModalOverlay').classList.contains('open')){$('#suiteModalOverlay').classList.remove('open');return}
|
|
1818
2866
|
if(S.selectedRun!==null){
|
|
1819
2867
|
var expanded=document.querySelector('#runsBody tr.expanded');
|
|
1820
2868
|
if(expanded){
|
|
@@ -1827,11 +2875,13 @@ document.addEventListener('keydown',function(e){
|
|
|
1827
2875
|
return;
|
|
1828
2876
|
}
|
|
1829
2877
|
if(e.key==='?'){$('#kbModal').classList.toggle('open');return}
|
|
1830
|
-
var viewMap={'1':'
|
|
2878
|
+
var viewMap={'1':'watch','2':'tests','3':'runs','4':'live','5':'instances'};
|
|
1831
2879
|
if(viewMap[e.key]){showView(viewMap[e.key]);return}
|
|
1832
2880
|
if(e.key==='r'){
|
|
1833
|
-
if(S.view==='
|
|
1834
|
-
else if(S.view==='
|
|
2881
|
+
if(S.view==='watch')refreshWatch();
|
|
2882
|
+
else if(S.view==='tests'){refreshSuites();refreshVariables()}
|
|
2883
|
+
else if(S.view==='runs'){refreshRuns();refreshScreenshots();refreshLearnings()}
|
|
2884
|
+
else if(S.view==='live')renderLive();
|
|
1835
2885
|
return;
|
|
1836
2886
|
}
|
|
1837
2887
|
if(S.view==='runs'&&(e.key==='j'||e.key==='k')){
|
|
@@ -1850,9 +2900,12 @@ document.addEventListener('keydown',function(e){
|
|
|
1850
2900
|
});
|
|
1851
2901
|
$('#kbModal').addEventListener('click',function(e){if(e.target===$('#kbModal'))$('#kbModal').classList.remove('open')});
|
|
1852
2902
|
|
|
2903
|
+
|
|
2904
|
+
/* ── init.js ── */
|
|
1853
2905
|
/* ══════════════════════════════════════════════════════════════════
|
|
1854
|
-
Init
|
|
2906
|
+
Init — startup sequence
|
|
1855
2907
|
══════════════════════════════════════════════════════════════════ */
|
|
2908
|
+
initTabs();
|
|
1856
2909
|
connectWS();
|
|
1857
2910
|
refreshStatus();
|
|
1858
2911
|
refreshProjects();
|
|
@@ -1860,8 +2913,10 @@ refreshSuites();
|
|
|
1860
2913
|
refreshRuns();
|
|
1861
2914
|
refreshScreenshots();
|
|
1862
2915
|
refreshLearnings();
|
|
1863
|
-
|
|
2916
|
+
refreshVariables();
|
|
2917
|
+
startWatchPolling();
|
|
1864
2918
|
|
|
2919
|
+
})();
|
|
1865
2920
|
</script>
|
|
1866
2921
|
</body>
|
|
1867
2922
|
</html>
|