plum-e2e 2.5.9 → 2.5.11
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/mcp/server.js +67 -0
- package/backend/routes/cron.routes.js +2 -2
- package/backend/services/cronService.js +2 -2
- package/backend/services/testRunService.js +19 -2
- package/frontend/.svelte-kit/ambient.d.ts +36 -32
- package/frontend/.svelte-kit/generated/client/app.js +18 -4
- package/frontend/.svelte-kit/generated/client/nodes/0.js +2 -0
- package/frontend/.svelte-kit/generated/client/nodes/10.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/11.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/12.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/3.js +1 -1
- package/frontend/.svelte-kit/generated/client/nodes/4.js +1 -1
- package/frontend/.svelte-kit/generated/client/nodes/5.js +1 -1
- package/frontend/.svelte-kit/generated/client/nodes/6.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/7.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/8.js +18 -0
- package/frontend/.svelte-kit/generated/client/nodes/9.js +18 -0
- package/frontend/.svelte-kit/generated/server/internal.js +2 -2
- package/frontend/src/lib/components/ui/Modal.svelte +7 -1
- package/frontend/src/lib/constants.js +0 -1
- package/frontend/src/lib/styles/tokens.css +2 -0
- package/frontend/src/routes/scheduled-tests/+page.svelte +3 -4
- package/frontend/src/routes/test-repository/runs/[id]/+page.svelte +12 -15
- package/package.json +1 -1
package/backend/mcp/server.js
CHANGED
|
@@ -67,6 +67,7 @@ async function api(method, path, body) {
|
|
|
67
67
|
const get = (path) => api('GET', path);
|
|
68
68
|
const post = (path, body) => api('POST', path, body);
|
|
69
69
|
const put = (path, body) => api('PUT', path, body);
|
|
70
|
+
const del = (path) => api('DELETE', path);
|
|
70
71
|
|
|
71
72
|
// ---------------------------------------------------------------------------
|
|
72
73
|
// Polling helper for test runs
|
|
@@ -176,6 +177,33 @@ server.tool(
|
|
|
176
177
|
}
|
|
177
178
|
);
|
|
178
179
|
|
|
180
|
+
server.tool(
|
|
181
|
+
'update_test_suite',
|
|
182
|
+
"Update a test suite's name, description, or priority. Only the fields provided are changed.",
|
|
183
|
+
{
|
|
184
|
+
suiteId: z.string().describe('UUID of the suite to update'),
|
|
185
|
+
name: z.string().min(1).optional(),
|
|
186
|
+
description: z.string().optional(),
|
|
187
|
+
priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
|
|
188
|
+
},
|
|
189
|
+
async ({ suiteId, name, description, priority }) => {
|
|
190
|
+
const data = await put(`/test-suites/${suiteId}`, { name, description, priority });
|
|
191
|
+
return { content: [{ type: 'text', text: JSON.stringify(data.suite, null, 2) }] };
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
server.tool(
|
|
196
|
+
'delete_test_suite',
|
|
197
|
+
'Permanently delete a test suite and all of its test cases. This cannot be undone.',
|
|
198
|
+
{
|
|
199
|
+
suiteId: z.string().describe('UUID of the suite to delete')
|
|
200
|
+
},
|
|
201
|
+
async ({ suiteId }) => {
|
|
202
|
+
await del(`/test-suites/${suiteId}`);
|
|
203
|
+
return { content: [{ type: 'text', text: `Suite ${suiteId} deleted.` }] };
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
179
207
|
// -- Test Repository: Cases -------------------------------------------------
|
|
180
208
|
|
|
181
209
|
server.tool(
|
|
@@ -193,6 +221,45 @@ server.tool(
|
|
|
193
221
|
}
|
|
194
222
|
);
|
|
195
223
|
|
|
224
|
+
server.tool(
|
|
225
|
+
'get_test_case',
|
|
226
|
+
'Get a test case by ID, including its manual steps and recent execution history.',
|
|
227
|
+
{
|
|
228
|
+
caseId: z.string().describe('UUID of the test case')
|
|
229
|
+
},
|
|
230
|
+
async ({ caseId }) => {
|
|
231
|
+
const data = await get(`/test-cases/${caseId}`);
|
|
232
|
+
return { content: [{ type: 'text', text: JSON.stringify(data.testCase, null, 2) }] };
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
server.tool(
|
|
237
|
+
'update_test_case',
|
|
238
|
+
"Update a test case's title, description, or priority. Only the fields provided are changed.",
|
|
239
|
+
{
|
|
240
|
+
caseId: z.string().describe('UUID of the test case to update'),
|
|
241
|
+
title: z.string().min(1).optional(),
|
|
242
|
+
description: z.string().optional(),
|
|
243
|
+
priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
|
|
244
|
+
},
|
|
245
|
+
async ({ caseId, title, description, priority }) => {
|
|
246
|
+
const data = await put(`/test-cases/${caseId}`, { title, description, priority });
|
|
247
|
+
return { content: [{ type: 'text', text: JSON.stringify(data.testCase, null, 2) }] };
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
server.tool(
|
|
252
|
+
'delete_test_case',
|
|
253
|
+
'Permanently delete a test case and its steps. This cannot be undone.',
|
|
254
|
+
{
|
|
255
|
+
caseId: z.string().describe('UUID of the test case to delete')
|
|
256
|
+
},
|
|
257
|
+
async ({ caseId }) => {
|
|
258
|
+
await del(`/test-cases/${caseId}`);
|
|
259
|
+
return { content: [{ type: 'text', text: `Test case ${caseId} deleted.` }] };
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
196
263
|
server.tool(
|
|
197
264
|
'set_test_steps',
|
|
198
265
|
'Set (replace) the manual test steps for a test case. Each step has an action, optional test data, and optional expected output.',
|
|
@@ -32,7 +32,7 @@ router.get('/', async (req, res) => {
|
|
|
32
32
|
router.post('/', async (req, res) => {
|
|
33
33
|
try {
|
|
34
34
|
const { cronExpression, taskName, tags } = req.body;
|
|
35
|
-
if (!cronExpression || !taskName
|
|
35
|
+
if (!cronExpression || !taskName) {
|
|
36
36
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
37
37
|
}
|
|
38
38
|
await cronService.addCronJob(req.body);
|
|
@@ -51,7 +51,7 @@ router.put('/:taskName', async (req, res) => {
|
|
|
51
51
|
try {
|
|
52
52
|
const { taskName } = req.params;
|
|
53
53
|
const { cronExpression, tags } = req.body;
|
|
54
|
-
if (!cronExpression
|
|
54
|
+
if (!cronExpression) {
|
|
55
55
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
56
56
|
}
|
|
57
57
|
await cronService.updateCronJob(taskName, req.body);
|
|
@@ -284,7 +284,7 @@ const addCronJob = async ({
|
|
|
284
284
|
notifyDiscord,
|
|
285
285
|
notifySlack
|
|
286
286
|
}) => {
|
|
287
|
-
if (!cronExpression || !taskName
|
|
287
|
+
if (!cronExpression || !taskName) {
|
|
288
288
|
return { status: 400, message: 'Missing required parameters' };
|
|
289
289
|
}
|
|
290
290
|
const runnerIdsStr =
|
|
@@ -294,7 +294,7 @@ const addCronJob = async ({
|
|
|
294
294
|
data: {
|
|
295
295
|
taskName,
|
|
296
296
|
cronExpression,
|
|
297
|
-
tags,
|
|
297
|
+
tags: tags ?? '',
|
|
298
298
|
workers: workers ?? 1,
|
|
299
299
|
browser: browser ?? 'chromium',
|
|
300
300
|
runnerIds: runnerIdsStr,
|
|
@@ -105,10 +105,27 @@ async function update(id, { title, status, caseIds }) {
|
|
|
105
105
|
if (status !== undefined) data.status = status;
|
|
106
106
|
|
|
107
107
|
if (caseIds !== undefined) {
|
|
108
|
-
|
|
108
|
+
// Diff against existing entries instead of delete-all/recreate, so cases that
|
|
109
|
+
// remain in the run keep their recorded status, notes, and assignment.
|
|
110
|
+
const existing = await prisma.testRunEntry.findMany({
|
|
111
|
+
where: { runId: id },
|
|
112
|
+
select: { id: true, caseId: true }
|
|
113
|
+
});
|
|
114
|
+
const existingIdByCaseId = new Map(existing.map((e) => [e.caseId, e.id]));
|
|
115
|
+
const keepCaseIds = new Set(caseIds);
|
|
116
|
+
const removedIds = existing.filter((e) => !keepCaseIds.has(e.caseId)).map((e) => e.id);
|
|
117
|
+
|
|
118
|
+
if (removedIds.length > 0) {
|
|
119
|
+
await prisma.testRunEntry.deleteMany({ where: { id: { in: removedIds } } });
|
|
120
|
+
}
|
|
109
121
|
await prisma.$transaction(
|
|
110
122
|
caseIds.map((caseId, i) =>
|
|
111
|
-
|
|
123
|
+
existingIdByCaseId.has(caseId)
|
|
124
|
+
? prisma.testRunEntry.update({
|
|
125
|
+
where: { id: existingIdByCaseId.get(caseId) },
|
|
126
|
+
data: { order: i }
|
|
127
|
+
})
|
|
128
|
+
: prisma.testRunEntry.create({ data: { runId: id, caseId, order: i } })
|
|
112
129
|
)
|
|
113
130
|
);
|
|
114
131
|
}
|
|
@@ -43,55 +43,56 @@
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
declare module '$env/static/private' {
|
|
46
|
-
export const
|
|
46
|
+
export const NoDefaultCurrentDirectoryInExePath: string;
|
|
47
|
+
export const CLAUDE_EFFORT: string;
|
|
48
|
+
export const CLAUDE_CODE_ENTRYPOINT: string;
|
|
47
49
|
export const TERM_PROGRAM: string;
|
|
48
50
|
export const NODE: string;
|
|
49
51
|
export const INIT_CWD: string;
|
|
52
|
+
export const VIKUNJA_BASE_URL: string;
|
|
50
53
|
export const TERM: string;
|
|
51
54
|
export const SHELL: string;
|
|
55
|
+
export const CLAUDE_CODE_CHILD_SESSION: string;
|
|
52
56
|
export const TMPDIR: string;
|
|
53
57
|
export const npm_config_global_prefix: string;
|
|
54
|
-
export const CODEX_MANAGED_PACKAGE_ROOT: string;
|
|
55
58
|
export const VSCODE_PYTHON_AUTOACTIVATE_GUARD: string;
|
|
56
59
|
export const TERM_PROGRAM_VERSION: string;
|
|
57
60
|
export const ZDOTDIR: string;
|
|
58
61
|
export const MallocNanoZone: string;
|
|
59
62
|
export const COLOR: string;
|
|
60
|
-
export const NO_COLOR: string;
|
|
61
63
|
export const npm_config_noproxy: string;
|
|
62
64
|
export const npm_config_local_prefix: string;
|
|
63
|
-
export const
|
|
65
|
+
export const GIT_EDITOR: string;
|
|
66
|
+
export const AI_AGENT: string;
|
|
64
67
|
export const COPILOT_DEBUG_NONCE: string;
|
|
68
|
+
export const VIKUNJA_API_KEY: string;
|
|
65
69
|
export const USER: string;
|
|
66
70
|
export const COMMAND_MODE: string;
|
|
67
71
|
export const npm_config_globalconfig: string;
|
|
68
|
-
export const
|
|
72
|
+
export const OUTLINE_BASE_URL: string;
|
|
69
73
|
export const CLAUDE_CODE_SSE_PORT: string;
|
|
70
|
-
export const
|
|
74
|
+
export const SSH_AUTH_SOCK: string;
|
|
71
75
|
export const VSCODE_PROFILE_INITIALIZED: string;
|
|
76
|
+
export const __CF_USER_TEXT_ENCODING: string;
|
|
72
77
|
export const npm_execpath: string;
|
|
73
|
-
export const PAGER: string;
|
|
74
78
|
export const PATH: string;
|
|
75
|
-
export const CODEX_SANDBOX: string;
|
|
76
79
|
export const npm_package_json: string;
|
|
77
80
|
export const npm_config_engine_strict: string;
|
|
78
81
|
export const _: string;
|
|
79
82
|
export const npm_config_userconfig: string;
|
|
80
83
|
export const npm_config_init_module: string;
|
|
81
|
-
export const __CFBundleIdentifier: string;
|
|
82
84
|
export const USER_ZDOTDIR: string;
|
|
83
|
-
export const
|
|
85
|
+
export const __CFBundleIdentifier: string;
|
|
84
86
|
export const npm_command: string;
|
|
85
87
|
export const PWD: string;
|
|
86
88
|
export const npm_lifecycle_event: string;
|
|
87
89
|
export const EDITOR: string;
|
|
90
|
+
export const OUTLINE_API_KEY: string;
|
|
88
91
|
export const npm_package_name: string;
|
|
89
92
|
export const LANG: string;
|
|
90
93
|
export const npm_config_npm_version: string;
|
|
91
|
-
export const XPC_FLAGS: string;
|
|
92
94
|
export const VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
|
|
93
|
-
export const
|
|
94
|
-
export const CODEX_CI: string;
|
|
95
|
+
export const XPC_FLAGS: string;
|
|
95
96
|
export const npm_config_node_gyp: string;
|
|
96
97
|
export const npm_package_version: string;
|
|
97
98
|
export const XPC_SERVICE_NAME: string;
|
|
@@ -99,17 +100,18 @@ declare module '$env/static/private' {
|
|
|
99
100
|
export const SHLVL: string;
|
|
100
101
|
export const HOME: string;
|
|
101
102
|
export const VSCODE_GIT_ASKPASS_MAIN: string;
|
|
102
|
-
export const
|
|
103
|
+
export const CLAUDE_CODE_EXECPATH: string;
|
|
103
104
|
export const npm_config_cache: string;
|
|
104
105
|
export const LOGNAME: string;
|
|
105
106
|
export const npm_lifecycle_script: string;
|
|
106
107
|
export const VSCODE_GIT_IPC_HANDLE: string;
|
|
107
|
-
export const
|
|
108
|
+
export const COREPACK_ENABLE_AUTO_PIN: string;
|
|
108
109
|
export const npm_config_user_agent: string;
|
|
110
|
+
export const CLAUDE_CODE_SESSION_ID: string;
|
|
109
111
|
export const VSCODE_GIT_ASKPASS_NODE: string;
|
|
110
112
|
export const GIT_ASKPASS: string;
|
|
111
113
|
export const OSLogRateLimit: string;
|
|
112
|
-
export const
|
|
114
|
+
export const CLAUDECODE: string;
|
|
113
115
|
export const npm_node_execpath: string;
|
|
114
116
|
export const npm_config_prefix: string;
|
|
115
117
|
export const COLORTERM: string;
|
|
@@ -145,55 +147,56 @@ declare module '$env/static/public' {
|
|
|
145
147
|
*/
|
|
146
148
|
declare module '$env/dynamic/private' {
|
|
147
149
|
export const env: {
|
|
148
|
-
|
|
150
|
+
NoDefaultCurrentDirectoryInExePath: string;
|
|
151
|
+
CLAUDE_EFFORT: string;
|
|
152
|
+
CLAUDE_CODE_ENTRYPOINT: string;
|
|
149
153
|
TERM_PROGRAM: string;
|
|
150
154
|
NODE: string;
|
|
151
155
|
INIT_CWD: string;
|
|
156
|
+
VIKUNJA_BASE_URL: string;
|
|
152
157
|
TERM: string;
|
|
153
158
|
SHELL: string;
|
|
159
|
+
CLAUDE_CODE_CHILD_SESSION: string;
|
|
154
160
|
TMPDIR: string;
|
|
155
161
|
npm_config_global_prefix: string;
|
|
156
|
-
CODEX_MANAGED_PACKAGE_ROOT: string;
|
|
157
162
|
VSCODE_PYTHON_AUTOACTIVATE_GUARD: string;
|
|
158
163
|
TERM_PROGRAM_VERSION: string;
|
|
159
164
|
ZDOTDIR: string;
|
|
160
165
|
MallocNanoZone: string;
|
|
161
166
|
COLOR: string;
|
|
162
|
-
NO_COLOR: string;
|
|
163
167
|
npm_config_noproxy: string;
|
|
164
168
|
npm_config_local_prefix: string;
|
|
165
|
-
|
|
169
|
+
GIT_EDITOR: string;
|
|
170
|
+
AI_AGENT: string;
|
|
166
171
|
COPILOT_DEBUG_NONCE: string;
|
|
172
|
+
VIKUNJA_API_KEY: string;
|
|
167
173
|
USER: string;
|
|
168
174
|
COMMAND_MODE: string;
|
|
169
175
|
npm_config_globalconfig: string;
|
|
170
|
-
|
|
176
|
+
OUTLINE_BASE_URL: string;
|
|
171
177
|
CLAUDE_CODE_SSE_PORT: string;
|
|
172
|
-
|
|
178
|
+
SSH_AUTH_SOCK: string;
|
|
173
179
|
VSCODE_PROFILE_INITIALIZED: string;
|
|
180
|
+
__CF_USER_TEXT_ENCODING: string;
|
|
174
181
|
npm_execpath: string;
|
|
175
|
-
PAGER: string;
|
|
176
182
|
PATH: string;
|
|
177
|
-
CODEX_SANDBOX: string;
|
|
178
183
|
npm_package_json: string;
|
|
179
184
|
npm_config_engine_strict: string;
|
|
180
185
|
_: string;
|
|
181
186
|
npm_config_userconfig: string;
|
|
182
187
|
npm_config_init_module: string;
|
|
183
|
-
__CFBundleIdentifier: string;
|
|
184
188
|
USER_ZDOTDIR: string;
|
|
185
|
-
|
|
189
|
+
__CFBundleIdentifier: string;
|
|
186
190
|
npm_command: string;
|
|
187
191
|
PWD: string;
|
|
188
192
|
npm_lifecycle_event: string;
|
|
189
193
|
EDITOR: string;
|
|
194
|
+
OUTLINE_API_KEY: string;
|
|
190
195
|
npm_package_name: string;
|
|
191
196
|
LANG: string;
|
|
192
197
|
npm_config_npm_version: string;
|
|
193
|
-
XPC_FLAGS: string;
|
|
194
198
|
VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
|
|
195
|
-
|
|
196
|
-
CODEX_CI: string;
|
|
199
|
+
XPC_FLAGS: string;
|
|
197
200
|
npm_config_node_gyp: string;
|
|
198
201
|
npm_package_version: string;
|
|
199
202
|
XPC_SERVICE_NAME: string;
|
|
@@ -201,17 +204,18 @@ declare module '$env/dynamic/private' {
|
|
|
201
204
|
SHLVL: string;
|
|
202
205
|
HOME: string;
|
|
203
206
|
VSCODE_GIT_ASKPASS_MAIN: string;
|
|
204
|
-
|
|
207
|
+
CLAUDE_CODE_EXECPATH: string;
|
|
205
208
|
npm_config_cache: string;
|
|
206
209
|
LOGNAME: string;
|
|
207
210
|
npm_lifecycle_script: string;
|
|
208
211
|
VSCODE_GIT_IPC_HANDLE: string;
|
|
209
|
-
|
|
212
|
+
COREPACK_ENABLE_AUTO_PIN: string;
|
|
210
213
|
npm_config_user_agent: string;
|
|
214
|
+
CLAUDE_CODE_SESSION_ID: string;
|
|
211
215
|
VSCODE_GIT_ASKPASS_NODE: string;
|
|
212
216
|
GIT_ASKPASS: string;
|
|
213
217
|
OSLogRateLimit: string;
|
|
214
|
-
|
|
218
|
+
CLAUDECODE: string;
|
|
215
219
|
npm_node_execpath: string;
|
|
216
220
|
npm_config_prefix: string;
|
|
217
221
|
COLORTERM: string;
|
|
@@ -23,16 +23,30 @@ export const nodes = [
|
|
|
23
23
|
() => import('./nodes/2'),
|
|
24
24
|
() => import('./nodes/3'),
|
|
25
25
|
() => import('./nodes/4'),
|
|
26
|
-
() => import('./nodes/5')
|
|
26
|
+
() => import('./nodes/5'),
|
|
27
|
+
() => import('./nodes/6'),
|
|
28
|
+
() => import('./nodes/7'),
|
|
29
|
+
() => import('./nodes/8'),
|
|
30
|
+
() => import('./nodes/9'),
|
|
31
|
+
() => import('./nodes/10'),
|
|
32
|
+
() => import('./nodes/11'),
|
|
33
|
+
() => import('./nodes/12')
|
|
27
34
|
];
|
|
28
35
|
|
|
29
36
|
export const server_loads = [];
|
|
30
37
|
|
|
31
38
|
export const dictionary = {
|
|
32
39
|
"/": [2],
|
|
33
|
-
"/
|
|
34
|
-
"/reports
|
|
35
|
-
"/
|
|
40
|
+
"/login": [3],
|
|
41
|
+
"/reports": [4],
|
|
42
|
+
"/reports/live": [6],
|
|
43
|
+
"/reports/[id]": [5],
|
|
44
|
+
"/scheduled-tests": [7],
|
|
45
|
+
"/settings": [8],
|
|
46
|
+
"/setup": [9],
|
|
47
|
+
"/test-repository": [10],
|
|
48
|
+
"/test-repository/runs/[id]": [11],
|
|
49
|
+
"/test-repository/suites/[id]": [12]
|
|
36
50
|
};
|
|
37
51
|
|
|
38
52
|
export const hooks = {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/test-repository/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/test-repository/runs/[id]/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/test-repository/suites/[id]/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/reports/live/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/scheduled-tests/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/settings/+page.svelte";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export { default as component } from "../../../../src/routes/setup/+page.svelte";
|
|
@@ -35,10 +35,10 @@ export const options = {
|
|
|
35
35
|
root,
|
|
36
36
|
service_worker: false,
|
|
37
37
|
templates: {
|
|
38
|
-
app: ({ head, body, assets, nonce, env }) => "<!--\nThis file is part of Plum.\n\nPlum is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nPlum is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Plum. If not, see https://www.gnu.org/licenses/.\n-->\n<!doctype html>\n<html lang=\"en\" data-theme=\"light\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.
|
|
38
|
+
app: ({ head, body, assets, nonce, env }) => "<!--\nThis file is part of Plum.\n\nPlum is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nPlum is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Plum. If not, see https://www.gnu.org/licenses/.\n-->\n<!doctype html>\n<html lang=\"en\" data-theme=\"light\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.ico\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n\t\t<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n\t\t<link\n\t\t\thref=\"https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap\"\n\t\t\trel=\"stylesheet\"\n\t\t/>\n\t\t<!-- Prevent theme flash before Svelte hydrates -->\n\t\t<script>\n\t\t\ttry {\n\t\t\t\tconst t = localStorage.getItem('plum-theme');\n\t\t\t\tif (t) document.documentElement.setAttribute('data-theme', t);\n\t\t\t} catch (e) {}\n\t\t</script>\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
|
|
39
39
|
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
|
|
40
40
|
},
|
|
41
|
-
version_hash: "
|
|
41
|
+
version_hash: "1r4g2tv"
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export async function get_hooks() {
|
|
@@ -82,7 +82,10 @@
|
|
|
82
82
|
<style>
|
|
83
83
|
.backdrop {
|
|
84
84
|
position: fixed;
|
|
85
|
-
|
|
85
|
+
top: 0;
|
|
86
|
+
left: 0;
|
|
87
|
+
right: 0;
|
|
88
|
+
bottom: var(--bottom-bar-height);
|
|
86
89
|
background: rgba(0, 0, 0, 0.45);
|
|
87
90
|
display: flex;
|
|
88
91
|
align-items: center;
|
|
@@ -90,6 +93,7 @@
|
|
|
90
93
|
z-index: 50;
|
|
91
94
|
padding: 1rem;
|
|
92
95
|
backdrop-filter: blur(2px);
|
|
96
|
+
overflow-y: auto;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
.panel {
|
|
@@ -99,6 +103,8 @@
|
|
|
99
103
|
padding: 1.75rem;
|
|
100
104
|
width: 100%;
|
|
101
105
|
max-width: 480px;
|
|
106
|
+
max-height: 100%;
|
|
107
|
+
overflow-y: auto;
|
|
102
108
|
box-shadow:
|
|
103
109
|
0 4px 6px rgba(0, 0, 0, 0.04),
|
|
104
110
|
0 24px 64px rgba(0, 0, 0, 0.14);
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
async function handleSave() {
|
|
178
|
-
if (!form.taskName || !form.cronExpression
|
|
178
|
+
if (!form.taskName || !form.cronExpression) {
|
|
179
179
|
formError = 'All fields are required.';
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
@@ -306,14 +306,13 @@
|
|
|
306
306
|
<div class="field">
|
|
307
307
|
<div class="field-label">
|
|
308
308
|
<span>Tags</span>
|
|
309
|
-
<span class="field-hint">Multiple: @test-1 or @test-2</span>
|
|
309
|
+
<span class="field-hint">Multiple: @test-1 or @test-2. Leave blank to run all tests</span>
|
|
310
310
|
</div>
|
|
311
311
|
<input
|
|
312
312
|
type="text"
|
|
313
313
|
class="field-input"
|
|
314
314
|
bind:value={form.tags}
|
|
315
|
-
placeholder="@suite-login"
|
|
316
|
-
required
|
|
315
|
+
placeholder="@suite-login (optional)"
|
|
317
316
|
/>
|
|
318
317
|
</div>
|
|
319
318
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
18
|
<script>
|
|
19
|
-
import { onMount
|
|
19
|
+
import { onMount } from 'svelte';
|
|
20
20
|
import { page } from '$app/stores';
|
|
21
21
|
import { fly } from 'svelte/transition';
|
|
22
22
|
import {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
import PriorityBadge from '$lib/components/ui/PriorityBadge.svelte';
|
|
37
37
|
import CaseIdChip from '$lib/components/ui/CaseIdChip.svelte';
|
|
38
38
|
import ResultChip from '$lib/components/ui/ResultChip.svelte';
|
|
39
|
-
import { TOAST_TIMEOUT_MS
|
|
39
|
+
import { TOAST_TIMEOUT_MS } from '$lib/constants';
|
|
40
40
|
|
|
41
41
|
const runId = $page.params.id;
|
|
42
42
|
|
|
@@ -117,8 +117,6 @@
|
|
|
117
117
|
setTimeout(() => (toast = null), TOAST_TIMEOUT_MS);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
let refreshInterval = null;
|
|
121
|
-
|
|
122
120
|
onMount(async () => {
|
|
123
121
|
try {
|
|
124
122
|
[run, suites, members] = await Promise.all([
|
|
@@ -133,16 +131,6 @@
|
|
|
133
131
|
} finally {
|
|
134
132
|
loading = false;
|
|
135
133
|
}
|
|
136
|
-
refreshInterval = setInterval(async () => {
|
|
137
|
-
try {
|
|
138
|
-
const fresh = await fetchRun(runId);
|
|
139
|
-
run = fresh;
|
|
140
|
-
} catch {}
|
|
141
|
-
}, RUN_REFRESH_MS);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
onDestroy(() => {
|
|
145
|
-
if (refreshInterval) clearInterval(refreshInterval);
|
|
146
134
|
});
|
|
147
135
|
|
|
148
136
|
async function handleAssignEntry(entryId, userId) {
|
|
@@ -188,8 +176,9 @@
|
|
|
188
176
|
suite.name.toLowerCase().includes(search.toLowerCase())
|
|
189
177
|
);
|
|
190
178
|
|
|
191
|
-
function addCase(tc) {
|
|
179
|
+
async function addCase(tc) {
|
|
192
180
|
if (!run || runCaseIds.has(tc.id)) return;
|
|
181
|
+
const previous = run;
|
|
193
182
|
const entry = {
|
|
194
183
|
id: `tmp-${tc.id}`,
|
|
195
184
|
order: run.entries.length,
|
|
@@ -200,6 +189,14 @@
|
|
|
200
189
|
case: tc
|
|
201
190
|
};
|
|
202
191
|
run = { ...run, entries: [...run.entries, entry] };
|
|
192
|
+
try {
|
|
193
|
+
const caseIds = run.entries.map((e) => e.case.id);
|
|
194
|
+
await updateRun(runId, { caseIds });
|
|
195
|
+
run = await fetchRun(runId);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
run = previous;
|
|
198
|
+
showToast('error', 'Failed to add case.');
|
|
199
|
+
}
|
|
203
200
|
}
|
|
204
201
|
|
|
205
202
|
function removeEntry(entryId) {
|