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.
- package/CLAUDE.md +1 -1
- package/backend/_scaffold/utils/browser.ts +47 -2
- package/backend/_scaffold/utils/hooks.ts +25 -2
- package/backend/config/scripts/run-tests.js +35 -13
- package/backend/logs/runner-cmr8o2x3n0000mg01410tpuck.log +20 -0
- package/backend/logs/runner-cmr90hvcx0000n2016edtmrtv.log +43 -0
- package/backend/logs/runner-cmr90i7h20001n201g2wg6v3n.log +43 -0
- package/backend/logs/runner-cmr90iilm0002n201mmisva95.log +43 -0
- package/backend/logs/runner-cmr90j70r0003n201xz8525dz.log +20 -0
- package/backend/logs/runner-cmr90yup70000qp017ianc2gp.log +43 -0
- package/backend/logs/runner-cmr90zbtm0001qp01u9tfdkh1.log +20 -0
- package/backend/logs/runner-cmr91a3mw0000tb01414rd1df.log +20 -0
- package/backend/logs/runner-cmr91dhla0001tb01jp7thjnl.log +20 -0
- package/backend/prisma/migrations/20260706000000_add_report_logs/migration.sql +2 -0
- package/backend/prisma/migrations/20260707032554_add_perf_indexes/migration.sql +44 -0
- package/backend/prisma/schema.prisma +20 -0
- package/backend/routes/node.routes.js +42 -4
- package/backend/routes/reports.routes.js +4 -2
- package/backend/services/reportService.js +58 -20
- package/backend/services/runnerService.js +11 -1
- package/backend/websockets/socketHandler.js +162 -16
- package/bin/plum.js +96 -68
- package/frontend/package.json +1 -1
- package/frontend/src/lib/api/reports.js +16 -5
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +53 -55
- package/frontend/src/lib/components/ui/AutomatedBadge.svelte +56 -0
- package/frontend/src/lib/components/ui/BackLink.svelte +77 -0
- package/frontend/src/lib/components/ui/Badge.svelte +1 -1
- package/frontend/src/lib/components/ui/Button.svelte +1 -1
- package/frontend/src/lib/components/ui/CaseIdChip.svelte +59 -0
- package/frontend/src/lib/components/ui/ConfirmModal.svelte +1 -1
- package/frontend/src/lib/components/ui/Pagination.svelte +1 -1
- package/frontend/src/lib/components/ui/PriorityBadge.svelte +83 -0
- package/frontend/src/lib/components/ui/ResultChip.svelte +92 -0
- package/frontend/src/lib/components/ui/StatusDot.svelte +64 -0
- package/frontend/src/lib/components/ui/StepKeyword.svelte +79 -0
- package/frontend/src/lib/components/ui/StepStatusIcon.svelte +66 -0
- package/frontend/src/lib/components/ui/TagChip.svelte +51 -0
- package/frontend/src/lib/constants.js +14 -0
- package/frontend/src/lib/stores/runner.js +5 -3
- package/frontend/src/lib/styles/tokens.css +26 -0
- package/frontend/src/lib/utils/format.js +74 -0
- package/frontend/src/routes/+page.svelte +3 -3
- package/frontend/src/routes/login/+page.svelte +55 -40
- package/frontend/src/routes/reports/+page.svelte +35 -27
- package/frontend/src/routes/reports/[id]/+page.svelte +703 -215
- package/frontend/src/routes/reports/live/+page.svelte +281 -283
- package/frontend/src/routes/scheduled-tests/+page.svelte +3 -3
- package/frontend/src/routes/settings/+page.svelte +6 -6
- package/frontend/src/routes/setup/+page.svelte +2 -1
- package/frontend/src/routes/test-repository/+page.svelte +5 -5
- package/frontend/src/routes/test-repository/runs/[id]/+page.svelte +33 -152
- package/frontend/src/routes/test-repository/suites/[id]/+page.svelte +4 -4
- package/frontend/vite.config.js +7 -1
- 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
|
-
|
|
51
|
-
<div class="
|
|
52
|
-
<
|
|
53
|
-
|
|
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
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class="
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
{#if error}
|
|
99
|
+
<p class="error">{error}</p>
|
|
100
|
+
{/if}
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
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(
|
|
38
|
-
$:
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
reports.forEach((r) => next.delete(r.id));
|
|
77
89
|
selected = next;
|
|
78
90
|
} else {
|
|
79
91
|
const next = new Set(selected);
|
|
80
|
-
|
|
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
|
-
{
|
|
148
|
+
{total} run{total !== 1 ? 's' : ''} recorded
|
|
137
149
|
</p>
|
|
138
150
|
</div>
|
|
139
151
|
|
|
140
|
-
{#if
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
479
|
+
color: var(--white);
|
|
472
480
|
}
|
|
473
481
|
|
|
474
482
|
/* ── Report rows ── */
|