@bugzy-ai/bugzy 1.18.2 → 1.18.4
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/LICENSE +21 -21
- package/README.md +273 -273
- package/dist/cli/index.cjs +295 -210
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +294 -209
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +291 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +291 -206
- package/dist/index.js.map +1 -1
- package/dist/subagents/index.cjs +115 -137
- package/dist/subagents/index.cjs.map +1 -1
- package/dist/subagents/index.js +115 -137
- package/dist/subagents/index.js.map +1 -1
- package/dist/subagents/metadata.cjs +12 -18
- package/dist/subagents/metadata.cjs.map +1 -1
- package/dist/subagents/metadata.js +12 -18
- package/dist/subagents/metadata.js.map +1 -1
- package/dist/tasks/index.cjs +142 -54
- package/dist/tasks/index.cjs.map +1 -1
- package/dist/tasks/index.js +142 -54
- package/dist/tasks/index.js.map +1 -1
- package/dist/templates/init/.bugzy/runtime/knowledge-base.md +61 -0
- package/dist/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -0
- package/dist/templates/init/.bugzy/runtime/project-context.md +35 -0
- package/dist/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -0
- package/dist/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -0
- package/dist/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -0
- package/dist/templates/init/.bugzy/runtime/test-execution-strategy.md +535 -0
- package/dist/templates/init/.bugzy/runtime/testing-best-practices.md +632 -0
- package/dist/templates/init/.gitignore-template +25 -0
- package/package.json +95 -95
- package/templates/init/.bugzy/runtime/knowledge-base.md +61 -61
- package/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -97
- package/templates/init/.bugzy/runtime/project-context.md +35 -35
- package/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -87
- package/templates/init/.bugzy/runtime/templates/event-examples.md +194 -194
- package/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -50
- package/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -498
- package/templates/init/.claude/settings.json +28 -28
- package/templates/init/.env.testdata +18 -18
- package/templates/init/.gitignore-template +24 -24
- package/templates/init/AGENTS.md +155 -155
- package/templates/init/CLAUDE.md +157 -157
- package/templates/init/test-runs/README.md +45 -45
- package/templates/init/tests/CLAUDE.md +193 -193
- package/templates/init/tests/docs/test-execution-strategy.md +535 -535
- package/templates/init/tests/docs/testing-best-practices.md +724 -724
- package/templates/playwright/BasePage.template.ts +190 -190
- package/templates/playwright/auth.setup.template.ts +89 -89
- package/templates/playwright/dataGenerators.helper.template.ts +148 -148
- package/templates/playwright/dateUtils.helper.template.ts +96 -96
- package/templates/playwright/pages.fixture.template.ts +50 -50
- package/templates/playwright/playwright.config.template.ts +97 -97
- package/templates/playwright/reporters/__tests__/bugzy-reporter-failure-classification.test.ts +299 -299
- package/templates/playwright/reporters/__tests__/bugzy-reporter-manifest-merge.test.ts +329 -329
- package/templates/playwright/reporters/__tests__/playwright.config.ts +5 -5
- package/templates/playwright/reporters/bugzy-reporter.ts +784 -784
- package/templates/init/.bugzy/runtime/handlers/messages/feedback.md +0 -178
- package/templates/init/.bugzy/runtime/handlers/messages/question.md +0 -122
- package/templates/init/.bugzy/runtime/handlers/messages/status.md +0 -146
|
@@ -1,329 +1,329 @@
|
|
|
1
|
-
import { test, expect } from '@playwright/test';
|
|
2
|
-
import { mergeManifests } from '../bugzy-reporter';
|
|
3
|
-
|
|
4
|
-
function makeExecution(overrides: Partial<{
|
|
5
|
-
number: number;
|
|
6
|
-
status: string;
|
|
7
|
-
duration: number;
|
|
8
|
-
videoFile: string | null;
|
|
9
|
-
hasTrace: boolean;
|
|
10
|
-
hasScreenshots: boolean;
|
|
11
|
-
error: string | null;
|
|
12
|
-
}> = {}) {
|
|
13
|
-
return {
|
|
14
|
-
number: 1,
|
|
15
|
-
status: 'passed',
|
|
16
|
-
duration: 1000,
|
|
17
|
-
videoFile: 'video.webm',
|
|
18
|
-
hasTrace: false,
|
|
19
|
-
hasScreenshots: false,
|
|
20
|
-
error: null,
|
|
21
|
-
...overrides,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function makeTestCase(id: string, executions: ReturnType<typeof makeExecution>[], finalStatus?: string) {
|
|
26
|
-
const lastExec = executions[executions.length - 1];
|
|
27
|
-
return {
|
|
28
|
-
id,
|
|
29
|
-
name: id.replace(/^TC-\d+-/, '').replace(/-/g, ' '),
|
|
30
|
-
totalExecutions: executions.length,
|
|
31
|
-
finalStatus: finalStatus ?? lastExec.status,
|
|
32
|
-
executions,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function makeManifest(overrides: Partial<{
|
|
37
|
-
bugzyExecutionId: string;
|
|
38
|
-
timestamp: string;
|
|
39
|
-
startTime: string;
|
|
40
|
-
endTime: string;
|
|
41
|
-
status: string;
|
|
42
|
-
stats: { totalTests: number; passed: number; failed: number; totalExecutions: number };
|
|
43
|
-
testCases: ReturnType<typeof makeTestCase>[];
|
|
44
|
-
}> = {}) {
|
|
45
|
-
const testCases = overrides.testCases ?? [];
|
|
46
|
-
const totalExecutions = testCases.reduce((sum, tc) => sum + tc.executions.length, 0);
|
|
47
|
-
const passed = testCases.filter(tc => tc.finalStatus === 'passed').length;
|
|
48
|
-
const failed = testCases.length - passed;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
bugzyExecutionId: 'local-20260127-060129',
|
|
52
|
-
timestamp: '20260127-060129',
|
|
53
|
-
startTime: '2026-01-27T06:01:29.000Z',
|
|
54
|
-
endTime: '2026-01-27T06:02:00.000Z',
|
|
55
|
-
status: 'passed',
|
|
56
|
-
stats: {
|
|
57
|
-
totalTests: testCases.length,
|
|
58
|
-
passed,
|
|
59
|
-
failed,
|
|
60
|
-
totalExecutions,
|
|
61
|
-
...overrides.stats,
|
|
62
|
-
},
|
|
63
|
-
...overrides,
|
|
64
|
-
testCases,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
test.describe('mergeManifests', () => {
|
|
69
|
-
test('returns current manifest unchanged when existing is null', () => {
|
|
70
|
-
const current = makeManifest({
|
|
71
|
-
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const result = mergeManifests(null, current);
|
|
75
|
-
|
|
76
|
-
expect(result).toEqual(current);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('merges test cases from both manifests', () => {
|
|
80
|
-
const existing = makeManifest({
|
|
81
|
-
testCases: [
|
|
82
|
-
makeTestCase('TC-001-login', [makeExecution({ number: 1 })]),
|
|
83
|
-
],
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const current = makeManifest({
|
|
87
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
88
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
89
|
-
testCases: [
|
|
90
|
-
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
91
|
-
],
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const result = mergeManifests(existing, current);
|
|
95
|
-
|
|
96
|
-
expect(result.testCases).toHaveLength(2);
|
|
97
|
-
expect(result.testCases.map(tc => tc.id)).toContain('TC-001-login');
|
|
98
|
-
expect(result.testCases.map(tc => tc.id)).toContain('TC-002-checkout');
|
|
99
|
-
expect(result.stats.totalTests).toBe(2);
|
|
100
|
-
expect(result.stats.totalExecutions).toBe(2);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('merges executions for the same test case across runs', () => {
|
|
104
|
-
const existing = makeManifest({
|
|
105
|
-
testCases: [
|
|
106
|
-
makeTestCase('TC-001-login', [
|
|
107
|
-
makeExecution({ number: 1, status: 'failed', error: 'timeout' }),
|
|
108
|
-
], 'failed'),
|
|
109
|
-
],
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const current = makeManifest({
|
|
113
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
114
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
115
|
-
testCases: [
|
|
116
|
-
makeTestCase('TC-001-login', [
|
|
117
|
-
makeExecution({ number: 2, status: 'passed' }),
|
|
118
|
-
]),
|
|
119
|
-
],
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const result = mergeManifests(existing, current);
|
|
123
|
-
|
|
124
|
-
expect(result.testCases).toHaveLength(1);
|
|
125
|
-
const tc = result.testCases[0];
|
|
126
|
-
expect(tc.executions).toHaveLength(2);
|
|
127
|
-
expect(tc.executions[0].number).toBe(1);
|
|
128
|
-
expect(tc.executions[0].status).toBe('failed');
|
|
129
|
-
expect(tc.executions[1].number).toBe(2);
|
|
130
|
-
expect(tc.executions[1].status).toBe('passed');
|
|
131
|
-
expect(tc.totalExecutions).toBe(2);
|
|
132
|
-
expect(tc.finalStatus).toBe('passed'); // Latest execution status
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('current run wins on execution number collision', () => {
|
|
136
|
-
const existing = makeManifest({
|
|
137
|
-
testCases: [
|
|
138
|
-
makeTestCase('TC-001-login', [
|
|
139
|
-
makeExecution({ number: 3, status: 'failed', duration: 500 }),
|
|
140
|
-
], 'failed'),
|
|
141
|
-
],
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const current = makeManifest({
|
|
145
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
146
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
147
|
-
testCases: [
|
|
148
|
-
makeTestCase('TC-001-login', [
|
|
149
|
-
makeExecution({ number: 3, status: 'passed', duration: 1200 }),
|
|
150
|
-
]),
|
|
151
|
-
],
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const result = mergeManifests(existing, current);
|
|
155
|
-
|
|
156
|
-
const tc = result.testCases[0];
|
|
157
|
-
expect(tc.executions).toHaveLength(1);
|
|
158
|
-
expect(tc.executions[0].status).toBe('passed');
|
|
159
|
-
expect(tc.executions[0].duration).toBe(1200);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('preserves test cases that only exist in existing manifest', () => {
|
|
163
|
-
const existing = makeManifest({
|
|
164
|
-
testCases: [
|
|
165
|
-
makeTestCase('TC-001-login', [makeExecution({ number: 1 })]),
|
|
166
|
-
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
167
|
-
],
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const current = makeManifest({
|
|
171
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
172
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
173
|
-
testCases: [
|
|
174
|
-
makeTestCase('TC-001-login', [makeExecution({ number: 2 })]),
|
|
175
|
-
],
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const result = mergeManifests(existing, current);
|
|
179
|
-
|
|
180
|
-
expect(result.testCases).toHaveLength(2);
|
|
181
|
-
const checkout = result.testCases.find(tc => tc.id === 'TC-002-checkout');
|
|
182
|
-
expect(checkout).toBeDefined();
|
|
183
|
-
expect(checkout!.executions).toHaveLength(1);
|
|
184
|
-
expect(checkout!.executions[0].number).toBe(1);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('recalculates stats correctly from merged data', () => {
|
|
188
|
-
const existing = makeManifest({
|
|
189
|
-
testCases: [
|
|
190
|
-
makeTestCase('TC-001-login', [
|
|
191
|
-
makeExecution({ number: 1, status: 'failed' }),
|
|
192
|
-
], 'failed'),
|
|
193
|
-
makeTestCase('TC-002-checkout', [
|
|
194
|
-
makeExecution({ number: 1, status: 'passed' }),
|
|
195
|
-
]),
|
|
196
|
-
],
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const current = makeManifest({
|
|
200
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
201
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
202
|
-
testCases: [
|
|
203
|
-
makeTestCase('TC-001-login', [
|
|
204
|
-
makeExecution({ number: 2, status: 'passed' }),
|
|
205
|
-
]),
|
|
206
|
-
makeTestCase('TC-003-profile', [
|
|
207
|
-
makeExecution({ number: 1, status: 'failed' }),
|
|
208
|
-
], 'failed'),
|
|
209
|
-
],
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const result = mergeManifests(existing, current);
|
|
213
|
-
|
|
214
|
-
expect(result.stats.totalTests).toBe(3);
|
|
215
|
-
// TC-001: exec-1 (failed) + exec-2 (passed) = 2 execs, finalStatus=passed
|
|
216
|
-
// TC-002: exec-1 (passed) = 1 exec, finalStatus=passed
|
|
217
|
-
// TC-003: exec-1 (failed) = 1 exec, finalStatus=failed
|
|
218
|
-
expect(result.stats.totalExecutions).toBe(4);
|
|
219
|
-
expect(result.stats.passed).toBe(2); // TC-001 and TC-002
|
|
220
|
-
expect(result.stats.failed).toBe(1); // TC-003
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test('uses earliest startTime and latest endTime', () => {
|
|
224
|
-
const existing = makeManifest({
|
|
225
|
-
startTime: '2026-01-27T06:01:00.000Z',
|
|
226
|
-
endTime: '2026-01-27T06:02:00.000Z',
|
|
227
|
-
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const current = makeManifest({
|
|
231
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
232
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
233
|
-
testCases: [makeTestCase('TC-001-login', [makeExecution({ number: 2 })])],
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const result = mergeManifests(existing, current);
|
|
237
|
-
|
|
238
|
-
expect(result.startTime).toBe('2026-01-27T06:01:00.000Z');
|
|
239
|
-
expect(result.endTime).toBe('2026-01-27T06:06:00.000Z');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test('sets status to failed if any test case has failed finalStatus', () => {
|
|
243
|
-
const existing = makeManifest({
|
|
244
|
-
status: 'passed',
|
|
245
|
-
testCases: [
|
|
246
|
-
makeTestCase('TC-001-login', [makeExecution({ number: 1, status: 'passed' })]),
|
|
247
|
-
],
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
const current = makeManifest({
|
|
251
|
-
status: 'passed',
|
|
252
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
253
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
254
|
-
testCases: [
|
|
255
|
-
makeTestCase('TC-002-checkout', [
|
|
256
|
-
makeExecution({ number: 1, status: 'failed' }),
|
|
257
|
-
], 'failed'),
|
|
258
|
-
],
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const result = mergeManifests(existing, current);
|
|
262
|
-
|
|
263
|
-
expect(result.status).toBe('failed');
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('preserves original session timestamp from existing manifest', () => {
|
|
267
|
-
const existing = makeManifest({
|
|
268
|
-
timestamp: '20260127-060129',
|
|
269
|
-
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const current = makeManifest({
|
|
273
|
-
timestamp: '20260127-060500',
|
|
274
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
275
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
276
|
-
testCases: [makeTestCase('TC-001-login', [makeExecution({ number: 2 })])],
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const result = mergeManifests(existing, current);
|
|
280
|
-
|
|
281
|
-
expect(result.timestamp).toBe('20260127-060129');
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test('handles timedOut status as failure in merged status', () => {
|
|
285
|
-
const existing = makeManifest({
|
|
286
|
-
status: 'passed',
|
|
287
|
-
testCases: [
|
|
288
|
-
makeTestCase('TC-001-login', [
|
|
289
|
-
makeExecution({ number: 1, status: 'timedOut' }),
|
|
290
|
-
], 'timedOut'),
|
|
291
|
-
],
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const current = makeManifest({
|
|
295
|
-
status: 'passed',
|
|
296
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
297
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
298
|
-
testCases: [
|
|
299
|
-
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
300
|
-
],
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const result = mergeManifests(existing, current);
|
|
304
|
-
|
|
305
|
-
expect(result.status).toBe('failed');
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test('does not mutate input manifests', () => {
|
|
309
|
-
const existingExec = makeExecution({ number: 1, status: 'failed' });
|
|
310
|
-
const existing = makeManifest({
|
|
311
|
-
testCases: [makeTestCase('TC-001-login', [existingExec], 'failed')],
|
|
312
|
-
});
|
|
313
|
-
const existingSnapshot = JSON.parse(JSON.stringify(existing));
|
|
314
|
-
|
|
315
|
-
const current = makeManifest({
|
|
316
|
-
startTime: '2026-01-27T06:05:00.000Z',
|
|
317
|
-
endTime: '2026-01-27T06:06:00.000Z',
|
|
318
|
-
testCases: [
|
|
319
|
-
makeTestCase('TC-001-login', [makeExecution({ number: 2, status: 'passed' })]),
|
|
320
|
-
],
|
|
321
|
-
});
|
|
322
|
-
const currentSnapshot = JSON.parse(JSON.stringify(current));
|
|
323
|
-
|
|
324
|
-
mergeManifests(existing, current);
|
|
325
|
-
|
|
326
|
-
expect(existing).toEqual(existingSnapshot);
|
|
327
|
-
expect(current).toEqual(currentSnapshot);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
import { mergeManifests } from '../bugzy-reporter';
|
|
3
|
+
|
|
4
|
+
function makeExecution(overrides: Partial<{
|
|
5
|
+
number: number;
|
|
6
|
+
status: string;
|
|
7
|
+
duration: number;
|
|
8
|
+
videoFile: string | null;
|
|
9
|
+
hasTrace: boolean;
|
|
10
|
+
hasScreenshots: boolean;
|
|
11
|
+
error: string | null;
|
|
12
|
+
}> = {}) {
|
|
13
|
+
return {
|
|
14
|
+
number: 1,
|
|
15
|
+
status: 'passed',
|
|
16
|
+
duration: 1000,
|
|
17
|
+
videoFile: 'video.webm',
|
|
18
|
+
hasTrace: false,
|
|
19
|
+
hasScreenshots: false,
|
|
20
|
+
error: null,
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeTestCase(id: string, executions: ReturnType<typeof makeExecution>[], finalStatus?: string) {
|
|
26
|
+
const lastExec = executions[executions.length - 1];
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
name: id.replace(/^TC-\d+-/, '').replace(/-/g, ' '),
|
|
30
|
+
totalExecutions: executions.length,
|
|
31
|
+
finalStatus: finalStatus ?? lastExec.status,
|
|
32
|
+
executions,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeManifest(overrides: Partial<{
|
|
37
|
+
bugzyExecutionId: string;
|
|
38
|
+
timestamp: string;
|
|
39
|
+
startTime: string;
|
|
40
|
+
endTime: string;
|
|
41
|
+
status: string;
|
|
42
|
+
stats: { totalTests: number; passed: number; failed: number; totalExecutions: number };
|
|
43
|
+
testCases: ReturnType<typeof makeTestCase>[];
|
|
44
|
+
}> = {}) {
|
|
45
|
+
const testCases = overrides.testCases ?? [];
|
|
46
|
+
const totalExecutions = testCases.reduce((sum, tc) => sum + tc.executions.length, 0);
|
|
47
|
+
const passed = testCases.filter(tc => tc.finalStatus === 'passed').length;
|
|
48
|
+
const failed = testCases.length - passed;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
bugzyExecutionId: 'local-20260127-060129',
|
|
52
|
+
timestamp: '20260127-060129',
|
|
53
|
+
startTime: '2026-01-27T06:01:29.000Z',
|
|
54
|
+
endTime: '2026-01-27T06:02:00.000Z',
|
|
55
|
+
status: 'passed',
|
|
56
|
+
stats: {
|
|
57
|
+
totalTests: testCases.length,
|
|
58
|
+
passed,
|
|
59
|
+
failed,
|
|
60
|
+
totalExecutions,
|
|
61
|
+
...overrides.stats,
|
|
62
|
+
},
|
|
63
|
+
...overrides,
|
|
64
|
+
testCases,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test.describe('mergeManifests', () => {
|
|
69
|
+
test('returns current manifest unchanged when existing is null', () => {
|
|
70
|
+
const current = makeManifest({
|
|
71
|
+
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const result = mergeManifests(null, current);
|
|
75
|
+
|
|
76
|
+
expect(result).toEqual(current);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('merges test cases from both manifests', () => {
|
|
80
|
+
const existing = makeManifest({
|
|
81
|
+
testCases: [
|
|
82
|
+
makeTestCase('TC-001-login', [makeExecution({ number: 1 })]),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const current = makeManifest({
|
|
87
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
88
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
89
|
+
testCases: [
|
|
90
|
+
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = mergeManifests(existing, current);
|
|
95
|
+
|
|
96
|
+
expect(result.testCases).toHaveLength(2);
|
|
97
|
+
expect(result.testCases.map(tc => tc.id)).toContain('TC-001-login');
|
|
98
|
+
expect(result.testCases.map(tc => tc.id)).toContain('TC-002-checkout');
|
|
99
|
+
expect(result.stats.totalTests).toBe(2);
|
|
100
|
+
expect(result.stats.totalExecutions).toBe(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('merges executions for the same test case across runs', () => {
|
|
104
|
+
const existing = makeManifest({
|
|
105
|
+
testCases: [
|
|
106
|
+
makeTestCase('TC-001-login', [
|
|
107
|
+
makeExecution({ number: 1, status: 'failed', error: 'timeout' }),
|
|
108
|
+
], 'failed'),
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const current = makeManifest({
|
|
113
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
114
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
115
|
+
testCases: [
|
|
116
|
+
makeTestCase('TC-001-login', [
|
|
117
|
+
makeExecution({ number: 2, status: 'passed' }),
|
|
118
|
+
]),
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const result = mergeManifests(existing, current);
|
|
123
|
+
|
|
124
|
+
expect(result.testCases).toHaveLength(1);
|
|
125
|
+
const tc = result.testCases[0];
|
|
126
|
+
expect(tc.executions).toHaveLength(2);
|
|
127
|
+
expect(tc.executions[0].number).toBe(1);
|
|
128
|
+
expect(tc.executions[0].status).toBe('failed');
|
|
129
|
+
expect(tc.executions[1].number).toBe(2);
|
|
130
|
+
expect(tc.executions[1].status).toBe('passed');
|
|
131
|
+
expect(tc.totalExecutions).toBe(2);
|
|
132
|
+
expect(tc.finalStatus).toBe('passed'); // Latest execution status
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('current run wins on execution number collision', () => {
|
|
136
|
+
const existing = makeManifest({
|
|
137
|
+
testCases: [
|
|
138
|
+
makeTestCase('TC-001-login', [
|
|
139
|
+
makeExecution({ number: 3, status: 'failed', duration: 500 }),
|
|
140
|
+
], 'failed'),
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const current = makeManifest({
|
|
145
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
146
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
147
|
+
testCases: [
|
|
148
|
+
makeTestCase('TC-001-login', [
|
|
149
|
+
makeExecution({ number: 3, status: 'passed', duration: 1200 }),
|
|
150
|
+
]),
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const result = mergeManifests(existing, current);
|
|
155
|
+
|
|
156
|
+
const tc = result.testCases[0];
|
|
157
|
+
expect(tc.executions).toHaveLength(1);
|
|
158
|
+
expect(tc.executions[0].status).toBe('passed');
|
|
159
|
+
expect(tc.executions[0].duration).toBe(1200);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('preserves test cases that only exist in existing manifest', () => {
|
|
163
|
+
const existing = makeManifest({
|
|
164
|
+
testCases: [
|
|
165
|
+
makeTestCase('TC-001-login', [makeExecution({ number: 1 })]),
|
|
166
|
+
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const current = makeManifest({
|
|
171
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
172
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
173
|
+
testCases: [
|
|
174
|
+
makeTestCase('TC-001-login', [makeExecution({ number: 2 })]),
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = mergeManifests(existing, current);
|
|
179
|
+
|
|
180
|
+
expect(result.testCases).toHaveLength(2);
|
|
181
|
+
const checkout = result.testCases.find(tc => tc.id === 'TC-002-checkout');
|
|
182
|
+
expect(checkout).toBeDefined();
|
|
183
|
+
expect(checkout!.executions).toHaveLength(1);
|
|
184
|
+
expect(checkout!.executions[0].number).toBe(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('recalculates stats correctly from merged data', () => {
|
|
188
|
+
const existing = makeManifest({
|
|
189
|
+
testCases: [
|
|
190
|
+
makeTestCase('TC-001-login', [
|
|
191
|
+
makeExecution({ number: 1, status: 'failed' }),
|
|
192
|
+
], 'failed'),
|
|
193
|
+
makeTestCase('TC-002-checkout', [
|
|
194
|
+
makeExecution({ number: 1, status: 'passed' }),
|
|
195
|
+
]),
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const current = makeManifest({
|
|
200
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
201
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
202
|
+
testCases: [
|
|
203
|
+
makeTestCase('TC-001-login', [
|
|
204
|
+
makeExecution({ number: 2, status: 'passed' }),
|
|
205
|
+
]),
|
|
206
|
+
makeTestCase('TC-003-profile', [
|
|
207
|
+
makeExecution({ number: 1, status: 'failed' }),
|
|
208
|
+
], 'failed'),
|
|
209
|
+
],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const result = mergeManifests(existing, current);
|
|
213
|
+
|
|
214
|
+
expect(result.stats.totalTests).toBe(3);
|
|
215
|
+
// TC-001: exec-1 (failed) + exec-2 (passed) = 2 execs, finalStatus=passed
|
|
216
|
+
// TC-002: exec-1 (passed) = 1 exec, finalStatus=passed
|
|
217
|
+
// TC-003: exec-1 (failed) = 1 exec, finalStatus=failed
|
|
218
|
+
expect(result.stats.totalExecutions).toBe(4);
|
|
219
|
+
expect(result.stats.passed).toBe(2); // TC-001 and TC-002
|
|
220
|
+
expect(result.stats.failed).toBe(1); // TC-003
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('uses earliest startTime and latest endTime', () => {
|
|
224
|
+
const existing = makeManifest({
|
|
225
|
+
startTime: '2026-01-27T06:01:00.000Z',
|
|
226
|
+
endTime: '2026-01-27T06:02:00.000Z',
|
|
227
|
+
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const current = makeManifest({
|
|
231
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
232
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
233
|
+
testCases: [makeTestCase('TC-001-login', [makeExecution({ number: 2 })])],
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const result = mergeManifests(existing, current);
|
|
237
|
+
|
|
238
|
+
expect(result.startTime).toBe('2026-01-27T06:01:00.000Z');
|
|
239
|
+
expect(result.endTime).toBe('2026-01-27T06:06:00.000Z');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('sets status to failed if any test case has failed finalStatus', () => {
|
|
243
|
+
const existing = makeManifest({
|
|
244
|
+
status: 'passed',
|
|
245
|
+
testCases: [
|
|
246
|
+
makeTestCase('TC-001-login', [makeExecution({ number: 1, status: 'passed' })]),
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const current = makeManifest({
|
|
251
|
+
status: 'passed',
|
|
252
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
253
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
254
|
+
testCases: [
|
|
255
|
+
makeTestCase('TC-002-checkout', [
|
|
256
|
+
makeExecution({ number: 1, status: 'failed' }),
|
|
257
|
+
], 'failed'),
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const result = mergeManifests(existing, current);
|
|
262
|
+
|
|
263
|
+
expect(result.status).toBe('failed');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('preserves original session timestamp from existing manifest', () => {
|
|
267
|
+
const existing = makeManifest({
|
|
268
|
+
timestamp: '20260127-060129',
|
|
269
|
+
testCases: [makeTestCase('TC-001-login', [makeExecution()])],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const current = makeManifest({
|
|
273
|
+
timestamp: '20260127-060500',
|
|
274
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
275
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
276
|
+
testCases: [makeTestCase('TC-001-login', [makeExecution({ number: 2 })])],
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const result = mergeManifests(existing, current);
|
|
280
|
+
|
|
281
|
+
expect(result.timestamp).toBe('20260127-060129');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('handles timedOut status as failure in merged status', () => {
|
|
285
|
+
const existing = makeManifest({
|
|
286
|
+
status: 'passed',
|
|
287
|
+
testCases: [
|
|
288
|
+
makeTestCase('TC-001-login', [
|
|
289
|
+
makeExecution({ number: 1, status: 'timedOut' }),
|
|
290
|
+
], 'timedOut'),
|
|
291
|
+
],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const current = makeManifest({
|
|
295
|
+
status: 'passed',
|
|
296
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
297
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
298
|
+
testCases: [
|
|
299
|
+
makeTestCase('TC-002-checkout', [makeExecution({ number: 1 })]),
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const result = mergeManifests(existing, current);
|
|
304
|
+
|
|
305
|
+
expect(result.status).toBe('failed');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('does not mutate input manifests', () => {
|
|
309
|
+
const existingExec = makeExecution({ number: 1, status: 'failed' });
|
|
310
|
+
const existing = makeManifest({
|
|
311
|
+
testCases: [makeTestCase('TC-001-login', [existingExec], 'failed')],
|
|
312
|
+
});
|
|
313
|
+
const existingSnapshot = JSON.parse(JSON.stringify(existing));
|
|
314
|
+
|
|
315
|
+
const current = makeManifest({
|
|
316
|
+
startTime: '2026-01-27T06:05:00.000Z',
|
|
317
|
+
endTime: '2026-01-27T06:06:00.000Z',
|
|
318
|
+
testCases: [
|
|
319
|
+
makeTestCase('TC-001-login', [makeExecution({ number: 2, status: 'passed' })]),
|
|
320
|
+
],
|
|
321
|
+
});
|
|
322
|
+
const currentSnapshot = JSON.parse(JSON.stringify(current));
|
|
323
|
+
|
|
324
|
+
mergeManifests(existing, current);
|
|
325
|
+
|
|
326
|
+
expect(existing).toEqual(existingSnapshot);
|
|
327
|
+
expect(current).toEqual(currentSnapshot);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineConfig } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
testDir: '.',
|
|
5
|
-
});
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: '.',
|
|
5
|
+
});
|