plum-e2e 1.0.10 → 1.1.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 (51) hide show
  1. package/.claude/settings.local.json +16 -1
  2. package/.vscode/settings.json +10 -0
  3. package/README.md +151 -37
  4. package/backend/_scaffold/features/LoginPage.feature +45 -3
  5. package/backend/_scaffold/pages/HomepagePage.ts +7 -0
  6. package/backend/_scaffold/pages/LoginPage.ts +37 -13
  7. package/backend/_scaffold/step_definitions/HomepageSteps.ts +6 -0
  8. package/backend/_scaffold/step_definitions/LoginSteps.ts +30 -4
  9. package/backend/_scaffold/utils/browser.ts +33 -0
  10. package/backend/_scaffold/utils/hooks.ts +8 -29
  11. package/backend/_scaffold/utils/utils.ts +3 -9
  12. package/backend/config/scripts/create-settings.js +7 -14
  13. package/backend/config/scripts/create-step.mjs +268 -0
  14. package/backend/config/scripts/generate-report.js +31 -75
  15. package/backend/config/scripts/run-tests.js +19 -4
  16. package/backend/package-lock.json +56 -641
  17. package/backend/package.json +4 -1
  18. package/backend/routes/reports.routes.js +6 -10
  19. package/backend/services/envService.js +4 -10
  20. package/backend/services/reportService.js +70 -20
  21. package/backend/services/testService.js +99 -24
  22. package/backend/tsconfig.json +2 -2
  23. package/backend/websockets/socketHandler.js +12 -6
  24. package/bin/plum.js +49 -3
  25. package/frontend/package-lock.json +436 -135
  26. package/frontend/package.json +1 -1
  27. package/frontend/src/app.css +241 -6
  28. package/frontend/src/app.html +14 -1
  29. package/frontend/src/lib/api/reports.js +68 -0
  30. package/frontend/src/lib/api/schedules.js +64 -0
  31. package/frontend/src/lib/api/tests.js +41 -0
  32. package/frontend/src/lib/components/layout/Nav.svelte +304 -0
  33. package/frontend/src/lib/components/layout/PageShell.svelte +28 -0
  34. package/frontend/src/lib/components/layout/RunnerPanel.svelte +378 -0
  35. package/frontend/src/lib/components/ui/Badge.svelte +63 -0
  36. package/frontend/src/lib/components/ui/Button.svelte +117 -0
  37. package/frontend/src/lib/components/ui/Modal.svelte +140 -0
  38. package/frontend/src/lib/components/ui/Pagination.svelte +100 -0
  39. package/frontend/src/lib/components/ui/Terminal.svelte +100 -0
  40. package/frontend/src/lib/stores/runner.js +55 -0
  41. package/frontend/src/lib/stores/theme.js +47 -0
  42. package/frontend/src/routes/+layout.svelte +7 -12
  43. package/frontend/src/routes/+page.svelte +690 -142
  44. package/frontend/src/routes/reports/+page.svelte +395 -125
  45. package/frontend/src/routes/reports/[slug]/+page.svelte +749 -0
  46. package/frontend/src/routes/scheduled-tests/+page.svelte +267 -303
  47. package/frontend/svelte.config.js +1 -4
  48. package/frontend/tailwind.config.js +2 -23
  49. package/package.json +2 -2
  50. package/backend/_scaffold/utils/world.ts +0 -25
  51. package/frontend/src/routes/components/Navigation.svelte +0 -53
@@ -17,347 +17,311 @@
17
17
 
18
18
  <script>
19
19
  import { onMount } from 'svelte';
20
+ import { fly } from 'svelte/transition';
21
+ import { fetchSchedules, fetchCronJobs, saveCronJob, deleteCronJob } from '$lib/api/schedules';
22
+ import Button from '$lib/components/ui/Button.svelte';
23
+ import Badge from '$lib/components/ui/Badge.svelte';
24
+ import Modal from '$lib/components/ui/Modal.svelte';
20
25
 
