plum-e2e 2.4.13 → 2.5.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.
Files changed (49) hide show
  1. package/backend/_scaffold/utils/browser.ts +47 -2
  2. package/backend/_scaffold/utils/hooks.ts +25 -2
  3. package/backend/config/scripts/run-tests.js +35 -13
  4. package/backend/logs/runner-cmr8o2x3n0000mg01410tpuck.log +20 -0
  5. package/backend/logs/runner-cmr90hvcx0000n2016edtmrtv.log +43 -0
  6. package/backend/logs/runner-cmr90i7h20001n201g2wg6v3n.log +43 -0
  7. package/backend/logs/runner-cmr90iilm0002n201mmisva95.log +43 -0
  8. package/backend/logs/runner-cmr90j70r0003n201xz8525dz.log +20 -0
  9. package/backend/logs/runner-cmr90yup70000qp017ianc2gp.log +43 -0
  10. package/backend/logs/runner-cmr90zbtm0001qp01u9tfdkh1.log +20 -0
  11. package/backend/logs/runner-cmr91a3mw0000tb01414rd1df.log +20 -0
  12. package/backend/logs/runner-cmr91dhla0001tb01jp7thjnl.log +20 -0
  13. package/backend/prisma/migrations/20260706000000_add_report_logs/migration.sql +2 -0
  14. package/backend/prisma/schema.prisma +1 -0
  15. package/backend/routes/node.routes.js +42 -4
  16. package/backend/services/reportService.js +24 -4
  17. package/backend/services/runnerService.js +11 -1
  18. package/backend/websockets/socketHandler.js +162 -16
  19. package/frontend/package.json +1 -1
  20. package/frontend/src/lib/components/layout/RunnerPanel.svelte +53 -55
  21. package/frontend/src/lib/components/ui/AutomatedBadge.svelte +56 -0
  22. package/frontend/src/lib/components/ui/BackLink.svelte +77 -0
  23. package/frontend/src/lib/components/ui/Badge.svelte +1 -1
  24. package/frontend/src/lib/components/ui/Button.svelte +1 -1
  25. package/frontend/src/lib/components/ui/CaseIdChip.svelte +59 -0
  26. package/frontend/src/lib/components/ui/ConfirmModal.svelte +1 -1
  27. package/frontend/src/lib/components/ui/Pagination.svelte +1 -1
  28. package/frontend/src/lib/components/ui/PriorityBadge.svelte +83 -0
  29. package/frontend/src/lib/components/ui/ResultChip.svelte +92 -0
  30. package/frontend/src/lib/components/ui/StatusDot.svelte +64 -0
  31. package/frontend/src/lib/components/ui/StepKeyword.svelte +79 -0
  32. package/frontend/src/lib/components/ui/StepStatusIcon.svelte +66 -0
  33. package/frontend/src/lib/components/ui/TagChip.svelte +51 -0
  34. package/frontend/src/lib/constants.js +14 -0
  35. package/frontend/src/lib/stores/runner.js +5 -3
  36. package/frontend/src/lib/styles/tokens.css +26 -0
  37. package/frontend/src/lib/utils/format.js +74 -0
  38. package/frontend/src/routes/+page.svelte +3 -3
  39. package/frontend/src/routes/login/+page.svelte +1 -1
  40. package/frontend/src/routes/reports/+page.svelte +3 -3
  41. package/frontend/src/routes/reports/[id]/+page.svelte +703 -215
  42. package/frontend/src/routes/reports/live/+page.svelte +281 -283
  43. package/frontend/src/routes/scheduled-tests/+page.svelte +3 -3
  44. package/frontend/src/routes/settings/+page.svelte +6 -6
  45. package/frontend/src/routes/setup/+page.svelte +1 -1
  46. package/frontend/src/routes/test-repository/+page.svelte +5 -5
  47. package/frontend/src/routes/test-repository/runs/[id]/+page.svelte +27 -151
  48. package/frontend/src/routes/test-repository/suites/[id]/+page.svelte +4 -4
  49. package/package.json +1 -1
@@ -630,7 +630,7 @@
630
630
  width: 14px;
631
631
  height: 14px;
