qa360 1.4.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/ai.d.ts +41 -0
  3. package/dist/commands/ai.js +499 -0
  4. package/dist/commands/ask.js +12 -12
  5. package/dist/commands/coverage.d.ts +8 -0
  6. package/dist/commands/coverage.js +252 -0
  7. package/dist/commands/explain.d.ts +27 -0
  8. package/dist/commands/explain.js +630 -0
  9. package/dist/commands/flakiness.d.ts +73 -0
  10. package/dist/commands/flakiness.js +435 -0
  11. package/dist/commands/generate.d.ts +66 -0
  12. package/dist/commands/generate.js +438 -0
  13. package/dist/commands/init.d.ts +56 -9
  14. package/dist/commands/init.js +217 -10
  15. package/dist/commands/monitor.d.ts +27 -0
  16. package/dist/commands/monitor.js +225 -0
  17. package/dist/commands/ollama.d.ts +40 -0
  18. package/dist/commands/ollama.js +301 -0
  19. package/dist/commands/pack.d.ts +37 -9
  20. package/dist/commands/pack.js +240 -141
  21. package/dist/commands/regression.d.ts +8 -0
  22. package/dist/commands/regression.js +340 -0
  23. package/dist/commands/repair.d.ts +26 -0
  24. package/dist/commands/repair.js +307 -0
  25. package/dist/commands/retry.d.ts +43 -0
  26. package/dist/commands/retry.js +275 -0
  27. package/dist/commands/run.d.ts +8 -3
  28. package/dist/commands/run.js +45 -31
  29. package/dist/commands/slo.d.ts +8 -0
  30. package/dist/commands/slo.js +327 -0
  31. package/dist/core/adapters/playwright-native-api.d.ts +183 -0
  32. package/dist/core/adapters/playwright-native-api.js +461 -0
  33. package/dist/core/adapters/playwright-ui.d.ts +7 -0
  34. package/dist/core/adapters/playwright-ui.js +29 -1
  35. package/dist/core/ai/anthropic-provider.d.ts +50 -0
  36. package/dist/core/ai/anthropic-provider.js +211 -0
  37. package/dist/core/ai/deepseek-provider.d.ts +81 -0
  38. package/dist/core/ai/deepseek-provider.js +254 -0
  39. package/dist/core/ai/index.d.ts +60 -0
  40. package/dist/core/ai/index.js +18 -0
  41. package/dist/core/ai/llm-client.d.ts +45 -0
  42. package/dist/core/ai/llm-client.js +7 -0
  43. package/dist/core/ai/mock-provider.d.ts +49 -0
  44. package/dist/core/ai/mock-provider.js +121 -0
  45. package/dist/core/ai/ollama-provider.d.ts +78 -0
  46. package/dist/core/ai/ollama-provider.js +192 -0
  47. package/dist/core/ai/openai-provider.d.ts +48 -0
  48. package/dist/core/ai/openai-provider.js +188 -0
  49. package/dist/core/ai/provider-factory.d.ts +160 -0
  50. package/dist/core/ai/provider-factory.js +269 -0
  51. package/dist/core/auth/api-key-provider.d.ts +16 -0
  52. package/dist/core/auth/api-key-provider.js +63 -0
  53. package/dist/core/auth/aws-iam-provider.d.ts +35 -0
  54. package/dist/core/auth/aws-iam-provider.js +177 -0
  55. package/dist/core/auth/azure-ad-provider.d.ts +15 -0
  56. package/dist/core/auth/azure-ad-provider.js +99 -0
  57. package/dist/core/auth/basic-auth-provider.d.ts +26 -0
  58. package/dist/core/auth/basic-auth-provider.js +111 -0
  59. package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
  60. package/dist/core/auth/gcp-adc-provider.js +126 -0
  61. package/dist/core/auth/index.d.ts +238 -0
  62. package/dist/core/auth/index.js +82 -0
  63. package/dist/core/auth/jwt-provider.d.ts +19 -0
  64. package/dist/core/auth/jwt-provider.js +160 -0
  65. package/dist/core/auth/manager.d.ts +84 -0
  66. package/dist/core/auth/manager.js +230 -0
  67. package/dist/core/auth/oauth2-provider.d.ts +17 -0
  68. package/dist/core/auth/oauth2-provider.js +114 -0
  69. package/dist/core/auth/totp-provider.d.ts +31 -0
  70. package/dist/core/auth/totp-provider.js +134 -0
  71. package/dist/core/auth/ui-login-provider.d.ts +26 -0
  72. package/dist/core/auth/ui-login-provider.js +198 -0
  73. package/dist/core/cache/index.d.ts +7 -0
  74. package/dist/core/cache/index.js +6 -0
  75. package/dist/core/cache/lru-cache.d.ts +203 -0
  76. package/dist/core/cache/lru-cache.js +397 -0
  77. package/dist/core/coverage/analyzer.d.ts +101 -0
  78. package/dist/core/coverage/analyzer.js +415 -0
  79. package/dist/core/coverage/collector.d.ts +74 -0
  80. package/dist/core/coverage/collector.js +459 -0
  81. package/dist/core/coverage/config.d.ts +37 -0
  82. package/dist/core/coverage/config.js +156 -0
  83. package/dist/core/coverage/index.d.ts +11 -0
  84. package/dist/core/coverage/index.js +15 -0
  85. package/dist/core/coverage/types.d.ts +267 -0
  86. package/dist/core/coverage/types.js +6 -0
  87. package/dist/core/coverage/vault.d.ts +95 -0
  88. package/dist/core/coverage/vault.js +405 -0
  89. package/dist/core/dashboard/assets.d.ts +6 -0
  90. package/dist/core/dashboard/assets.js +690 -0
  91. package/dist/core/dashboard/index.d.ts +6 -0
  92. package/dist/core/dashboard/index.js +5 -0
  93. package/dist/core/dashboard/server.d.ts +72 -0
  94. package/dist/core/dashboard/server.js +354 -0
  95. package/dist/core/dashboard/types.d.ts +70 -0
  96. package/dist/core/dashboard/types.js +5 -0
  97. package/dist/core/discoverer/index.d.ts +115 -0
  98. package/dist/core/discoverer/index.js +250 -0
  99. package/dist/core/flakiness/index.d.ts +228 -0
  100. package/dist/core/flakiness/index.js +384 -0
  101. package/dist/core/generation/code-formatter.d.ts +111 -0
  102. package/dist/core/generation/code-formatter.js +307 -0
  103. package/dist/core/generation/code-generator.d.ts +144 -0
  104. package/dist/core/generation/code-generator.js +293 -0
  105. package/dist/core/generation/generator.d.ts +40 -0
  106. package/dist/core/generation/generator.js +76 -0
  107. package/dist/core/generation/index.d.ts +30 -0
  108. package/dist/core/generation/index.js +28 -0
  109. package/dist/core/generation/pack-generator.d.ts +107 -0
  110. package/dist/core/generation/pack-generator.js +416 -0
  111. package/dist/core/generation/prompt-builder.d.ts +132 -0
  112. package/dist/core/generation/prompt-builder.js +672 -0
  113. package/dist/core/generation/source-analyzer.d.ts +213 -0
  114. package/dist/core/generation/source-analyzer.js +657 -0
  115. package/dist/core/generation/test-optimizer.d.ts +117 -0
  116. package/dist/core/generation/test-optimizer.js +328 -0
  117. package/dist/core/generation/types.d.ts +214 -0
  118. package/dist/core/generation/types.js +4 -0
  119. package/dist/core/index.d.ts +23 -1
  120. package/dist/core/index.js +39 -0
  121. package/dist/core/pack/validator.js +31 -1
  122. package/dist/core/pack-v2/index.d.ts +9 -0
  123. package/dist/core/pack-v2/index.js +8 -0
  124. package/dist/core/pack-v2/loader.d.ts +62 -0
  125. package/dist/core/pack-v2/loader.js +231 -0
  126. package/dist/core/pack-v2/migrator.d.ts +56 -0
  127. package/dist/core/pack-v2/migrator.js +455 -0
  128. package/dist/core/pack-v2/validator.d.ts +61 -0
  129. package/dist/core/pack-v2/validator.js +577 -0
  130. package/dist/core/regression/detector.d.ts +107 -0
  131. package/dist/core/regression/detector.js +497 -0
  132. package/dist/core/regression/index.d.ts +9 -0
  133. package/dist/core/regression/index.js +11 -0
  134. package/dist/core/regression/trend-analyzer.d.ts +102 -0
  135. package/dist/core/regression/trend-analyzer.js +345 -0
  136. package/dist/core/regression/types.d.ts +222 -0
  137. package/dist/core/regression/types.js +7 -0
  138. package/dist/core/regression/vault.d.ts +87 -0
  139. package/dist/core/regression/vault.js +289 -0
  140. package/dist/core/repair/engine/fixer.d.ts +24 -0
  141. package/dist/core/repair/engine/fixer.js +226 -0
  142. package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
  143. package/dist/core/repair/engine/suggestion-engine.js +187 -0
  144. package/dist/core/repair/index.d.ts +10 -0
  145. package/dist/core/repair/index.js +13 -0
  146. package/dist/core/repair/repairer.d.ts +90 -0
  147. package/dist/core/repair/repairer.js +284 -0
  148. package/dist/core/repair/types.d.ts +91 -0
  149. package/dist/core/repair/types.js +6 -0
  150. package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
  151. package/dist/core/repair/utils/error-analyzer.js +264 -0
  152. package/dist/core/retry/flakiness-integration.d.ts +60 -0
  153. package/dist/core/retry/flakiness-integration.js +228 -0
  154. package/dist/core/retry/index.d.ts +14 -0
  155. package/dist/core/retry/index.js +16 -0
  156. package/dist/core/retry/retry-engine.d.ts +80 -0
  157. package/dist/core/retry/retry-engine.js +296 -0
  158. package/dist/core/retry/types.d.ts +178 -0
  159. package/dist/core/retry/types.js +52 -0
  160. package/dist/core/retry/vault.d.ts +77 -0
  161. package/dist/core/retry/vault.js +304 -0
  162. package/dist/core/runner/e2e-helpers.d.ts +102 -0
  163. package/dist/core/runner/e2e-helpers.js +153 -0
  164. package/dist/core/runner/phase3-runner.d.ts +101 -2
  165. package/dist/core/runner/phase3-runner.js +559 -24
  166. package/dist/core/self-healing/assertion-healer.d.ts +97 -0
  167. package/dist/core/self-healing/assertion-healer.js +371 -0
  168. package/dist/core/self-healing/engine.d.ts +122 -0
  169. package/dist/core/self-healing/engine.js +538 -0
  170. package/dist/core/self-healing/index.d.ts +10 -0
  171. package/dist/core/self-healing/index.js +11 -0
  172. package/dist/core/self-healing/selector-healer.d.ts +103 -0
  173. package/dist/core/self-healing/selector-healer.js +372 -0
  174. package/dist/core/self-healing/types.d.ts +152 -0
  175. package/dist/core/self-healing/types.js +6 -0
  176. package/dist/core/slo/config.d.ts +107 -0
  177. package/dist/core/slo/config.js +360 -0
  178. package/dist/core/slo/index.d.ts +11 -0
  179. package/dist/core/slo/index.js +15 -0
  180. package/dist/core/slo/sli-calculator.d.ts +92 -0
  181. package/dist/core/slo/sli-calculator.js +364 -0
  182. package/dist/core/slo/slo-tracker.d.ts +148 -0
  183. package/dist/core/slo/slo-tracker.js +379 -0
  184. package/dist/core/slo/types.d.ts +281 -0
  185. package/dist/core/slo/types.js +7 -0
  186. package/dist/core/slo/vault.d.ts +102 -0
  187. package/dist/core/slo/vault.js +427 -0
  188. package/dist/core/tui/index.d.ts +7 -0
  189. package/dist/core/tui/index.js +6 -0
  190. package/dist/core/tui/monitor.d.ts +92 -0
  191. package/dist/core/tui/monitor.js +271 -0
  192. package/dist/core/tui/renderer.d.ts +33 -0
  193. package/dist/core/tui/renderer.js +218 -0
  194. package/dist/core/tui/types.d.ts +63 -0
  195. package/dist/core/tui/types.js +5 -0
  196. package/dist/core/types/pack-v2.d.ts +425 -0
  197. package/dist/core/types/pack-v2.js +8 -0
  198. package/dist/core/vault/index.d.ts +116 -0
  199. package/dist/core/vault/index.js +400 -5
  200. package/dist/core/watch/index.d.ts +7 -0
  201. package/dist/core/watch/index.js +6 -0
  202. package/dist/core/watch/watch-mode.d.ts +213 -0
  203. package/dist/core/watch/watch-mode.js +389 -0
  204. package/dist/index.js +68 -68
  205. package/dist/utils/config.d.ts +5 -0
  206. package/dist/utils/config.js +136 -0
  207. package/package.json +5 -1
  208. package/dist/core/adapters/playwright-api.d.ts +0 -82
  209. package/dist/core/adapters/playwright-api.js +0 -264