21
- let scheduledTests = [];
22
- let schedules = [];
23
- let cronExpression = '';
24
- let taskName = '';
25
- let tags = '';
26
- let successMessage = '';
27
- let errorMessage = '';
28
- let isModalOpen = false;
29
- let isEditing = false; // To track if we are editing a cron job
30
- let editTaskName = ''; // To store the task name of the cron job being edited
31
- let isDeleting = false; // To track if the user is confirming deletion
32
- let taskToDelete = ''; // The task name of the cron job being deleted
33
-
34
- const cronRegex =
26
+ const CRON_REGEX =
35
27
  /^(\*|([0-5]?[0-9])) (\*|([01]?[0-9]|2[0-3])) (\*|([01]?[0-9]|3[01])) (\*|([1-9]|1[0-2])) (\*|[0-6])$/;
36
28
 
37
- // Fetch schedules from the settings.json through backend API
38
- async function fetchSchedules() {
39
- try {
40
- const res = await fetch('http://localhost:3001/schedules');
41
- const data = await res.json();
29
+ let cronJobs = [];
30
+ let scheduleOptions = [];
31
+ let toast = null; // { type: 'success' | 'error', message }
42
32
 
43
- if (data.schedules) {
44
- schedules = data.schedules;
45
- }
46
- } catch (error) {
47
- errorMessage = 'Error fetching schedules.';
48
- console.error('Error fetching schedules', error);
49
- }
33
+ let modalOpen = false;
34
+ let deleteModalOpen = false;
35
+ let isEditing = false;
36
+ let editTaskName = '';
37
+ let taskToDelete = '';
38
+
39
+ let form = { taskName: '', cronExpression: '', tags: '' };
40
+ let formError = '';
41
+
42
+ function showToast(type, message) {
43
+ toast = { type, message };
44
+ setTimeout(() => (toast = null), 4000);
50
45
  }
51
46
 
52
- // Fetch scheduled tests (cron jobs) from the backend API
53
- async function fetchScheduledTests() {
54
- try {
55
- const res = await fetch('http://localhost:3001/cron-jobs');
56
- const data = await res.json();
57
- if (data.cronJobs) {
58
- scheduledTests = data.cronJobs;
59
- }
60
- } catch (error) {
61
- errorMessage = 'Error fetching scheduled tests.';
62
- console.error('Error fetching scheduled tests:', error);
63
- }
47
+ function openAddModal() {
48
+ isEditing = false;
49
+ editTaskName = '';
50
+ form = { taskName: '', cronExpression: '', tags: '' };
51
+ formError = '';
52
+ modalOpen = true;
53
+ }
54
+
55
+ function openEditModal(job) {
56
+ isEditing = true;
57
+ editTaskName = job.taskName;
58
+ form = { taskName: job.taskName, cronExpression: job.cronExpression, tags: job.tags };
59
+ formError = '';
60
+ modalOpen = true;
61
+ }
62
+
63
+ function openDeleteModal(taskName) {
64
+ taskToDelete = taskName;
65
+ deleteModalOpen = true;
64
66
  }
65
67
 
66
- // Add or edit a cron job via the API
67
- async function saveCronJob() {
68
- if (!cronExpression || !taskName || !tags) {
69
- errorMessage = 'Please fill in all fields';
68
+ async function handleSave() {
69
+ if (!form.taskName || !form.cronExpression || !form.tags) {
70
+ formError = 'All fields are required.';
70
71
  return;
71
72
  }
72
-
73
- // Validate cron expression
74
- if (!cronRegex.test(cronExpression)) {
75
- errorMessage = 'Invalid cron expression. Please use a valid cron format.';
73
+ if (!CRON_REGEX.test(form.cronExpression)) {
74
+ formError = 'Invalid cron expression.';
76
75
  return;
77
76
  }
78
-
79
- // Lowercase " OR " while keeping other tags intact
80
- const formattedTags = tags.replace(/\sOR\s/gi, (match) => match.toLowerCase());
81
-
77
+ formError = '';
82
78
  try {
83
- let res;
84
- if (isEditing) {
85
- // Edit cron job
86
- res = await fetch(`http://localhost:3001/cron-jobs/${editTaskName}`, {
87
- method: 'PUT',
88
- headers: {
89
- 'Content-Type': 'application/json'
90
- },
91
- body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
92
- });
93
- } else {
94
- // Add new cron job
95
- res = await fetch('http://localhost:3001/cron-jobs', {
96
- method: 'POST',
97
- headers: {
98
- 'Content-Type': 'application/json'
99
- },
100
- body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
101
- });
102
- }
103
-
104
- const data = await res.json();
79
+ const data = await saveCronJob({ ...form, isEditing, editTaskName });
105
80
  if (data.message) {
106
- successMessage = `Cron job ${isEditing ? 'updated' : 'added'}: ${data.message}`;
107
- fetchScheduledTests(); // Refresh the list
108
- setTimeout(() => {
109
- successMessage = ''; // Clear success message after 5 seconds
110
- }, 5000);
111
- clearModal();
81
+ showToast('success', `${isEditing ? 'Updated' : 'Added'}: ${form.taskName}`);
82
+ modalOpen = false;
83
+ cronJobs = await fetchCronJobs();
112
84
  } else {
113
- errorMessage = 'Failed to save cron job';
85
+ formError = 'Failed to save. Please try again.';
114
86
  }
115
- } catch (error) {
116
- errorMessage = 'Error saving cron job.';
117
- console.error('Error saving cron job:', error);
87
+ } catch {
88
+ formError = 'Network error.';
118
89
  }
119
90
  }
120
91
 
121
- // Delete a cron job
122
- async function deleteCronJob(taskName) {
92
+ async function handleDelete() {
123
93
  try {
124
- const res = await fetch(`http://localhost:3001/cron-jobs/${taskName}`, {
125
- method: 'DELETE'
126
- });
127
- const data = await res.json();
94
+ const data = await deleteCronJob(taskToDelete);
128
95
  if (data.message) {
129
- successMessage = `Cron job deleted: ${data.message}`;
130
- // Refresh the list
131
- fetchScheduledTests();
132
- setTimeout(() => {
133
- successMessage = '';
134
- }, 5000);
96
+ showToast('success', `Deleted: ${taskToDelete}`);
97
+ cronJobs = await fetchCronJobs();
135
98
  } else {
136
- errorMessage = 'Failed to delete cron job';
137
- setTimeout(() => {
138
- errorMessage = '';
139
- }, 5000);
99
+ showToast('error', 'Could not delete cron job.');
140
100
  }
141
- } catch (error) {
142
- errorMessage = 'Error deleting cron job.';
143
- console.error('Error deleting cron job:', error);
101
+ } catch {
102
+ showToast('error', 'Network error.');
144
103
  }
145
- }
146
-
147
- // Open modal to edit cron job
148
- function openEditModal(cronJob) {
149
- isEditing = true;
150
- editTaskName = cronJob.taskName;
151
- taskName = cronJob.taskName;
152
- cronExpression = cronJob.cronExpression;
153
- tags = cronJob.tags;
154
- isModalOpen = true;
155
- }
156
-
157
- // Reset modal values
158
- function clearModal() {
159
- isEditing = false;
160
- editTaskName = '';
161
- taskName = '';
162
- cronExpression = '';
163
- tags = '';
164
- isModalOpen = false;
165
- }
166
-
167
- // Open confirmation modal for deleting cron job
168
- function openDeleteConfirmation(taskName) {
169
- isDeleting = true;
170
- taskToDelete = taskName;
171
- }
172
-
173
- // Confirm deletion
174
- function confirmDelete() {
175
- deleteCronJob(taskToDelete);
176
- isDeleting = false;
104
+ deleteModalOpen = false;
177
105
  taskToDelete = '';
178
106
  }
179
107
 
180
- // Cancel deletion
181
- function cancelDelete() {
182
- isDeleting = false;
183
- taskToDelete = '';
108
+ function cronLabel(expression) {
109
+ return scheduleOptions.find((s) => s.value === expression)?.label ?? expression;
184
110
  }
185
111
 
186
- // Get human-readable label for cron expression
187
- function getCronLabel(cronExpression) {
188
- const match = schedules.find((option) => option.value === cronExpression);
189
- return match ? match.label : cronExpression;
190
- }
191
-
192
- onMount(() => {
193
- fetchScheduledTests();
194
- fetchSchedules(); // <-- also call this here
112
+ onMount(async () => {
113
+ [cronJobs, scheduleOptions] = await Promise.all([fetchCronJobs(), fetchSchedules()]);
195
114
  });
196
115
  </script>
197
116
 
198
- <!-- Add/Modify modal -->
199
- <dialog id="my_modal_1" class="modal" class:modal-open={isModalOpen}>
200
- <div class="modal-box">
201
- <div class="modal-header">
202
- <button class="btn btn-sm btn-circle z-10 absolute right-2 top-2" on:click={clearModal}>
203
-
204
- </button>
117
+ <!-- Add/Edit modal -->
118
+ <Modal bind:open={modalOpen} title={isEditing ? 'Edit Cron Job' : 'New Cron Job'}>
119
+ <form on:submit|preventDefault={handleSave} class="modal-form">
120
+ <div class="field">
121
+ <div class="field-label">
122
+ <span>Task Name</span>
123
+ <span class="field-hint">
124
+ {isEditing ? 'Name is the ID — cannot be changed' : 'Use a unique, meaningful name'}
125
+ </span>
126
+ </div>
127
+ <input
128
+ type="text"
129
+ class="field-input"
130
+ bind:value={form.taskName}
131
+ placeholder="nightly-login-suite"
132
+ disabled={isEditing}
133
+ required
134
+ />
205
135
  </div>
206
136
 
207
- <h2 class="card-title mb-4">{isEditing ? 'Edit' : 'Add New'} Cron Job</h2>
208
-
209
- <form on:submit|preventDefault={saveCronJob} class="space-y-4">
210
- <div>
211
- <div for="taskName" class="label">
212
- <span class="label-text">Task Name</span>
213
- <span class="label-text-alt"
214
- >{isEditing
215
- ? 'Name is used as ID. This cannot be changed.'
216
- : 'Use a meaningful name'}</span
217
- >
218
- </div>
219
- <input
220
- type="text"
221
- id="taskName"
222
- bind:value={taskName}
223
- class="input input-bordered w-full"
224
- placeholder="Task Name"
225
- required
226
- disabled={isEditing}
227
- />
137
+ <div class="field">
138
+ <div class="field-label">
139
+ <span>Schedule</span>
228
140
  </div>
229
- <div>
230
- <div for="cronExpression" class="label">
231
- <span class="label-text">Cron Expression</span>
232
- <span class="label-text-alt">You can use AI to construct your expression.</span>
233
- </div>
234
- <select
235
- id="cronExpression"
236
- bind:value={cronExpression}
237
- class="input input-bordered w-full"
238
- required
239
- >
240
- <option value="" disabled selected>Select Cron Expression</option>
241
- {#each schedules as schedule}
242
- <option value={schedule.value}>{schedule.label}</option>
243
- {/each}
244
- </select>
245
- </div>
246
- <div>
247
- <div for="tags" class="label">
248
- <span class="label-text">Tags</span>
249
- <span class="label-text-alt">If using multiple tags, "@test-1 or @test-2".</span>
250
- </div>
251
- <input
252
- type="text"
253
- id="tags"
254
- bind:value={tags}
255
- class="input input-bordered w-full"
256
- placeholder="Tags"
257
- required
258
- />
141
+ <select class="field-input" bind:value={form.cronExpression} required>
142
+ <option value="" disabled selected>Select a schedule</option>
143
+ {#each scheduleOptions as opt}
144
+ <option value={opt.value}>{opt.label}</option>
145
+ {/each}
146
+ </select>
147
+ </div>
148
+
149
+ <div class="field">
150
+ <div class="field-label">
151
+ <span>Tags</span>
152
+ <span class="field-hint">Multiple: @test-1 or @test-2</span>
259
153
  </div>
260
- <button type="submit" class="btn btn-success w-full">
261
- {isEditing ? 'Edit' : 'Add'} Cron Job
262
- </button>
263
- </form>
264
- </div>
265
- </dialog>
266
-
267
- <!-- Delete Confirmation Modal -->
268
- <dialog id="delete_confirmation_modal" class="modal" class:modal-open={isDeleting}>
269
- <div class="modal-box">
270
- <h2 class="card-title mb-4">Are you sure you want to delete this cron job?</h2>
271
- <div class="flex justify-center gap-4">
272
- <button class="btn btn-error" on:click={confirmDelete}>Yes</button>
273
- <button class="btn" on:click={cancelDelete}>No</button>
154
+ <input
155
+ type="text"
156
+ class="field-input"
157
+ bind:value={form.tags}
158
+ placeholder="@suite-login"
159
+ required
160
+ />
274
161
  </div>
162
+
163
+ {#if formError}
164
+ <p class="form-error">{formError}</p>
165
+ {/if}
166
+
167
+ <Button type="submit" size="md">
168
+ {isEditing ? 'Save Changes' : 'Add Cron Job'}
169
+ </Button>
170
+ </form>
171
+ </Modal>
172
+
173
+ <!-- Delete confirmation modal -->
174
+ <Modal bind:open={deleteModalOpen} title="Delete Cron Job">
175
+ <p class="confirm-text">
176
+ Are you sure you want to delete <strong>{taskToDelete}</strong>? This cannot be undone.
177
+ </p>
178
+ <div class="confirm-actions">
179
+ <Button variant="danger" on:click={handleDelete}>Delete</Button>
180
+ <Button variant="ghost" on:click={() => (deleteModalOpen = false)}>Cancel</Button>
275
181
  </div>
276
- </dialog>
277
-
278
- <div class="flex justify-center items-center w-full my-4">
279
- <div class="card bg-base-300 rounded-box p-4">
280
- <div class="card-body text-left">
281
- <h2 class="card-title sticky top-0 bg-base-300 z-10">Scheduled Tests</h2>
282
- {#if successMessage}
283
- <div role="alert" class="alert alert-success z-0">
284
- <svg
285
- xmlns="http://www.w3.org/2000/svg"
286
- class="h-6 w-6 shrink-0 stroke-current"
287
- fill="none"
288
- viewBox="0 0 24 24"
289
- >
290
- <path
291
- stroke-linecap="round"
292
- stroke-linejoin="round"
293
- stroke-width="2"
294
- d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
295
- />
296
- </svg>
297
- <span>{successMessage}</span>
298
- </div>
299
- {/if}
300
-
301
- {#if errorMessage}
302
- <div role="alert" class="alert alert-error">
303
- <svg
304
- xmlns="http://www.w3.org/2000/svg"
305
- class="h-6 w-6 shrink-0 stroke-current"
306
- fill="none"
307
- viewBox="0 0 24 24"
308
- >
309
- <path
310
- stroke-linecap="round"
311
- stroke-linejoin="round"
312
- stroke-width="2"
313
- d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
314
- />
315
- </svg>
316
- <span>{errorMessage}</span>
317
- </div>
318
- {/if}
319
- <div class="mt-4">
320
- {#if scheduledTests.length > 0}
321
- <div class="overflow-x-auto">
322
- <table class="table table-zebra">
323
- <thead>
324
- <tr>
325
- <th>Name</th>
326
- <th>Frequency</th>
327
- <th>Tags</th>
328
- <th>Action</th>
329
- </tr>
330
- </thead>
331
- <tbody>
332
- {#each scheduledTests as scheduledTest}
333
- <tr>
334
- <td>{scheduledTest.taskName}</td>
335
- <td>{getCronLabel(scheduledTest.cronExpression)}</td>
336
- <td>{scheduledTest.tags}</td>
337
- <td>
338
- <button
339
- class="btn btn-warning btn-sm"
340
- on:click={() => openEditModal(scheduledTest)}
341
- >
342
- Edit
343
- </button>
344
- <button
345
- class="btn btn-error btn-sm"
346
- on:click={() => openDeleteConfirmation(scheduledTest.taskName)}
347
- >
348
- Delete
349
- </button>
350
- </td>
351
- </tr>
352
- {/each}
353
- </tbody>
354
- </table>
355
- </div>
356
- {:else}
357
- <p>No Scheduled Tests Available.</p>
358
- {/if}
359
- </div>
360
- <button class="btn mt-2" on:click={() => (isModalOpen = true)}>Add New Cron Job</button>
182
+ </Modal>
183
+
184
+ <!-- Page -->
185
+ <div class="page-header">
186
+ <div class="header-row">
187
+ <div>
188
+ <h1>Scheduled Tests</h1>
189
+ <p class="subtitle">Manage recurring test runs via cron jobs</p>
361
190
  </div>
191
+ <Button on:click={openAddModal}>+ New Job</Button>
362
192
  </div>
363
193
  </div>
194
+
195
+ {#if toast}
196
+ <div class="toast alert alert-{toast.type}" transition:fly={{ y: -8, duration: 240 }}>
197
+ {toast.message}
198
+ </div>
199
+ {/if}
200
+
201
+ <div class="card" style="padding: 0; overflow: hidden;">
202
+ {#if cronJobs.length === 0}
203
+ <p class="empty">No scheduled tests yet. Create one to get started.</p>
204
+ {:else}
205
+ <div class="table-wrap">
206
+ <table class="data-table">
207
+ <thead>
208
+ <tr>
209
+ <th>Name</th>
210
+ <th>Schedule</th>
211
+ <th>Tags</th>
212
+ <th>Actions</th>
213
+ </tr>
214
+ </thead>
215
+ <tbody>
216
+ {#each cronJobs as job, i}
217
+ <tr style="animation-delay: {i * 45}ms" class="job-row">
218
+ <td class="job-name">{job.taskName}</td>
219
+ <td>
220
+ <Badge variant="schedule">{cronLabel(job.cronExpression)}</Badge>
221
+ </td>
222
+ <td>
223
+ <span class="tag-text">{job.tags}</span>
224
+ </td>
225
+ <td class="actions-cell">
226
+ <Button variant="ghost" size="sm" on:click={() => openEditModal(job)}>Edit</Button>
227
+ <Button variant="danger" size="sm" on:click={() => openDeleteModal(job.taskName)}
228
+ >Delete</Button
229
+ >
230
+ </td>
231
+ </tr>
232
+ {/each}
233
+ </tbody>
234
+ </table>
235
+ </div>
236
+ {/if}
237
+ </div>
238
+
239
+ <style>
240
+ .page-header {
241
+ margin-bottom: 2rem;
242
+ padding-bottom: 1.5rem;
243
+ border-bottom: 1px solid var(--border);
244
+ }
245
+
246
+ .header-row {
247
+ display: flex;
248
+ align-items: flex-start;
249
+ justify-content: space-between;
250
+ gap: 1rem;
251
+ }
252
+
253
+ .page-header h1 {
254
+ font-size: 2.5rem;
255
+ margin-bottom: 0.375rem;
256
+ }
257
+
258
+ .subtitle {
259
+ color: var(--text-muted);
260
+ font-size: 0.9375rem;
261
+ }
262
+
263
+ .job-row {
264
+ animation: fadeUp 0.32s var(--ease-out) both;
265
+ }
266
+
267
+ .toast {
268
+ margin-bottom: 1.25rem;
269
+ border-radius: var(--radius-md);
270
+ }
271
+
272
+ .table-wrap {
273
+ overflow-x: auto;
274
+ }
275
+
276
+ .job-name {
277
+ font-weight: 400;
278
+ font-size: 0.875rem;
279
+ }
280
+
281
+ .tag-text {
282
+ font-family: 'JetBrains Mono', monospace;
283
+ font-size: 0.78rem;
284
+ color: var(--text-muted);
285
+ }
286
+
287
+ .actions-cell {
288
+ display: flex;
289
+ gap: 0.375rem;
290
+ }
291
+
292
+ .empty {
293
+ padding: 3rem 1.5rem;
294
+ color: var(--text-muted);
295
+ font-size: 0.9375rem;
296
+ text-align: center;
297
+ }
298
+
299
+ /* Modal form */
300
+ .modal-form {
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: 1rem;
304
+ }
305
+
306
+ .form-error {
307
+ font-size: 0.8125rem;
308
+ color: var(--fail);
309
+ }
310
+
311
+ .confirm-text {
312
+ font-size: 0.9375rem;
313
+ color: var(--text-muted);
314
+ line-height: 1.6;
315
+ }
316
+
317
+ .confirm-text strong {
318
+ color: var(--text);
319
+ font-weight: 500;
320
+ }
321
+
322
+ .confirm-actions {
323
+ display: flex;
324
+ gap: 0.625rem;
325
+ padding-top: 0.25rem;
326
+ }
327
+ </style>
@@ -15,14 +15,11 @@
15
15
  * along with Plum. If not, see https://www.gnu.org/licenses/.
16
16
  */
17
17
 
18
- import adapter from '@sveltejs/adapter-auto';
18
+ import adapter from '@sveltejs/adapter-node';
19
19
 
20
20
  /** @type {import('@sveltejs/kit').Config} */
21
21
  const config = {
22
22
  kit: {
23
- // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
24
- // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
25
- // See https://svelte.dev/docs/kit/adapters for more information about adapters.
26
23
  adapter: adapter()
27
24
  }
28
25
  };
@@ -18,27 +18,6 @@
18
18
  /** @type {import('tailwindcss').Config} */
19
19
  export default {
20
20
  content: ['./src/**/*.{html,js,svelte,ts}'],
21
- theme: {
22
- extend: {
23
- animation: {
24
- 'rise-quick': 'rise 0.6s ease-out' // Quick rising animation
25
- },
26
- keyframes: {
27
- rise: {
28
- '0%': { transform: 'translateY(20px)', opacity: '0' }, // Start slightly below, invisible
29
- '100%': { transform: 'translateY(0)', opacity: '1' } // End in place, fully visible
30
- }
31
- }
32
- }
33
- },
34
- plugins: [require('daisyui')],
35
- daisyui: {
36
- themes: ['emerald', 'dark'], // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
37
- base: true, // applies background color and foreground color for root element by default
38
- styled: true, // include daisyUI colors and design decisions for all components
39
- utils: true, // adds responsive and modifier utility classes
40
- prefix: '', // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)
41
- logs: true, // Shows info about daisyUI version and used config in the console when building your CSS
42
- themeRoot: ':root' // The element that receives theme color CSS variables
43
- }
21
+ theme: { extend: {} },
22
+ plugins: []
44
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plum-e2e",
3
- "version": "1.0.10",
3
+ "version": "1.1.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/silverlunah/plum.git"
@@ -8,7 +8,7 @@
8
8
  "description": "A detached test automation environment that combines Playwright and Cucumber with a Svelte frontend and an Express backend. It allows users to trigger tests, monitor reports, and schedule test runs through an intuitive UI.",
9
9
  "main": "index.js",
10
10
  "scripts": {
11
- "init": "(npm install) && (cd backend && npm install && npm run create-env && node config/scripts/create-settings.js) && (cd frontend && npm install && echo 'Frontend install complete')",
11
+ "init": "(npm install) && (cd backend && npm install && npm run init) && (cd frontend && npm install && echo 'Frontend install complete')",
12
12
  "format": "prettier --write .",
13
13
  "add-license": "npx license-check-and-add add -f license-config.json",
14
14
  "prepare": "husky",