plum-e2e 1.3.5 → 1.3.6

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.
@@ -27,7 +27,9 @@ const runners = parallel || process.env.REPORT_RUNNERS || '1';
27
27
  const tag = process.env.TAG || process.argv.slice(2).find((a) => a.startsWith('@'));
28
28
  const browser = process.env.BROWSER || 'chromium';
29
29
 
30
- const reportFile = path.resolve(process.cwd(), 'reports', 'cucumber_report.json');
30
+ const reportFile =
31
+ process.env.CUCUMBER_REPORT_FILE ||
32
+ path.resolve(process.cwd(), 'reports', 'cucumber_report.json');
31
33
 
32
34
  // Wipe any previous report so a crashed/empty run can never return stale results.
33
35
  try {
@@ -87,7 +89,7 @@ try {
87
89
  );
88
90
  }
89
91
 
90
- baseCommand.push('--format', 'json:reports/cucumber_report.json');
92
+ baseCommand.push('--format', `json:${reportFile.replace(/\\/g, '/')}`);
91
93
 
92
94
  if (tag) {
93
95
  baseCommand.push('--tags', `"${tag}"`);
@@ -50,13 +50,18 @@ router.post('/execute', authGuard, (req, res) => {
50
50
  }
51
51
  }
52
52
 
53
+ // Each job writes to its own temp file so concurrent jobs on the same node
54
+ // cannot clobber each other's reports (shared cucumber_report.json race condition).
55
+ const reportFile = path.join(os.tmpdir(), `plum-report-${jobId}.json`);
56
+
53
57
  jobs[jobId] = {
54
58
  status: 'running',
55
59
  logs: '',
56
60
  exitCode: null,
57
61
  startedAt: Date.now(),
58
62
  meta: { tags: tags || '', browser, workers },
59
- tempTestsDir
63
+ tempTestsDir,
64
+ reportFile
60
65
  };
61
66
 
62
67
  const env = {
@@ -65,6 +70,7 @@ router.post('/execute', authGuard, (req, res) => {
65
70
  TRIGGER: TRIGGER_REMOTE,
66
71
  BROWSER: browser,
67
72
  REPORT_RUNNERS: String(workers),
73
+ CUCUMBER_REPORT_FILE: reportFile,
68
74
  ...(tempTestsDir ? { TESTS_ROOT: tempTestsDir } : {})
69
75
  };
70
76
  if (workers > 1) env.PARALLEL = String(workers);
@@ -81,9 +87,8 @@ router.post('/execute', authGuard, (req, res) => {
81
87
  jobs[jobId].exitCode = code;
82
88
 
83
89
  try {
84
- const reportPath = path.resolve(process.cwd(), 'reports', 'cucumber_report.json');
85
- if (fs.existsSync(reportPath)) {
86
- jobs[jobId].reportContent = fs.readFileSync(reportPath, 'utf8');
90
+ if (fs.existsSync(reportFile)) {
91
+ jobs[jobId].reportContent = fs.readFileSync(reportFile, 'utf8');
87
92
  }
88
93
  } catch {}
89
94
 
@@ -91,7 +96,12 @@ router.post('/execute', authGuard, (req, res) => {
91
96
  fs.rm(jobs[jobId].tempTestsDir, { recursive: true, force: true }, () => {});
92
97
  }
93
98
 
94
- setTimeout(() => delete jobs[jobId], 600_000);
99
+ setTimeout(() => {
100
+ try {
101
+ fs.unlinkSync(reportFile);
102
+ } catch {}
103
+ delete jobs[jobId];
104
+ }, 600_000);
95
105
  });
96
106
 
97
107
  res.json({ jobId, status: 'started' });
package/bin/plum.js CHANGED
@@ -323,7 +323,7 @@ async function configureNode({ force }) {
323
323
 
324
324
  if (interactive) {
325
325
  const primaryVal = await clack.text({
326
- message: 'Primary server URL',
326
+ message: 'Your Plum server backend URL',
327
327
  placeholder: primary || 'http://localhost:3001',
328
328
  defaultValue: primary
329
329
  });
@@ -331,7 +331,7 @@ async function configureNode({ force }) {
331
331
  primary = primaryVal || primary;
332
332
 
333
333
  const portVal = await clack.text({
334
- message: 'Local port this node listens on',
334
+ message: 'Local port this Plum node listens on',
335
335
  placeholder: port,
336
336
  defaultValue: port
337
337
  });
@@ -340,7 +340,7 @@ async function configureNode({ force }) {
340
340
 
341
341
  const defaultUrl = url || `http://${detectLanIp()}:${port}`;
342
342
  const urlVal = await clack.text({
343
- message: 'URL the primary calls back (advertised)',
343
+ message: 'The URL your Plum server calls to communicate with this node',
344
344
  placeholder: defaultUrl,
345
345
  defaultValue: defaultUrl
346
346
  });
@@ -234,7 +234,7 @@
234
234
  function adjustWorkers(delta) {
235
235
  runnerConfig.update((c) => ({
236
236
  ...c,
237
- workers: Math.max(1, Math.min(16, c.workers + delta))
237
+ workers: Math.max(1, Math.min(10, c.workers + delta))
238
238
  }));
239
239
  }
240
240
 
@@ -353,7 +353,7 @@
353
353
  <button
354
354
  class="step-btn"
355
355
  on:click={() => adjustWorkers(1)}
356
- disabled={cfg.workers >= 16 || state.running}>+</button
356
+ disabled={cfg.workers >= 10 || state.running}>+</button
357
357
  >
358
358
  </div>
359
359
  </div>
@@ -27,8 +27,6 @@ export const TRIGGER_TYPES = Object.freeze({
27
27
  CLI: 'command-line-trigger'
28
28
  });
29
29
 
30
- export const WORKER_OPTIONS = [1, 2, 4, 8];
31
-
32
30
  export const REPORTS_PER_PAGE = 15;
33
31
 
34
32
  export const COPY_TIMEOUT_MS = 1400;
@@ -26,7 +26,7 @@
26
26
  } from '$lib/api/schedules';
27
27
  import { fetchRunners } from '$lib/api/runners';
28
28
  import { activeCronJobs } from '$lib/stores/runner';
29
- import { BROWSERS, WORKER_OPTIONS, TOAST_TIMEOUT_MS } from '$lib/constants';
29
+ import { BROWSERS, TOAST_TIMEOUT_MS } from '$lib/constants';
30
30
  import { stagger } from '$lib/utils/format';
31
31
  import Button from '$lib/components/ui/Button.svelte';
32
32
  import Badge from '$lib/components/ui/Badge.svelte';
@@ -124,6 +124,10 @@
124
124
  form.runnerIds = current.includes(id) ? current.filter((r) => r !== id) : [...current, id];
125
125
  }
126
126
 
127
+ function adjustFormWorkers(delta) {
128
+ form.workers = Math.max(1, Math.min(10, (form.workers || 1) + delta));
129
+ }
130
+
127
131
  function openAddModal() {
128
132
  isEditing = false;
129
133
  editTaskName = '';
@@ -303,18 +307,20 @@
303
307
  <span>Workers</span>
304
308
  <span class="field-hint">Parallel workers for this job</span>
305
309
  </div>
306
- <div class="seg-control">
307
- {#each WORKER_OPTIONS as n}
308
- <button
309
- type="button"
310
- class="seg-btn"
311
- class:active={form.workers === n}
312
- on:click={() => (form.workers = n)}
313
- >
314
- <span class="seg-num">{n}</span>
315
- <span class="seg-label">{n === 1 ? 'worker' : 'workers'}</span>
316
- </button>
317
- {/each}
310
+ <div class="stepper">
311
+ <button
312
+ type="button"
313
+ class="step-btn"
314
+ on:click={() => adjustFormWorkers(-1)}
315
+ disabled={form.workers <= 1}>−</button
316
+ >
317
+ <span class="step-val">{form.workers}</span>
318
+ <button
319
+ type="button"
320
+ class="step-btn"
321
+ on:click={() => adjustFormWorkers(1)}
322
+ disabled={form.workers >= 10}>+</button
323
+ >
318
324
  </div>
319
325
  </div>
320
326
 
@@ -663,6 +669,57 @@
663
669
  color: var(--fail);
664
670
  }
665
671
 
672
+ /* Workers stepper */
673
+ .stepper {
674
+ display: flex;
675
+ align-items: center;
676
+ background: var(--bg-subtle);
677
+ border: 1px solid var(--border);
678
+ border-radius: var(--radius-sm);
679
+ overflow: hidden;
680
+ width: fit-content;
681
+ }
682
+
683
+ .step-btn {
684
+ width: 28px;
685
+ height: 30px;
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: center;
689
+ border: none;
690
+ background: transparent;
691
+ color: var(--text-muted);
692
+ cursor: pointer;
693
+ font-size: 0.875rem;
694
+ font-weight: 400;
695
+ line-height: 1;
696
+ transition:
697
+ color var(--duration-fast),
698
+ background var(--duration-fast);
699
+ }
700
+
701
+ .step-btn:hover:not(:disabled) {
702
+ color: var(--text);
703
+ background: var(--bg-elevated);
704
+ }
705
+
706
+ .step-btn:disabled {
707
+ opacity: 0.3;
708
+ cursor: default;
709
+ }
710
+
711
+ .step-val {
712
+ min-width: 28px;
713
+ text-align: center;
714
+ font-size: 0.85rem;
715
+ font-weight: 500;
716
+ color: var(--text);
717
+ font-family: 'JetBrains Mono', monospace;
718
+ line-height: 30px;
719
+ border-left: 1px solid var(--border);
720
+ border-right: 1px solid var(--border);
721
+ }
722
+
666
723
  /* Segmented control */
667
724
  .seg-control {
668
725
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plum-e2e",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/silverlunah/plum.git"