632
632
  border-radius: 50%;
633
- background: #fff;
633
+ background: var(--white);
634
634
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
635
635
  transition: left var(--duration-fast);
636
636
  }
@@ -666,7 +666,7 @@
666
666
 
667
667
  .run-icon-btn:hover {
668
668
  background: var(--accent);
669
- color: #fff;
669
+ color: var(--white);
670
670
  border-color: var(--accent);
671
671
  }
672
672
 
@@ -803,7 +803,7 @@
803
803
  }
804
804
  .seg-btn.active {
805
805
  background: var(--accent);
806
- color: #fff;
806
+ color: var(--white);
807
807
  }
808
808
  .seg-btn.active .seg-label {
809
809
  opacity: 0.85;
@@ -1828,7 +1828,7 @@
1828
1828
  flex-shrink: 0;
1829
1829
  width: 40px;
1830
1830
  height: 22px;
1831
- border-radius: 100px;
1831
+ border-radius: var(--radius-pill);
1832
1832
  border: none;
1833
1833
  background: var(--border);
1834
1834
  cursor: pointer;
@@ -1908,7 +1908,7 @@
1908
1908
  color: var(--text-muted);
1909
1909
  background: var(--bg-subtle);
1910
1910
  border: 1px solid var(--border);
1911
- border-radius: 100px;
1911
+ border-radius: var(--radius-pill);
1912
1912
  padding: 0.1rem 0.45rem;
1913
1913
  flex-shrink: 0;
1914
1914
  }
@@ -1935,7 +1935,7 @@
1935
1935
  font-size: 0.7rem;
1936
1936
  font-weight: 500;
1937
1937
  padding: 0.15rem 0.5rem;
1938
- border-radius: 100px;
1938
+ border-radius: var(--radius-pill);
1939
1939
  flex-shrink: 0;
1940
1940
  }
1941
1941
 
@@ -2084,7 +2084,7 @@
2084
2084
  flex-shrink: 0;
2085
2085
  width: 40px;
2086
2086
  height: 22px;
2087
- border-radius: 100px;
2087
+ border-radius: var(--radius-pill);
2088
2088
  border: none;
2089
2089
  background: var(--border);
2090
2090
  cursor: pointer;
@@ -2274,7 +2274,7 @@
2274
2274
  .role-chip {
2275
2275
  font-size: 0.7rem;
2276
2276
  font-weight: 500;
2277
- border-radius: 100px;
2277
+ border-radius: var(--radius-pill);
2278
2278
  padding: 0.15rem 0.55rem;
2279
2279
  flex-shrink: 0;
2280
2280
  }
@@ -2295,7 +2295,7 @@
2295
2295
  color: var(--text-muted);
2296
2296
  background: var(--bg-subtle);
2297
2297
  border: 1px solid var(--border);
2298
- border-radius: 100px;
2298
+ border-radius: var(--radius-pill);
2299
2299
  padding: 0.15rem 0.55rem;
2300
2300
  flex-shrink: 0;
2301
2301
  }
@@ -228,7 +228,7 @@
228
228
  .submit-btn {
229
229
  height: 40px;
230
230
  background: var(--accent);
231
- color: #fff;
231
+ color: var(--white);
232
232
  border: none;
233
233
  border-radius: var(--radius-sm);
234
234
  font-family: var(--font-body);
@@ -890,7 +890,7 @@
890
890
  font-size: 0.7rem;
891
891
  background: var(--bg-subtle);
892
892
  border: 1px solid var(--border);
893
- border-radius: 100px;
893
+ border-radius: var(--radius-pill);
894
894
  padding: 0.05rem 0.4rem;
895
895
  color: var(--text-muted);
896
896
  }
@@ -991,7 +991,7 @@
991
991
  color: var(--text-muted);
992
992
  background: var(--bg-elevated);
993
993
  border: 1px solid var(--border);
994
- border-radius: 100px;
994
+ border-radius: var(--radius-pill);
995
995
  cursor: pointer;
996
996
  transition:
997
997
  color var(--duration-fast),
@@ -1181,7 +1181,7 @@
1181
1181
  text-transform: uppercase;