@@ -0,0 +1,690 @@
1
+ /**
2
+ * Dashboard Assets - HTML/CSS/JS for the web dashboard
3
+ * Embedded as strings to avoid external file dependencies
4
+ */
5
+ export const DASHBOARD_HTML = `<!DOCTYPE html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <title>QA360 - Test Execution Dashboard</title>
11
+ <style>
12
+ * { margin: 0; padding: 0; box-sizing: border-box; }
13
+ :root {
14
+ --bg-primary: #0d1117;
15
+ --bg-secondary: #161b22;
16
+ --bg-tertiary: #21262d;
17
+ --border: #30363d;
18
+ --text-primary: #c9d1d9;
19
+ --text-secondary: #8b949e;
20
+ --accent: #58a6ff;
21
+ --success: #3fb950;
22
+ --danger: #f85149;
23
+ --warning: #d29922;
24
+ --pending: #8b949e;
25
+ }
26
+ body {
27
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
28
+ background: var(--bg-primary);
29
+ color: var(--text-primary);
30
+ min-height: 100vh;
31
+ line-height: 1.5;
32
+ }
33
+ .container {
34
+ max-width: 1400px;
35
+ margin: 0 auto;
36
+ padding: 20px;
37
+ }
38
+ .header {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ padding: 20px;
43
+ background: var(--bg-secondary);
44
+ border: 1px solid var(--border);
45
+ border-radius: 8px;
46
+ margin-bottom: 20px;
47
+ }
48
+ .header h1 {
49
+ font-size: 24px;
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 10px;
53
+ }
54
+ .badge {
55
+ padding: 4px 12px;
56
+ border-radius: 20px;
57
+ font-size: 12px;
58
+ font-weight: 600;
59
+ text-transform: uppercase;
60
+ }
61
+ .badge.idle { background: var(--bg-tertiary); color: var(--pending); }
62
+ .badge.running { background: rgba(88, 166, 255, 0.15); color: var(--accent); animation: pulse 2s infinite; }
63
+ .badge.completed { background: rgba(63, 185, 80, 0.15); color: var(--success); }
64
+ .badge.failed { background: rgba(248, 81, 73, 0.15); color: var(--danger); }
65
+ @keyframes pulse {
66
+ 0%, 100% { opacity: 1; }
67
+ 50% { opacity: 0.7; }
68
+ }
69
+ .stats {
70
+ display: grid;
71
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
72
+ gap: 15px;
73
+ margin-bottom: 20px;
74
+ }
75
+ .stat-card {
76
+ background: var(--bg-secondary);
77
+ border: 1px solid var(--border);
78
+ border-radius: 8px;
79
+ padding: 20px;
80
+ }
81
+ .stat-label {
82
+ font-size: 12px;
83
+ color: var(--text-secondary);
84
+ text-transform: uppercase;
85
+ letter-spacing: 0.5px;
86
+ }
87
+ .stat-value {
88
+ font-size: 32px;
89
+ font-weight: 700;
90
+ margin-top: 5px;
91
+ }
92
+ .stat-value.success { color: var(--success); }
93
+ .stat-value.danger { color: var(--danger); }
94
+ .progress-section {
95
+ background: var(--bg-secondary);
96
+ border: 1px solid var(--border);
97
+ border-radius: 8px;
98
+ padding: 20px;
99
+ margin-bottom: 20px;
100
+ }
101
+ .progress-bar {
102
+ height: 8px;
103
+ background: var(--bg-tertiary);
104
+ border-radius: 4px;
105
+ overflow: hidden;
106
+ margin-top: 10px;
107
+ }
108
+ .progress-fill {
109
+ height: 100%;
110
+ background: linear-gradient(90deg, var(--accent), var(--success));
111
+ transition: width 0.3s ease;
112
+ }
113
+ .gates-grid {
114
+ display: grid;
115
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
116
+ gap: 15px;
117
+ }
118
+ .gate-card {
119
+ background: var(--bg-secondary);
120
+ border: 1px solid var(--border);
121
+ border-radius: 8px;
122
+ padding: 15px;
123
+ transition: all 0.2s ease;
124
+ }
125
+ .gate-card:hover {
126
+ border-color: var(--accent);
127
+ }
128
+ .gate-card.running {
129
+ border-color: var(--accent);
130
+ box-shadow: 0 0 0 1px var(--accent);
131
+ }
132
+ .gate-header {
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: center;
136
+ margin-bottom: 10px;
137
+ }
138
+ .gate-name {
139
+ font-weight: 600;
140
+ }
141
+ .gate-status {
142
+ width: 12px;
143
+ height: 12px;
144
+ border-radius: 50%;
145
+ }
146
+ .gate-status.pending { background: var(--pending); }
147
+ .gate-status.running { background: var(--accent); animation: pulse 1s infinite; }
148
+ .gate-status.passed { background: var(--success); }
149
+ .gate-status.failed { background: var(--danger); }
150
+ .gate-status.skipped { background: var(--pending); }
151
+ .gate-tests {
152
+ font-size: 13px;
153
+ color: var(--text-secondary);
154
+ }
155
+ .gate-duration {
156
+ font-size: 12px;
157
+ color: var(--text-secondary);
158
+ margin-top: 5px;
159
+ }
160
+ .footer {
161
+ text-align: center;
162
+ padding: 20px;
163
+ color: var(--text-secondary);
164
+ font-size: 13px;
165
+ }
166
+ .reconnect-info {
167
+ text-align: center;
168
+ padding: 10px;
169
+ background: var(--bg-tertiary);
170
+ border-radius: 4px;
171
+ margin-bottom: 20px;
172
+ font-size: 12px;
173
+ color: var(--text-secondary);
174
+ }
175
+ .flakiness-section {
176
+ background: var(--bg-secondary);
177
+ border: 1px solid var(--border);
178
+ border-radius: 8px;
179
+ padding: 20px;
180
+ margin-bottom: 20px;
181
+ }
182
+ .flakiness-header {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ margin-bottom: 15px;
187
+ }
188
+ .flakiness-title {
189
+ font-size: 18px;
190
+ font-weight: 600;
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 8px;
194
+ }
195
+ .flakiness-score {
196
+ font-size: 48px;
197
+ font-weight: 700;
198
+ text-align: center;
199
+ padding: 15px;
200
+ border-radius: 8px;
201
+ background: var(--bg-tertiary);
202
+ }
203
+ .flakiness-score.legendary { color: #3fb950; }
204
+ .flakiness-score.solid { color: #3fb950; }
205
+ .flakiness-score.good { color: #d29922; }
206
+ .flakiness-score.shaky { color: #d29922; }
207
+ .flakiness-score.unstable { color: #f85149; }
208
+ .flakiness-score-label {
209
+ text-align: center;
210
+ font-size: 14px;
211
+ color: var(--text-secondary);
212
+ margin-top: 5px;
213
+ }
214
+ .flakiness-grid {
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
217
+ gap: 10px;
218
+ margin-top: 15px;
219
+ }
220
+ .flakiness-dist-item {
221
+ background: var(--bg-tertiary);
222
+ border-radius: 6px;
223
+ padding: 10px;
224
+ text-align: center;
225
+ }
226
+ .flakiness-dist-emoji {
227
+ font-size: 20px;
228
+ }
229
+ .flakiness-dist-label {
230
+ font-size: 11px;
231
+ color: var(--text-secondary);
232
+ margin-top: 4px;
233
+ }
234
+ .flakiness-dist-value {
235
+ font-size: 20px;
236
+ font-weight: 600;
237
+ margin-top: 4px;
238
+ }
239
+ .quarantine-list {
240
+ margin-top: 15px;
241
+ }
242
+ .quarantine-item {
243
+ background: var(--bg-tertiary);
244
+ border-left: 3px solid var(--danger);
245
+ border-radius: 4px;
246
+ padding: 12px;
247
+ margin-bottom: 10px;
248
+ }
249
+ .quarantine-item.resolved {
250
+ border-left-color: var(--success);
251
+ opacity: 0.7;
252
+ }
253
+ .quarantine-header {
254
+ display: flex;
255
+ justify-content: space-between;
256
+ align-items: center;
257
+ margin-bottom: 6px;
258
+ }
259
+ .quarantine-test-name {
260
+ font-weight: 600;
261
+ }
262
+ .quarantine-badge {
263
+ padding: 2px 8px;
264
+ border-radius: 4px;
265
+ font-size: 10px;
266
+ font-weight: 600;
267
+ text-transform: uppercase;
268
+ }
269
+ .quarantine-badge.active {
270
+ background: rgba(248, 81, 73, 0.15);
271
+ color: var(--danger);
272
+ }
273
+ .quarantine-badge.resolved {
274
+ background: rgba(63, 185, 80, 0.15);
275
+ color: var(--success);
276
+ }
277
+ .quarantine-reason {
278
+ font-size: 13px;
279
+ color: var(--text-secondary);
280
+ }
281
+ .quarantine-meta {
282
+ font-size: 11px;
283
+ color: var(--text-secondary);
284
+ margin-top: 6px;
285
+ }
286
+ .patterns-grid {
287
+ display: grid;
288
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
289
+ gap: 10px;
290
+ margin-top: 15px;
291
+ }
292
+ .pattern-card {
293
+ background: var(--bg-tertiary);
294
+ border-radius: 6px;
295
+ padding: 12px;
296
+ }
297
+ .pattern-header {
298
+ display: flex;
299
+ justify-content: space-between;
300
+ align-items: center;
301
+ margin-bottom: 8px;
302
+ }
303
+ .pattern-type {
304
+ font-size: 12px;
305
+ font-weight: 600;
306
+ text-transform: uppercase;
307
+ color: var(--accent);
308
+ }
309
+ .pattern-confidence {
310
+ font-size: 11px;
311
+ padding: 2px 6px;
312
+ border-radius: 4px;
313
+ background: rgba(88, 166, 255, 0.15);
314
+ color: var(--accent);
315
+ }
316
+ .pattern-test {
317
+ font-size: 13px;
318
+ font-weight: 500;
319
+ margin-bottom: 4px;
320
+ }
321
+ .pattern-description {
322
+ font-size: 12px;
323
+ color: var(--text-secondary);
324
+ margin-bottom: 6px;
325
+ }
326
+ .pattern-fix {
327
+ font-size: 11px;
328
+ color: var(--success);
329
+ }
330
+ .section-tabs {
331
+ display: flex;
332
+ gap: 10px;
333
+ margin-bottom: 15px;
334
+ border-bottom: 1px solid var(--border);
335
+ }
336
+ .tab {
337
+ padding: 8px 16px;
338
+ cursor: pointer;
339
+ border-bottom: 2px solid transparent;
340
+ color: var(--text-secondary);
341
+ transition: all 0.2s;
342
+ }
343
+ .tab:hover {
344
+ color: var(--text-primary);
345
+ }
346
+ .tab.active {
347
+ color: var(--accent);
348
+ border-bottom-color: var(--accent);
349
+ }
350
+ .tab-content {
351
+ display: none;
352
+ }
353
+ .tab-content.active {
354
+ display: block;
355
+ }
356
+ .empty-state {
357
+ text-align: center;
358
+ padding: 30px;
359
+ color: var(--text-secondary);
360
+ }
361
+ .empty-state-icon {
362
+ font-size: 40px;
363
+ margin-bottom: 10px;
364
+ }
365
+ </style>
366
+ </head>
367
+ <body>
368
+ <div class="container">
369
+ <div class="header">
370
+ <h1>
371
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
372
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
373
+ <path d="M2 17l10 5 10-5"/>
374
+ <path d="M2 12l10 5 10-5"/>
375
+ </svg>
376
+ QA360 Dashboard
377
+ </h1>
378
+ <span id="status-badge" class="badge idle">Idle</span>
379
+ </div>
380
+
381
+ <div class="reconnect-info" id="connection-status">
382
+ Connecting...
383
+ </div>
384
+
385
+ <div class="stats">
386
+ <div class="stat-card">
387
+ <div class="stat-label">Total Gates</div>
388
+ <div class="stat-value" id="total-gates">0</div>
389
+ </div>
390
+ <div class="stat-card">
391
+ <div class="stat-label">Passed</div>
392
+ <div class="stat-value success" id="passed-gates">0</div>
393
+ </div>
394
+ <div class="stat-card">
395
+ <div class="stat-label">Failed</div>
396
+ <div class="stat-value danger" id="failed-gates">0</div>
397
+ </div>
398
+ <div class="stat-card">
399
+ <div class="stat-label">Duration</div>
400
+ <div class="stat-value" id="duration">0s</div>
401
+ </div>
402
+ </div>
403
+
404
+ <div class="progress-section">
405
+ <div style="display: flex; justify-content: space-between; align-items: center;">
406
+ <span id="phase-label">Ready to start</span>
407
+ <span id="progress-percent">0%</span>
408
+ </div>
409
+ <div class="progress-bar">
410
+ <div class="progress-fill" id="progress-fill" style="width: 0%"></div>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- Flakiness Section -->
415
+ <div class="flakiness-section" id="flakiness-section" style="display: none;">
416
+ <div class="flakiness-header">
417
+ <div class="flakiness-title">
418
+ <span>🎲</span>
419
+ <span>Test Flakiness</span>
420
+ </div>
421
+ <span id="flakiness-badge" class="badge"></span>
422
+ </div>
423
+
424
+ <div class="section-tabs">
425
+ <div class="tab active" data-tab="overview">Overview</div>
426
+ <div class="tab" data-tab="quarantine">Quarantine</div>
427
+ <div class="tab" data-tab="patterns">Patterns</div>
428
+ </div>
429
+
430
+ <!-- Overview Tab -->
431
+ <div class="tab-content active" id="tab-overview">
432
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
433
+ <div>
434
+ <div class="flakiness-score" id="flakiness-score">-</div>
435
+ <div class="flakiness-score-label">Average Score</div>
436
+ </div>
437
+ <div class="stat-card">
438
+ <div class="stat-label">Total Tests</div>
439
+ <div class="stat-value" id="flakiness-total">0</div>
440
+ </div>
441
+ </div>
442
+
443
+ <div class="flakiness-grid">
444
+ <div class="flakiness-dist-item">
445
+ <div class="flakiness-dist-emoji">🟢</div>
446
+ <div class="flakiness-dist-label">Legendary</div>
447
+ <div class="flakiness-dist-value" id="dist-legendary">0</div>
448
+ </div>
449
+ <div class="flakiness-dist-item">
450
+ <div class="flakiness-dist-emoji">🟢</div>
451
+ <div class="flakiness-dist-label">Solid</div>
452
+ <div class="flakiness-dist-value" id="dist-solid">0</div>
453
+ </div>
454
+ <div class="flakiness-dist-item">
455
+ <div class="flakiness-dist-emoji">🟡</div>
456
+ <div class="flakiness-dist-label">Good</div>
457
+ <div class="flakiness-dist-value" id="dist-good">0</div>
458
+ </div>
459
+ <div class="flakiness-dist-item">
460
+ <div class="flakiness-dist-emoji">🟠</div>
461
+ <div class="flakiness-dist-label">Shaky</div>
462
+ <div class="flakiness-dist-value" id="dist-shaky">0</div>
463
+ </div>
464
+ <div class="flakiness-dist-item">
465
+ <div class="flakiness-dist-emoji">🔴</div>
466
+ <div class="flakiness-dist-label">Unstable</div>
467
+ <div class="flakiness-dist-value" id="dist-unstable">0</div>
468
+ </div>
469
+ </div>
470
+ </div>
471
+
472
+ <!-- Quarantine Tab -->
473
+ <div class="tab-content" id="tab-quarantine">
474
+ <div id="quarantine-list" class="quarantine-list"></div>
475
+ </div>
476
+
477
+ <!-- Patterns Tab -->
478
+ <div class="tab-content" id="tab-patterns">
479
+ <div id="patterns-grid" class="patterns-grid"></div>
480
+ </div>
481
+ </div>
482
+
483
+ <div class="gates-grid" id="gates-container"></div>
484
+
485
+ <div class="footer">
486
+ QA360 v1.4.7 - Real-time test execution monitoring
487
+ </div>
488
+ </div>
489
+
490
+ <script>
491
+ let eventSource = null;
492
+ let reconnectTimer = null;
493
+
494
+ // Tab switching
495
+ document.querySelectorAll('.tab').forEach(tab => {
496
+ tab.addEventListener('click', () => {
497
+ const tabName = tab.dataset.tab;
498
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
499
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
500
+ tab.classList.add('active');
501
+ document.getElementById('tab-' + tabName).classList.add('active');
502
+ });
503
+ });
504
+
505
+ function connect() {
506
+ eventSource = new EventSource('/events');
507
+
508
+ eventSource.onopen = () => {
509
+ console.log('Connected to dashboard');
510
+ document.getElementById('connection-status').textContent = 'Connected';
511
+ document.getElementById('connection-status').style.display = 'none';
512
+ if (reconnectTimer) {
513
+ clearTimeout(reconnectTimer);
514
+ reconnectTimer = null;
515
+ }
516
+ };
517
+
518
+ eventSource.onmessage = (event) => {
519
+ const state = JSON.parse(event.data);
520
+ updateState(state);
521
+ };
522
+
523
+ eventSource.onerror = (error) => {
524
+ console.error('SSE error:', error);
525
+ document.getElementById('connection-status').textContent = 'Disconnected, reconnecting...';
526
+ document.getElementById('connection-status').style.display = 'block';
527
+ if (eventSource) {
528
+ eventSource.close();
529
+ }
530
+ reconnectTimer = setTimeout(connect, 2000);
531
+ };
532
+ }
533
+
534
+ function updateState(state) {
535
+ // Update status badge
536
+ const badge = document.getElementById('status-badge');
537
+ badge.className = 'badge ' + state.status;
538
+ badge.textContent = state.status;
539
+
540
+ // Update stats
541
+ document.getElementById('total-gates').textContent = state.gates.length;
542
+ document.getElementById('passed-gates').textContent =
543
+ state.gates.filter(g => g.status === 'passed').length;
544
+ document.getElementById('failed-gates').textContent =
545
+ state.gates.filter(g => g.status === 'failed').length;
546
+ document.getElementById('duration').textContent = formatDuration(state.elapsed);
547
+
548
+ // Update progress
549
+ document.getElementById('phase-label').textContent = state.currentPhase || 'Ready';
550
+ document.getElementById('progress-percent').textContent = state.progress.toFixed(1) + '%';
551
+ document.getElementById('progress-fill').style.width = state.progress + '%';
552
+
553
+ // Update flakiness section
554
+ updateFlakiness(state.flakiness);
555
+
556
+ // Update gates
557
+ renderGates(state.gates);
558
+ }
559
+
560
+ function updateFlakiness(flakiness) {
561
+ const section = document.getElementById('flakiness-section');
562
+ if (!flakiness || !flakiness.enabled) {
563
+ section.style.display = 'none';
564
+ return;
565
+ }
566
+
567
+ section.style.display = 'block';
568
+
569
+ // Update badge
570
+ const badge = document.getElementById('flakiness-badge');
571
+ const scoreClass = getScoreClass(flakiness.averageScore);
572
+ badge.className = 'badge ' + scoreClass;
573
+ badge.textContent = Math.round(flakiness.averageScore) + '%';
574
+
575
+ // Update score display
576
+ const scoreEl = document.getElementById('flakiness-score');
577
+ scoreEl.textContent = Math.round(flakiness.averageScore) + '%';
578
+ scoreEl.className = 'flakiness-score ' + scoreClass;
579
+
580
+ // Update total tests
581
+ document.getElementById('flakiness-total').textContent = flakiness.totalTests;
582
+
583
+ // Update distribution
584
+ document.getElementById('dist-legendary').textContent = flakiness.distribution.legendary;
585
+ document.getElementById('dist-solid').textContent = flakiness.distribution.solid;
586
+ document.getElementById('dist-good').textContent = flakiness.distribution.good;
587
+ document.getElementById('dist-shaky').textContent = flakiness.distribution.shaky;
588
+ document.getElementById('dist-unstable').textContent = flakiness.distribution.unstable;
589
+
590
+ // Update quarantine list
591
+ renderQuarantine(flakiness.quarantined);
592
+
593
+ // Update patterns
594
+ renderPatterns(flakiness.recentPatterns);
595
+ }
596
+
597
+ function getScoreClass(score) {
598
+ if (score >= 90) return 'legendary';
599
+ if (score >= 75) return 'good';
600
+ if (score >= 50) return 'shaky';
601
+ return 'unstable';
602
+ }
603
+
604
+ function renderQuarantine(quarantined) {
605
+ const container = document.getElementById('quarantine-list');
606
+ if (!quarantined || quarantined.length === 0) {
607
+ container.innerHTML = \`
608
+ <div class="empty-state">
609
+ <div class="empty-state-icon">🚫</div>
610
+ <div>No tests in quarantine</div>
611
+ </div>
612
+ \`;
613
+ return;
614
+ }
615
+
616
+ container.innerHTML = quarantined.map(q => \`
617
+ <div class="quarantine-item \${q.resolved ? 'resolved' : ''}">
618
+ <div class="quarantine-header">
619
+ <span class="quarantine-test-name">\${q.testName}</span>
620
+ <span class="quarantine-badge \${q.resolved ? 'resolved' : 'active'}">
621
+ \${q.resolved ? 'Resolved' : 'Active'}
622
+ </span>
623
+ </div>
624
+ <div class="quarantine-reason">\${q.reason}</div>
625
+ <div class="quarantine-meta">
626
+ \${q.gate} • Score: \${q.score}% • \${new Date(q.quarantinedAt).toLocaleDateString()}
627
+ </div>
628
+ </div>
629
+ \`).join('');
630
+ }
631
+
632
+ function renderPatterns(patterns) {
633
+ const container = document.getElementById('patterns-grid');
634
+ if (!patterns || patterns.length === 0) {
635
+ container.innerHTML = \`
636
+ <div class="empty-state" style="grid-column: 1 / -1;">
637
+ <div class="empty-state-icon">🔍</div>
638
+ <div>No flakiness patterns detected</div>
639
+ </div>
640
+ \`;
641
+ return;
642
+ }
643
+
644
+ container.innerHTML = patterns.map(p => \`
645
+ <div class="pattern-card">
646
+ <div class="pattern-header">
647
+ <span class="pattern-type">\${p.patternType.replace(/_/g, ' ')}</span>
648
+ <span class="pattern-confidence">\${Math.round(p.confidence * 100)}% confidence</span>
649
+ </div>
650
+ <div class="pattern-test">\${p.testName}</div>
651
+ <div class="pattern-description">\${p.description}</div>
652
+ <div class="pattern-fix">💡 \${p.suggestedFix}</div>
653
+ <div style="font-size: 10px; color: var(--text-secondary); margin-top: 6px;">
654
+ Detected \${p.detectionCount}x
655
+ </div>
656
+ </div>
657
+ \`).join('');
658
+ }
659
+
660
+ function renderGates(gates) {
661
+ const container = document.getElementById('gates-container');
662
+ container.innerHTML = gates.map(gate => \`
663
+ <div class="gate-card \${gate.status}">
664
+ <div class="gate-header">
665
+ <span class="gate-name">\${gate.name}</span>
666
+ <span class="gate-status \${gate.status}"></span>
667
+ </div>
668
+ <div class="gate-tests">
669
+ \${gate.testsTotal ? \`\${gate.testsPassed || 0}/\${gate.testsTotal}\` : '--'}
670
+ \${gate.testsFailed ? \` (\${gate.testsFailed} failed)\` : ''}
671
+ </div>
672
+ \${gate.duration ? \`<div class="gate-duration">\${formatDuration(gate.duration)}</div>\` : ''}
673
+ </div>
674
+ \`).join('');
675
+ }
676
+
677
+ function formatDuration(ms) {
678
+ if (ms < 1000) return ms + 'ms';
679
+ if (ms < 60000) return (ms / 1000).toFixed(1) + 's';
680
+ const min = Math.floor(ms / 60000);
681
+ const sec = Math.floor((ms % 60000) / 1000);
682
+ return min + 'm ' + sec + 's';
683
+ }
684
+
685
+ // Auto-connect
686
+ connect();
687
+ </script>
688
+ </body>
689
+ </html>`;
690
+ export const DASHBOARD_FAVICON = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📊</text></svg>`;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * QA360 Dashboard Module
3
+ * Phase 8: Web Dashboard for test execution visualization
4
+ */
5
+ export type { DashboardConfig, DashboardState, DashboardGateState, DashboardEvent, DashboardFlakinessState, FlakinessDistribution, DashboardQuarantineItem, DashboardPatternItem } from './types.js';
6
+ export { DashboardServer } from './server.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * QA360 Dashboard Module
3
+ * Phase 8: Web Dashboard for test execution visualization
4
+ */
5
+ export { DashboardServer } from './server.js';