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.
- package/backend/config/scripts/run-tests.js +4 -2
- package/backend/routes/node.routes.js +15 -5
- package/bin/plum.js +3 -3
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +2 -2
- package/frontend/src/lib/constants.js +0 -2
- package/frontend/src/routes/scheduled-tests/+page.svelte +70 -13
- package/package.json +1 -1
|
@@ -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 =
|
|
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',
|
|
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
|
-
|
|
85
|
-
|
|
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(() =>
|
|
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: '
|
|
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
|
|
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(
|
|
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 >=
|
|
356
|
+
disabled={cfg.workers >= 10 || state.running}>+</button
|
|
357
357
|
>
|
|
358
358
|
</div>
|
|
359
359
|
</div>
|
|
@@ -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,
|
|
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="
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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;
|