1182
1182
  color: var(--pass);
1183
1183
  background: var(--pass-soft);
1184
- border-radius: 100px;
1184
+ border-radius: var(--radius-pill);
1185
1185
  padding: 0.1rem 0.4rem;
1186
1186
  flex-shrink: 0;
1187
1187
  }
@@ -1190,7 +1190,7 @@
1190
1190
  .priority-badge {
1191
1191
  font-size: 0.68rem;
1192
1192
  font-weight: 500;
1193
- border-radius: 100px;
1193
+ border-radius: var(--radius-pill);
1194
1194
  padding: 0.15rem 0.5rem;
1195
1195
  flex-shrink: 0;
1196
1196
  }
@@ -1320,7 +1320,7 @@
1320
1320
  color: var(--text-muted);
1321
1321
  background: var(--bg-subtle);
1322
1322
  border: 1px solid var(--border);
1323
- border-radius: 100px;
1323
+ border-radius: var(--radius-pill);
1324
1324
  padding: 0.1rem 0.4rem;
1325
1325
  }
1326
1326
 
@@ -31,7 +31,12 @@
31
31
  import { auth } from '$lib/stores/auth';
32
32
  import Button from '$lib/components/ui/Button.svelte';
33
33
  import Toast from '$lib/components/ui/Toast.svelte';
34
- import { TOAST_TIMEOUT_MS } from '$lib/constants';
34
+ import EmptyState from '$lib/components/ui/EmptyState.svelte';
35
+ import AutomatedBadge from '$lib/components/ui/AutomatedBadge.svelte';
36
+ import PriorityBadge from '$lib/components/ui/PriorityBadge.svelte';
37
+ import CaseIdChip from '$lib/components/ui/CaseIdChip.svelte';
38
+ import ResultChip from '$lib/components/ui/ResultChip.svelte';
39
+ import { TOAST_TIMEOUT_MS, RUN_REFRESH_MS } from '$lib/constants';
35
40
 
36
41
  const runId = $page.params.id;
37
42
 
@@ -133,7 +138,7 @@
133
138
  const fresh = await fetchRun(runId);
134
139
  run = fresh;
135
140
  } catch {}
136
- }, 15000);
141
+ }, RUN_REFRESH_MS);
137
142
  });
138
143
 
