plum-e2e 2.4.13 → 2.5.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 (55) hide show
  1. package/CLAUDE.md +1 -1
  2. package/backend/_scaffold/utils/browser.ts +47 -2
  3. package/backend/_scaffold/utils/hooks.ts +25 -2
  4. package/backend/config/scripts/run-tests.js +35 -13
  5. package/backend/logs/runner-cmr8o2x3n0000mg01410tpuck.log +20 -0
  6. package/backend/logs/runner-cmr90hvcx0000n2016edtmrtv.log +43 -0
  7. package/backend/logs/runner-cmr90i7h20001n201g2wg6v3n.log +43 -0
  8. package/backend/logs/runner-cmr90iilm0002n201mmisva95.log +43 -0
  9. package/backend/logs/runner-cmr90j70r0003n201xz8525dz.log +20 -0
  10. package/backend/logs/runner-cmr90yup70000qp017ianc2gp.log +43 -0
  11. package/backend/logs/runner-cmr90zbtm0001qp01u9tfdkh1.log +20 -0
  12. package/backend/logs/runner-cmr91a3mw0000tb01414rd1df.log +20 -0
  13. package/backend/logs/runner-cmr91dhla0001tb01jp7thjnl.log +20 -0
  14. package/backend/prisma/migrations/20260706000000_add_report_logs/migration.sql +2 -0
  15. package/backend/prisma/migrations/20260707032554_add_perf_indexes/migration.sql +44 -0
  16. package/backend/prisma/schema.prisma +20 -0
  17. package/backend/routes/node.routes.js +42 -4
  18. package/backend/routes/reports.routes.js +4 -2
  19. package/backend/services/reportService.js +58 -20
  20. package/backend/services/runnerService.js +11 -1
  21. package/backend/websockets/socketHandler.js +162 -16
  22. package/bin/plum.js +96 -68
  23. package/frontend/package.json +1 -1
  24. package/frontend/src/lib/api/reports.js +16 -5
  25. package/frontend/src/lib/components/layout/RunnerPanel.svelte +53 -55
  26. package/frontend/src/lib/components/ui/AutomatedBadge.svelte +56 -0
  27. package/frontend/src/lib/components/ui/BackLink.svelte +77 -0
  28. package/frontend/src/lib/components/ui/Badge.svelte +1 -1
  29. package/frontend/src/lib/components/ui/Button.svelte +1 -1
  30. package/frontend/src/lib/components/ui/CaseIdChip.svelte +59 -0
  31. package/frontend/src/lib/components/ui/ConfirmModal.svelte +1 -1
  32. package/frontend/src/lib/components/ui/Pagination.svelte +1 -1
  33. package/frontend/src/lib/components/ui/PriorityBadge.svelte +83 -0
  34. package/frontend/src/lib/components/ui/ResultChip.svelte +92 -0
  35. package/frontend/src/lib/components/ui/StatusDot.svelte +64 -0
  36. package/frontend/src/lib/components/ui/StepKeyword.svelte +79 -0
  37. package/frontend/src/lib/components/ui/StepStatusIcon.svelte +66 -0
  38. package/frontend/src/lib/components/ui/TagChip.svelte +51 -0
  39. package/frontend/src/lib/constants.js +14 -0
  40. package/frontend/src/lib/stores/runner.js +5 -3
  41. package/frontend/src/lib/styles/tokens.css +26 -0
  42. package/frontend/src/lib/utils/format.js +74 -0
  43. package/frontend/src/routes/+page.svelte +3 -3
  44. package/frontend/src/routes/login/+page.svelte +55 -40
  45. package/frontend/src/routes/reports/+page.svelte +35 -27
  46. package/frontend/src/routes/reports/[id]/+page.svelte +703 -215
  47. package/frontend/src/routes/reports/live/+page.svelte +281 -283
  48. package/frontend/src/routes/scheduled-tests/+page.svelte +3 -3
  49. package/frontend/src/routes/settings/+page.svelte +6 -6
  50. package/frontend/src/routes/setup/+page.svelte +2 -1
  51. package/frontend/src/routes/test-repository/+page.svelte +5 -5
  52. package/frontend/src/routes/test-repository/runs/[id]/+page.svelte +33 -152
  53. package/frontend/src/routes/test-repository/suites/[id]/+page.svelte +4 -4
  54. package/frontend/vite.config.js +7 -1
  55. package/package.json +1 -1
@@ -16,14 +16,27 @@
16
16
  -->
17
17
 
18
18
  <script>
19
+ import { onMount } from 'svelte';
20
+ import { goto } from '$app/navigation';
19
21
  import { auth } from '$lib/stores/auth';
20
- import { login } from '$lib/api/auth';
22
+ import { login, checkNeedsSetup } from '$lib/api/auth';
21
23
  import { theme } from '$lib/stores/theme';
22
24
 
23
25
  let email = '';
24
26
  let password = '';
25
27
  let error = '';
26
28
  let loading = false;
29
+ let checking = true;
30
+
31
+ onMount(async () => {
32
+ try {
33
+ if (await checkNeedsSetup()) {
34
+ goto('/setup');
35
+ return;
36
+ }
37
+ } catch {}
38
+ checking = false;
39
+ });
27
40
 
28
41
  async function handleSubmit() {
29
42
  error = '';
@@ -47,48 +60,50 @@
47
60
  <svelte:head><title>Sign in — Plum</title></svelte:head>
48
61
 
49
62
  <div class="page" data-theme={$theme}>
50
- <div class="card">
51
- <div class="brand">
52
- <span class="brand-serif">Pl</span><span class="brand-sans">um</span>
53
- </div>
54
- <h1 class="title">Sign in</h1>
55
- <p class="subtitle">Access your test workspace</p>
56
-
57
- <div class="fields">
58
- <div class="field">
59
- <label class="label" for="email">Email</label>
60
- <input
61
- id="email"
62
- type="email"
63
- class="input"
64
- bind:value={email}
65
- placeholder="jane@example.com"
66
- autocomplete="email"
67
- on:keydown={onKeydown}
68
- />
63
+ {#if !checking}
64
+ <div class="card">
65
+ <div class="brand">
66
+ <span class="brand-serif">Pl</span><span class="brand-sans">um</span>
69
67
  </div>
70
- <div class="field">
71
- <label class="label" for="password">Password</label>
72
- <input
73
- id="password"
74
- type="password"
75
- class="input"
76
- bind:value={password}
77
- placeholder="••••••••"
78
- autocomplete="current-password"
79
- on:keydown={onKeydown}
80
- />
68
+ <h1 class="title">Sign in</h1>
69
+ <p class="subtitle">Access your test workspace</p>
70
+
71
+ <div class="fields">
72
+ <div class="field">
73
+ <label class="label" for="email">Email</label>
74
+ <input
75
+ id="email"
76
+ type="email"
77
+ class="input"
78
+ bind:value={email}
79
+ placeholder="jane@example.com"
80
+ autocomplete="email"
81
+ on:keydown={onKeydown}
82
+ />
83
+ </div>
84
+ <div class="field">
85
+ <label class="label" for="password">Password</label>
86
+ <input
87
+ id="password"
88
+ type="password"
89
+ class="input"
90
+ bind:value={password}
91
+ placeholder="••••••••"
92
+ autocomplete="current-password"
93
+ on:keydown={onKeydown}
94
+ />
95
+ </div>
81
96
  </div>
82
- </div>
83
97
 
84
- {#if error}
85
- <p class="error">{error}</p>
86
- {/if}
98
+ {#if error}
99
+ <p class="error">{error}</p>
100
+ {/if}
87
101
 
88
- <button class="submit-btn" on:click={handleSubmit} disabled={loading || !email || !password}>
89
- {loading ? 'Signing in…' : 'Sign in'}
90
- </button>
91
- </div>
102
+ <button class="submit-btn" on:click={handleSubmit} disabled={loading || !email || !password}>
103
+ {loading ? 'Signing in…' : 'Sign in'}
104
+ </button>
105
+ </div>
106
+ {/if}
92
107
  </div>
93
108
 
94
109
  <style>
@@ -188,7 +203,7 @@
188
203
  .submit-btn {
189
204
  height: 40px;
190
205
  background: var(--accent);
191
- color: #fff;
206
+ color: var(--white);
192
207
  border: none;
193
208
  border-radius: var(--radius-sm);
194
209
  font-family: var(--font-body);
@@ -27,6 +27,10 @@
27
27
  import EmptyState from '$lib/components/ui/EmptyState.svelte';
28
28
 
29
29
  let reports = [];
30
+ let total = 0;
31
+ let passCount = 0;
32
+ let failCount = 0;
33
+ let trend = [];
30
34
  let currentPage = 1;
31
35
  let animateBar = false;
32
36
 
@@ -34,21 +38,24 @@
34
38
  let deleteModal = { open: false, targets: [] };
35
39
  let deleting = false;
36
40
 
37
- $: totalPages = Math.ceil(reports.length / REPORTS_PER_PAGE);
38
- $: paginated = reports.slice(
39
- (currentPage - 1) * REPORTS_PER_PAGE,
40
- currentPage * REPORTS_PER_PAGE
41
- );
42
- $: passCount = reports.filter((r) => r.status === 'PASS').length;
43
- $: failCount = reports.length - passCount;
44
- $: passRate = reports.length ? Math.round((passCount / reports.length) * 100) : 0;
45
- $: trend = reports.slice(0, 12).reverse();
46
- $: allOnPageSelected = paginated.length > 0 && paginated.every((r) => selected.has(r.id));
41
+ $: totalPages = Math.ceil(total / REPORTS_PER_PAGE);
42
+ $: passRate = total ? Math.round((passCount / total) * 100) : 0;
43
+ $: trendDots = [...trend].reverse();
44
+ $: allOnPageSelected = reports.length > 0 && reports.every((r) => selected.has(r.id));
47
45
  $: someSelected = selected.size > 0;
48
46
 
49
47
  async function loadReports() {
50
48
  try {
51
- reports = await fetchReports();
49
+ let data = await fetchReports({ page: currentPage, limit: REPORTS_PER_PAGE });
50
+ if (data.reports.length === 0 && currentPage > 1) {
51
+ currentPage = Math.max(1, Math.ceil(data.total / REPORTS_PER_PAGE));
52
+ data = await fetchReports({ page: currentPage, limit: REPORTS_PER_PAGE });
53
+ }
54
+ reports = data.reports;
55
+ total = data.total;
56
+ passCount = data.passCount;
57
+ failCount = data.failCount;
58
+ trend = data.trend;
52
59
  selected = new Set();
53
60
  await tick();
54
61
  animateBar = true;
@@ -57,6 +64,11 @@
57
64
  }
58
65
  }
59
66
 
67
+ function goToPage(page) {
68
+ currentPage = page;
69
+ loadReports();
70
+ }
71
+
60
72
  onMount(loadReports);
61
73
  $: if ($reportsVersion) loadReports();
62
74
 
@@ -73,11 +85,11 @@
73
85
  e.stopPropagation();
74
86
  if (allOnPageSelected) {
75
87
  const next = new Set(selected);
76
- paginated.forEach((r) => next.delete(r.id));
88
+ reports.forEach((r) => next.delete(r.id));
77
89
  selected = next;
78
90
  } else {
79
91
  const next = new Set(selected);
80
- paginated.forEach((r) => next.add(r.id));
92
+ reports.forEach((r) => next.add(r.id));
81
93
  selected = next;
82
94
  }
83
95
  }
@@ -133,11 +145,11 @@
133
145
  <div>
134
146
  <h1>Reports</h1>
135
147
  <p class="subtitle">
136
- {reports.length} run{reports.length !== 1 ? 's' : ''} recorded
148
+ {total} run{total !== 1 ? 's' : ''} recorded
137
149
  </p>
138
150
  </div>
139
151
 
140
- {#if reports.length > 0}
152
+ {#if total > 0}
141
153
  <div class="rate-display">
142
154
  <span
143
155
  class="rate-number"
@@ -152,7 +164,7 @@
152
164
  {/if}
153
165
  </div>
154
166
 
155
- {#if reports.length > 0}
167
+ {#if total > 0}
156
168
  <div class="stats-bar">
157
169
  <div class="pass-bar-track">
158
170
  <div class="pass-bar-fill" style="width: {animateBar ? passRate + '%' : '0'}"></div>
@@ -166,7 +178,7 @@
166
178
  <div class="trend-row">
167
179
  <span class="trend-label">Recent</span>
168
180
  <div class="trend-dots">
169
- {#each trend as r, i}
181
+ {#each trendDots as r, i}
170
182
  <span
171
183
  class="trend-dot"
172
184
  class:pass={r.status === 'PASS'}
@@ -181,7 +193,7 @@
181
193
  {/if}
182
194
  </div>
183
195
 
184
- {#if reports.length === 0}
196
+ {#if total === 0}
185
197
  <EmptyState message="No reports yet. Run a test to generate one." />
186
198
  {:else}
187
199
  <div class="list-header">
@@ -202,7 +214,7 @@
202
214
  </div>
203
215
 
204
216
  <div class="report-list">
205
- {#each paginated as report, i}
217
+ {#each reports as report, i}
206
218
  <div class="report-row" class:is-selected={selected.has(report.id)} style={stagger(i)}>
207
219
  <label class="row-check-wrap" title="Select">
208
220
  <input
@@ -287,11 +299,7 @@
287
299
 
288
300
  {#if totalPages > 1}
289
301
  <div class="pagination-wrap">
290
- <Pagination
291
- current={currentPage}
292
- total={totalPages}
293
- on:change={(e) => (currentPage = e.detail)}
294
- />
302
+ <Pagination current={currentPage} total={totalPages} on:change={(e) => goToPage(e.detail)} />
295
303
  </div>
296
304
  {/if}
297
305
  {/if}
@@ -361,7 +369,7 @@
361
369
  .pass-bar-track {
362
370
  height: 3px;
363
371
  background: var(--fail-soft);
364
- border-radius: 100px;
372
+ border-radius: var(--radius-pill);
365
373
  overflow: hidden;
366
374
  margin-bottom: 0.5rem;
367
375
  }
@@ -369,7 +377,7 @@
369
377
  .pass-bar-fill {
370
378
  height: 100%;
371
379
  background: var(--pass);
372
- border-radius: 100px;
380
+ border-radius: var(--radius-pill);
373
381
  transition: width 0.9s var(--ease-out) 0.1s;
374
382
  }
375
383
 
@@ -468,7 +476,7 @@
468
476
 
469
477
  .btn-delete-selected:hover {
470
478
  background: var(--fail);
471
- color: #fff;
479
+ color: var(--white);
472
480
  }
473
481
 
474
482
  /* ── Report rows ── */