139
144
  onDestroy(() => {
@@ -292,18 +297,6 @@
292
297
  dragOverEntryId = null;
293
298
  }
294
299
 
295
- function priorityClass(p) {
296
- return p?.toLowerCase() ?? 'medium';
297
- }
298
-
299
- function statusClass(s) {
300
- if (s === 'pass') return 'pass';
301
- if (s === 'fail') return 'fail';
302
- if (s === 'blocked') return 'warn';
303
- if (s === 'skip') return 'muted';
304
- return 'pending';
305
- }
306
-
307
300
  $: completedCount = run?.entries.filter((e) => e.status !== 'pending').length ?? 0;
308
301
  $: totalCount = run?.entries.length ?? 0;
309
302
  $: passCount = run?.entries.filter((e) => e.status === 'pass').length ?? 0;
@@ -378,7 +371,7 @@
378
371
  </div>
379
372
 
380
373
  {#if run.entries.length === 0}
381
- <div class="drop-hint">No cases in this run.</div>
374
+ <EmptyState message="No cases in this run." size="sm" />
382
375
  {:else}
383
376
  <div class="entry-sort-bar">
384
377
  {#each [['order', 'Order'], ['name', 'Name'], ['priority', 'Priority'], ['status', 'Status'], ['suite', 'Suite']] as [val, label]}
@@ -428,16 +421,14 @@
428
421
  {/if}
429
422
  <div class="entry-info">
430
423
  <div class="entry-badges">
431
- <span class="id-chip">{entry.case.displayId}</span>
432
- <span class="priority-badge {priorityClass(entry.case.priority)}"
433
- >{entry.case.priority}</span
434
- >
424
+ <CaseIdChip id={entry.case.displayId} />
425
+ <PriorityBadge priority={entry.case.priority} />
435
426
  </div>
436
427
  <p class="entry-title">{entry.case.title}</p>
437
428
  <span class="entry-suite">{entry.case.suite?.name ?? ''}</span>
438
429
  </div>
439
430
  {#if entry.case.isAutomated}
440
- <span class="assignee-auto-label">Automated</span>
431
+ <AutomatedBadge />
441
432
  {:else}
442
433
  <select
443
434
  class="assignee-select"
@@ -511,13 +502,11 @@
511
502
  <div class="browser-cases" transition:fly={{ y: -4, duration: 120 }}>
512
503
  {#each suite.cases as tc (tc.id)}
513
504
  <button class="browser-case" on:click={() => addCase(tc)}>
514
- <span class="id-chip small">{tc.displayId}</span>
505
+ <CaseIdChip id={tc.displayId} small />
515
506
  <span class="browser-case-title">{tc.title}</span>
516
- <span class="priority-badge small {priorityClass(tc.priority)}"
517
- >{tc.priority}</span
518
- >
507
+ <PriorityBadge priority={tc.priority} small />
519
508
  {#if tc.isAutomated}
520
- <span class="auto-pill">Auto</span>
509
+ <AutomatedBadge />
521
510
  {/if}
522
511
  </button>
523
512
  {/each}
@@ -586,8 +575,8 @@
586
575
  >
587
576
  </button>
588
577
  {/if}
589
- <span class="id-chip">{entry.case.displayId}</span>
590
- {#if entry.case.isAutomated}<span class="auto-pill">Automated</span>{/if}
578
+ <CaseIdChip id={entry.case.displayId} />
579
+ {#if entry.case.isAutomated}<AutomatedBadge />{/if}
591
580
  <div>
592
581
  <p class="exec-title">{entry.case.title}</p>
593
582
  <span class="exec-suite">{entry.case.suite?.name ?? ''}</span>
@@ -596,7 +585,7 @@
596
585
  <div class="exec-actions">
597
586
  {#if entry.case.isAutomated}
598
587
  <div class="exec-assignee-row">
599
- <span class="exec-assignee-name auto-label">Automated</span>
588
+ <AutomatedBadge />
600
589
  </div>
601
590
  <div class="exec-assignee-divider"></div>
602
591
  {:else if entry.assignedTo || (!isLocked && entry.assignedTo?.id !== currentUserId)}
@@ -617,7 +606,7 @@
617
606
  <div class="exec-assignee-divider"></div>
618
607
  {/if}
619
608
  {#if isLocked || run.status !== 'in-progress'}
620
- <span class="result-chip {statusClass(entry.status)}">{entry.status}</span>
609
+ <ResultChip status={entry.status} />
621
610
  {:else if entry.status === 'pending' || entry.status === 'in-progress'}
622
611
  <div class="exec-btns">
623
612
  <button
@@ -640,7 +629,7 @@
640
629
  </div>
641
630
  {:else}
642
631
  <div class="exec-btns">
643
- <span class="result-chip {statusClass(entry.status)}">{entry.status}</span>
632
+ <ResultChip status={entry.status} />
644
633
  <button
645
634
  class="exec-reset"
646
635
  on:click={() => handleMarkEntry(entry, 'pending')}
@@ -731,7 +720,7 @@
731
720
  font-size: 0.7rem;
732
721
  font-weight: 500;
733
722
  text-transform: capitalize;
734
- border-radius: 100px;
723
+ border-radius: var(--radius-pill);
735
724
  padding: 0.2rem 0.6rem;
736
725
  border: 1px solid var(--border);
737
726
  color: var(--text-muted);
@@ -764,7 +753,7 @@
764
753
  color: var(--text-muted);
765
754
  background: var(--bg-subtle);
766
755
  border: 1px solid var(--border);
767
- border-radius: 100px;
756
+ border-radius: var(--radius-pill);
768
757
  padding: 0.1rem 0.45rem;
769
758
  margin-left: auto;
770
759
  }
@@ -830,7 +819,7 @@
830
819
  font-size: 0.7rem;
831
820
  background: var(--bg-subtle);
832
821
  border: 1px solid var(--border);
833
- border-radius: 100px;
822
+ border-radius: var(--radius-pill);
834
823
  padding: 0.05rem 0.4rem;
835
824
  color: var(--text-muted);
836
825
  }
@@ -869,7 +858,7 @@
869
858
  font-size: 0.7rem;
870
859
  background: var(--bg-elevated);
871
860
  border: 1px solid var(--border);
872
- border-radius: 100px;
861
+ border-radius: var(--radius-pill);
873
862
  padding: 0.05rem 0.4rem;
874
863
  color: var(--text-muted);
875
864
  }
@@ -891,14 +880,6 @@
891
880
  border-color: var(--accent);
892
881
  }
893
882
 
894
- .drop-hint {
895
- padding: 2rem 1.25rem;
896
- font-size: 0.8125rem;
897
- color: var(--text-muted);
898
- text-align: center;
899
- line-height: 1.6;
900
- }
901
-
902
883
  .entry-sort-bar {
903
884
  display: flex;
904
885
  align-items: center;
@@ -911,7 +892,7 @@
911
892
  .entry-sort-chip {
912
893
  font-size: 0.75rem;
913
894
  padding: 0.2rem 0.55rem;
914
- border-radius: 100px;
895
+ border-radius: var(--radius-pill);
915
896
  border: 1px solid var(--border);
916
897
  background: var(--bg);
917
898
  color: var(--text-muted);
@@ -995,33 +976,6 @@
995
976
  color: var(--text-muted);
996
977
  }
997
978
 
998
- .assignee-auto-label {
999
- font-size: 0.72rem;
1000
- font-weight: 500;
1001
- color: var(--accent);
1002
- background: var(--accent-soft);
1003
- border-radius: 100px;
1004
- padding: 0.15rem 0.5rem;
1005
- flex-shrink: 0;
1006
- }
1007
-
1008
- .auto-pill {
1009
- font-size: 0.62rem;
1010
- font-weight: 600;
1011
- text-transform: uppercase;
1012
- letter-spacing: 0.05em;
1013
- color: var(--accent);
1014
- background: var(--accent-soft);
1015
- border-radius: 100px;
1016
- padding: 0.1rem 0.4rem;
1017
- flex-shrink: 0;
1018
- }
1019
-
1020
- .auto-label {
1021
- color: var(--accent);
1022
- font-weight: 500;
1023
- }
1024
-
1025
979
  .exec-locked-banner {
1026
980
  font-size: 0.8125rem;
1027
981
  color: var(--text-muted);
@@ -1165,9 +1119,6 @@
1165
1119
  }
1166
1120
 
1167
1121
  /* ── Execute ── */
1168
- .execute-workspace {
1169
- }
1170
-
1171
1122
  .progress-bar-wrap {
1172
1123
  margin-bottom: 1.25rem;
1173
1124
  }
@@ -1197,14 +1148,14 @@
1197
1148
  .progress-bar {
1198
1149
  height: 6px;
1199
1150
  background: var(--bg-subtle);
1200
- border-radius: 100px;
1151
+ border-radius: var(--radius-pill);
1201
1152
  overflow: hidden;
1202
1153
  display: flex;
1203
1154
  }
1204
1155
 
1205
1156
  .progress-fill {
1206
1157
  height: 100%;
1207
- border-radius: 100px;
1158
+ border-radius: var(--radius-pill);
1208
1159
  transition: width 0.3s var(--ease-out);
1209
1160
  }
1210
1161
 
@@ -1365,7 +1316,7 @@
1365
1316
  color: var(--accent);
1366
1317
  background: none;
1367
1318
  border: 1px solid var(--accent);
1368
- border-radius: 100px;
1319
+ border-radius: var(--radius-pill);
1369
1320
  padding: 0.1rem 0.5rem;
1370
1321
  cursor: pointer;
1371
1322
  transition: background var(--duration-fast);
@@ -1380,36 +1331,6 @@
1380
1331
  cursor: not-allowed;
1381
1332
  }
1382
1333
 
1383
- .result-chip {
1384
- font-size: 0.7rem;
1385
- font-weight: 600;
1386
- text-transform: uppercase;
1387
- letter-spacing: 0.05em;
1388
- padding: 0.2rem 0.6rem;
1389
- border-radius: 100px;
1390
- }
1391
-
1392
- .result-chip.pass {
1393
- background: var(--pass-soft);
1394
- color: var(--pass);
1395
- }
1396
- .result-chip.fail {
1397
- background: var(--fail-soft);
1398
- color: var(--fail);
1399
- }
1400
- .result-chip.warn {
1401
- background: var(--warn-soft);
1402
- color: var(--warn);
1403
- }
1404
- .result-chip.muted {
1405
- background: var(--bg-subtle);
1406
- color: var(--text-muted);
1407
- }
1408
- .result-chip.pending {
1409
- background: var(--bg-subtle);
1410
- color: var(--text-muted);
1411
- }
1412
-
1413
1334
  .exec-chevron {
1414
1335
  background: none;
1415
1336
  border: none;
@@ -1469,51 +1390,6 @@
1469
1390
  margin-top: 0.2rem;
1470
1391
  }
1471
1392
 
1472
- /* ── Shared chips ── */
1473
- .id-chip {
1474
- font-family: 'JetBrains Mono', monospace;
1475
- font-weight: 600;
1476
- color: var(--accent);
1477
- background: var(--accent-soft);
1478
- border-radius: 4px;
1479
- letter-spacing: 0.02em;
1480
- font-size: 0.7rem;
1481
- padding: 0.1rem 0.4rem;
1482
- flex-shrink: 0;
1483
- }
1484
-
1485
- .id-chip.small {
1486
- font-size: 0.63rem;
1487
- }
1488
-
1489
- .priority-badge {
1490
- font-size: 0.65rem;
1491
- font-weight: 500;
1492
- border-radius: 100px;
1493
- padding: 0.12rem 0.45rem;
1494
- flex-shrink: 0;
1495
- }
1496
-
1497
- .priority-badge.small {
1498
- font-size: 0.6rem;
1499
- }
1500
- .priority-badge.critical {
1501
- background: var(--fail-soft);
1502
- color: var(--fail);
1503
- }
1504
- .priority-badge.high {
1505
- background: var(--warn-soft);
1506
- color: var(--warn);
1507
- }
1508
- .priority-badge.medium {
1509
- background: var(--node-soft);
1510
- color: var(--node);
1511
- }
1512
- .priority-badge.low {
1513
- background: var(--bg-subtle);
1514
- color: var(--text-muted);
1515
- }
1516
-
1517
1393
  .icon-btn {
1518
1394
  display: flex;
1519
1395
  align-items: center;
@@ -980,7 +980,7 @@
980
980
  color: var(--text-muted);
981
981
  background: var(--bg);
982
982
  border: 1px solid var(--border);
983
- border-radius: 100px;
983
+ border-radius: var(--radius-pill);
984
984
  cursor: pointer;
985
985
  transition:
986
986
  color var(--duration-fast),
@@ -1321,7 +1321,7 @@
1321
1321
  text-transform: uppercase;
1322
1322
  letter-spacing: 0.05em;
1323
1323
  padding: 0.15rem 0.5rem;
1324
- border-radius: 100px;
1324
+ border-radius: var(--radius-pill);
1325
1325
  align-self: flex-start;
1326
1326
  }
1327
1327
 
@@ -1364,7 +1364,7 @@
1364
1364
  .priority-badge {
1365
1365
  font-size: 0.68rem;
1366
1366
  font-weight: 500;
1367
- border-radius: 100px;
1367
+ border-radius: var(--radius-pill);
1368
1368
  padding: 0.15rem 0.5rem;
1369
1369
  }
1370
1370
 
@@ -1393,7 +1393,7 @@
1393
1393
  font-weight: 500;
1394
1394
  background: var(--pass-soft);
1395
1395
  color: var(--pass);
1396
- border-radius: 100px;
1396
+ border-radius: var(--radius-pill);
1397
1397
  padding: 0.15rem 0.5rem;
1398
1398
  }
1399
1399
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plum-e2e",
3
- "version": "2.4.13",
3
+ "version": "2.5.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/silverlunah/plum.git"