@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.
Files changed (61) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +273 -273
  3. package/dist/cli/index.cjs +295 -210
  4. package/dist/cli/index.cjs.map +1 -1
  5. package/dist/cli/index.js +294 -209
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.cjs +291 -206
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +291 -206
  10. package/dist/index.js.map +1 -1
  11. package/dist/subagents/index.cjs +115 -137
  12. package/dist/subagents/index.cjs.map +1 -1
  13. package/dist/subagents/index.js +115 -137
  14. package/dist/subagents/index.js.map +1 -1
  15. package/dist/subagents/metadata.cjs +12 -18
  16. package/dist/subagents/metadata.cjs.map +1 -1
  17. package/dist/subagents/metadata.js +12 -18
  18. package/dist/subagents/metadata.js.map +1 -1
  19. package/dist/tasks/index.cjs +142 -54
  20. package/dist/tasks/index.cjs.map +1 -1
  21. package/dist/tasks/index.js +142 -54
  22. package/dist/tasks/index.js.map +1 -1
  23. package/dist/templates/init/.bugzy/runtime/knowledge-base.md +61 -0
  24. package/dist/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -0
  25. package/dist/templates/init/.bugzy/runtime/project-context.md +35 -0
  26. package/dist/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -0
  27. package/dist/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -0
  28. package/dist/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -0
  29. package/dist/templates/init/.bugzy/runtime/test-execution-strategy.md +535 -0
  30. package/dist/templates/init/.bugzy/runtime/testing-best-practices.md +632 -0
  31. package/dist/templates/init/.gitignore-template +25 -0
  32. package/package.json +95 -95
  33. package/templates/init/.bugzy/runtime/knowledge-base.md +61 -61
  34. package/templates/init/.bugzy/runtime/knowledge-maintenance-guide.md +97 -97
  35. package/templates/init/.bugzy/runtime/project-context.md +35 -35
  36. package/templates/init/.bugzy/runtime/subagent-memory-guide.md +87 -87
  37. package/templates/init/.bugzy/runtime/templates/event-examples.md +194 -194
  38. package/templates/init/.bugzy/runtime/templates/test-plan-template.md +50 -50
  39. package/templates/init/.bugzy/runtime/templates/test-result-schema.md +498 -498
  40. package/templates/init/.claude/settings.json +28 -28
  41. package/templates/init/.env.testdata +18 -18
  42. package/templates/init/.gitignore-template +24 -24
  43. package/templates/init/AGENTS.md +155 -155
  44. package/templates/init/CLAUDE.md +157 -157
  45. package/templates/init/test-runs/README.md +45 -45
  46. package/templates/init/tests/CLAUDE.md +193 -193
  47. package/templates/init/tests/docs/test-execution-strategy.md +535 -535
  48. package/templates/init/tests/docs/testing-best-practices.md +724 -724
  49. package/templates/playwright/BasePage.template.ts +190 -190
  50. package/templates/playwright/auth.setup.template.ts +89 -89
  51. package/templates/playwright/dataGenerators.helper.template.ts +148 -148
  52. package/templates/playwright/dateUtils.helper.template.ts +96 -96
  53. package/templates/playwright/pages.fixture.template.ts +50 -50
  54. package/templates/playwright/playwright.config.template.ts +97 -97
  55. package/templates/playwright/reporters/__tests__/bugzy-reporter-failure-classification.test.ts +299 -299
  56. package/templates/playwright/reporters/__tests__/bugzy-reporter-manifest-merge.test.ts +329 -329
  57. package/templates/playwright/reporters/__tests__/playwright.config.ts +5 -5
  58. package/templates/playwright/reporters/bugzy-reporter.ts +784 -784
  59. package/templates/init/.bugzy/runtime/handlers/messages/feedback.md +0 -178
  60. package/templates/init/.bugzy/runtime/handlers/messages/question.md +0 -122
  61. package/templates/init/.bugzy/runtime/handlers/messages/status.md +0 -146
@@ -1,299 +1,299 @@
1
- import { test, expect } from '@playwright/test';
2
- import { classifyFailures } from '../bugzy-reporter';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
-
7
- function makeManifest(overrides: Partial<{
8
- timestamp: string;
9
- testCases: Array<{
10
- id: string;
11
- name: string;
12
- totalExecutions: number;
13
- finalStatus: string;
14
- executions: Array<{
15
- number: number;
16
- status: string;
17
- duration: number;
18
- videoFile: string | null;
19
- hasTrace: boolean;
20
- hasScreenshots: boolean;
21
- error: string | null;
22
- }>;
23
- }>;
24
- }> = {}) {
25
- const testCases = overrides.testCases ?? [];
26
- const totalExecutions = testCases.reduce((sum, tc) => sum + tc.executions.length, 0);
27
- const passed = testCases.filter(tc => tc.finalStatus === 'passed').length;
28
- const failed = testCases.length - passed;
29
-
30
- return {
31
- bugzyExecutionId: 'local-test',
32
- timestamp: overrides.timestamp ?? '20260216-120000',
33
- startTime: '2026-02-16T12:00:00.000Z',
34
- endTime: '2026-02-16T12:01:00.000Z',
35
- status: failed > 0 ? 'failed' : 'passed',
36
- stats: { totalTests: testCases.length, passed, failed, totalExecutions },
37
- testCases,
38
- };
39
- }
40
-
41
- function makeTestCase(id: string, finalStatus: string, error?: string) {
42
- return {
43
- id,
44
- name: id.replace(/^TC-\d+-/, '').replace(/-/g, ' '),
45
- totalExecutions: 1,
46
- finalStatus,
47
- executions: [{
48
- number: 1,
49
- status: finalStatus,
50
- duration: 1000,
51
- videoFile: null,
52
- hasTrace: false,
53
- hasScreenshots: false,
54
- error: error ?? null,
55
- }],
56
- };
57
- }
58
-
59
- function setupTestRunsDir(manifests: Array<{ timestamp: string; manifest: any }>) {
60
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bugzy-test-'));
61
- const testRunsRoot = path.join(tmpDir, 'test-runs');
62
- fs.mkdirSync(testRunsRoot, { recursive: true });
63
-
64
- for (const { timestamp, manifest } of manifests) {
65
- const runDir = path.join(testRunsRoot, timestamp);
66
- fs.mkdirSync(runDir, { recursive: true });
67
- fs.writeFileSync(
68
- path.join(runDir, 'manifest.json'),
69
- JSON.stringify(manifest, null, 2)
70
- );
71
- }
72
-
73
- return testRunsRoot;
74
- }
75
-
76
- test.describe('classifyFailures', () => {
77
- test('returns empty arrays when no failures', () => {
78
- const manifest = makeManifest({
79
- testCases: [makeTestCase('TC-001-login', 'passed')],
80
- });
81
-
82
- const result = classifyFailures(manifest, '/nonexistent');
83
-
84
- expect(result.newFailures).toHaveLength(0);
85
- expect(result.knownFailures).toHaveLength(0);
86
- });
87
-
88
- test('all failures are new when no previous runs exist', () => {
89
- const manifest = makeManifest({
90
- timestamp: '20260216-120000',
91
- testCases: [
92
- makeTestCase('TC-001-login', 'failed', 'timeout'),
93
- makeTestCase('TC-002-checkout', 'failed', 'element not found'),
94
- ],
95
- });
96
-
97
- const testRunsRoot = setupTestRunsDir([]);
98
-
99
- const result = classifyFailures(manifest, testRunsRoot);
100
-
101
- expect(result.newFailures).toHaveLength(2);
102
- expect(result.knownFailures).toHaveLength(0);
103
- expect(result.newFailures[0].id).toBe('TC-001-login');
104
- expect(result.newFailures[0].error).toBe('timeout');
105
- expect(result.newFailures[1].id).toBe('TC-002-checkout');
106
- });
107
-
108
- test('failure is new when test passed in recent run', () => {
109
- const previousManifest = makeManifest({
110
- timestamp: '20260215-120000',
111
- testCases: [
112
- makeTestCase('TC-001-login', 'passed'),
113
- makeTestCase('TC-002-checkout', 'passed'),
114
- ],
115
- });
116
-
117
- const testRunsRoot = setupTestRunsDir([
118
- { timestamp: '20260215-120000', manifest: previousManifest },
119
- ]);
120
-
121
- const currentManifest = makeManifest({
122
- timestamp: '20260216-120000',
123
- testCases: [
124
- makeTestCase('TC-001-login', 'failed', 'timeout'),
125
- ],
126
- });
127
-
128
- const result = classifyFailures(currentManifest, testRunsRoot);
129
-
130
- expect(result.newFailures).toHaveLength(1);
131
- expect(result.knownFailures).toHaveLength(0);
132
- expect(result.newFailures[0].id).toBe('TC-001-login');
133
- expect(result.newFailures[0].lastPassedRun).toBe('20260215-120000');
134
- });
135
-
136
- test('failure is known when test failed in all previous runs', () => {
137
- const prev1 = makeManifest({
138
- timestamp: '20260215-120000',
139
- testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
140
- });
141
- const prev2 = makeManifest({
142
- timestamp: '20260214-120000',
143
- testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
144
- });
145
-
146
- const testRunsRoot = setupTestRunsDir([
147
- { timestamp: '20260215-120000', manifest: prev1 },
148
- { timestamp: '20260214-120000', manifest: prev2 },
149
- ]);
150
-
151
- const currentManifest = makeManifest({
152
- timestamp: '20260216-120000',
153
- testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
154
- });
155
-
156
- const result = classifyFailures(currentManifest, testRunsRoot);
157
-
158
- expect(result.newFailures).toHaveLength(0);
159
- expect(result.knownFailures).toHaveLength(1);
160
- expect(result.knownFailures[0].id).toBe('TC-001-login');
161
- });
162
-
163
- test('mixed new and known failures', () => {
164
- const previousManifest = makeManifest({
165
- timestamp: '20260215-120000',
166
- testCases: [
167
- makeTestCase('TC-001-login', 'passed'),
168
- makeTestCase('TC-002-checkout', 'failed', 'always broken'),
169
- ],
170
- });
171
-
172
- const testRunsRoot = setupTestRunsDir([
173
- { timestamp: '20260215-120000', manifest: previousManifest },
174
- ]);
175
-
176
- const currentManifest = makeManifest({
177
- timestamp: '20260216-120000',
178
- testCases: [
179
- makeTestCase('TC-001-login', 'failed', 'new regression'),
180
- makeTestCase('TC-002-checkout', 'failed', 'still broken'),
181
- ],
182
- });
183
-
184
- const result = classifyFailures(currentManifest, testRunsRoot);
185
-
186
- expect(result.newFailures).toHaveLength(1);
187
- expect(result.newFailures[0].id).toBe('TC-001-login');
188
- expect(result.newFailures[0].lastPassedRun).toBe('20260215-120000');
189
-
190
- expect(result.knownFailures).toHaveLength(1);
191
- expect(result.knownFailures[0].id).toBe('TC-002-checkout');
192
- });
193
-
194
- test('new test not in history is treated as new failure', () => {
195
- const previousManifest = makeManifest({
196
- timestamp: '20260215-120000',
197
- testCases: [makeTestCase('TC-001-login', 'passed')],
198
- });
199
-
200
- const testRunsRoot = setupTestRunsDir([
201
- { timestamp: '20260215-120000', manifest: previousManifest },
202
- ]);
203
-
204
- const currentManifest = makeManifest({
205
- timestamp: '20260216-120000',
206
- testCases: [
207
- makeTestCase('TC-003-new-feature', 'failed', 'new test fails'),
208
- ],
209
- });
210
-
211
- const result = classifyFailures(currentManifest, testRunsRoot);
212
-
213
- expect(result.newFailures).toHaveLength(1);
214
- expect(result.newFailures[0].id).toBe('TC-003-new-feature');
215
- expect(result.newFailures[0].lastPassedRun).toBeNull();
216
- });
217
-
218
- test('respects BUGZY_FAILURE_LOOKBACK env var', () => {
219
- // Set lookback to 1
220
- const origEnv = process.env.BUGZY_FAILURE_LOOKBACK;
221
- process.env.BUGZY_FAILURE_LOOKBACK = '1';
222
-
223
- try {
224
- // Run 1: test passed
225
- // Run 2: test failed
226
- // Run 3 (current): test failed
227
- // With lookback=1, only run 2 is checked (most recent)
228
- const run1 = makeManifest({
229
- timestamp: '20260213-120000',
230
- testCases: [makeTestCase('TC-001-login', 'passed')],
231
- });
232
- const run2 = makeManifest({
233
- timestamp: '20260214-120000',
234
- testCases: [makeTestCase('TC-001-login', 'failed', 'broken')],
235
- });
236
-
237
- const testRunsRoot = setupTestRunsDir([
238
- { timestamp: '20260213-120000', manifest: run1 },
239
- { timestamp: '20260214-120000', manifest: run2 },
240
- ]);
241
-
242
- const currentManifest = makeManifest({
243
- timestamp: '20260215-120000',
244
- testCases: [makeTestCase('TC-001-login', 'failed', 'still broken')],
245
- });
246
-
247
- const result = classifyFailures(currentManifest, testRunsRoot);
248
-
249
- // With lookback=1, only sees run2 where test failed → known failure
250
- expect(result.knownFailures).toHaveLength(1);
251
- expect(result.newFailures).toHaveLength(0);
252
- } finally {
253
- if (origEnv !== undefined) {
254
- process.env.BUGZY_FAILURE_LOOKBACK = origEnv;
255
- } else {
256
- delete process.env.BUGZY_FAILURE_LOOKBACK;
257
- }
258
- }
259
- });
260
-
261
- test('handles timedOut status as failure', () => {
262
- const previousManifest = makeManifest({
263
- timestamp: '20260215-120000',
264
- testCases: [makeTestCase('TC-001-login', 'passed')],
265
- });
266
-
267
- const testRunsRoot = setupTestRunsDir([
268
- { timestamp: '20260215-120000', manifest: previousManifest },
269
- ]);
270
-
271
- const currentManifest = makeManifest({
272
- timestamp: '20260216-120000',
273
- testCases: [makeTestCase('TC-001-login', 'timedOut', 'Test timeout')],
274
- });
275
-
276
- const result = classifyFailures(currentManifest, testRunsRoot);
277
-
278
- expect(result.newFailures).toHaveLength(1);
279
- expect(result.newFailures[0].id).toBe('TC-001-login');
280
- });
281
-
282
- test('skips current run timestamp when reading previous manifests', () => {
283
- // Only the current run exists - should be treated as first run
284
- const currentManifest = makeManifest({
285
- timestamp: '20260216-120000',
286
- testCases: [makeTestCase('TC-001-login', 'failed', 'error')],
287
- });
288
-
289
- const testRunsRoot = setupTestRunsDir([
290
- { timestamp: '20260216-120000', manifest: currentManifest },
291
- ]);
292
-
293
- const result = classifyFailures(currentManifest, testRunsRoot);
294
-
295
- // First run - all failures are new
296
- expect(result.newFailures).toHaveLength(1);
297
- expect(result.knownFailures).toHaveLength(0);
298
- });
299
- });
1
+ import { test, expect } from '@playwright/test';
2
+ import { classifyFailures } from '../bugzy-reporter';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+
7
+ function makeManifest(overrides: Partial<{
8
+ timestamp: string;
9
+ testCases: Array<{
10
+ id: string;
11
+ name: string;
12
+ totalExecutions: number;
13
+ finalStatus: string;
14
+ executions: Array<{
15
+ number: number;
16
+ status: string;
17
+ duration: number;
18
+ videoFile: string | null;
19
+ hasTrace: boolean;
20
+ hasScreenshots: boolean;
21
+ error: string | null;
22
+ }>;
23
+ }>;
24
+ }> = {}) {
25
+ const testCases = overrides.testCases ?? [];
26
+ const totalExecutions = testCases.reduce((sum, tc) => sum + tc.executions.length, 0);
27
+ const passed = testCases.filter(tc => tc.finalStatus === 'passed').length;
28
+ const failed = testCases.length - passed;
29
+
30
+ return {
31
+ bugzyExecutionId: 'local-test',
32
+ timestamp: overrides.timestamp ?? '20260216-120000',
33
+ startTime: '2026-02-16T12:00:00.000Z',
34
+ endTime: '2026-02-16T12:01:00.000Z',
35
+ status: failed > 0 ? 'failed' : 'passed',
36
+ stats: { totalTests: testCases.length, passed, failed, totalExecutions },
37
+ testCases,
38
+ };
39
+ }
40
+
41
+ function makeTestCase(id: string, finalStatus: string, error?: string) {
42
+ return {
43
+ id,
44
+ name: id.replace(/^TC-\d+-/, '').replace(/-/g, ' '),
45
+ totalExecutions: 1,
46
+ finalStatus,
47
+ executions: [{
48
+ number: 1,
49
+ status: finalStatus,
50
+ duration: 1000,
51
+ videoFile: null,
52
+ hasTrace: false,
53
+ hasScreenshots: false,
54
+ error: error ?? null,
55
+ }],
56
+ };
57
+ }
58
+
59
+ function setupTestRunsDir(manifests: Array<{ timestamp: string; manifest: any }>) {
60
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bugzy-test-'));
61
+ const testRunsRoot = path.join(tmpDir, 'test-runs');
62
+ fs.mkdirSync(testRunsRoot, { recursive: true });
63
+
64
+ for (const { timestamp, manifest } of manifests) {
65
+ const runDir = path.join(testRunsRoot, timestamp);
66
+ fs.mkdirSync(runDir, { recursive: true });
67
+ fs.writeFileSync(
68
+ path.join(runDir, 'manifest.json'),
69
+ JSON.stringify(manifest, null, 2)
70
+ );
71
+ }
72
+
73
+ return testRunsRoot;
74
+ }
75
+
76
+ test.describe('classifyFailures', () => {
77
+ test('returns empty arrays when no failures', () => {
78
+ const manifest = makeManifest({
79
+ testCases: [makeTestCase('TC-001-login', 'passed')],
80
+ });
81
+
82
+ const result = classifyFailures(manifest, '/nonexistent');
83
+
84
+ expect(result.newFailures).toHaveLength(0);
85
+ expect(result.knownFailures).toHaveLength(0);
86
+ });
87
+
88
+ test('all failures are new when no previous runs exist', () => {
89
+ const manifest = makeManifest({
90
+ timestamp: '20260216-120000',
91
+ testCases: [
92
+ makeTestCase('TC-001-login', 'failed', 'timeout'),
93
+ makeTestCase('TC-002-checkout', 'failed', 'element not found'),
94
+ ],
95
+ });
96
+
97
+ const testRunsRoot = setupTestRunsDir([]);
98
+
99
+ const result = classifyFailures(manifest, testRunsRoot);
100
+
101
+ expect(result.newFailures).toHaveLength(2);
102
+ expect(result.knownFailures).toHaveLength(0);
103
+ expect(result.newFailures[0].id).toBe('TC-001-login');
104
+ expect(result.newFailures[0].error).toBe('timeout');
105
+ expect(result.newFailures[1].id).toBe('TC-002-checkout');
106
+ });
107
+
108
+ test('failure is new when test passed in recent run', () => {
109
+ const previousManifest = makeManifest({
110
+ timestamp: '20260215-120000',
111
+ testCases: [
112
+ makeTestCase('TC-001-login', 'passed'),
113
+ makeTestCase('TC-002-checkout', 'passed'),
114
+ ],
115
+ });
116
+
117
+ const testRunsRoot = setupTestRunsDir([
118
+ { timestamp: '20260215-120000', manifest: previousManifest },
119
+ ]);
120
+
121
+ const currentManifest = makeManifest({
122
+ timestamp: '20260216-120000',
123
+ testCases: [
124
+ makeTestCase('TC-001-login', 'failed', 'timeout'),
125
+ ],
126
+ });
127
+
128
+ const result = classifyFailures(currentManifest, testRunsRoot);
129
+
130
+ expect(result.newFailures).toHaveLength(1);
131
+ expect(result.knownFailures).toHaveLength(0);
132
+ expect(result.newFailures[0].id).toBe('TC-001-login');
133
+ expect(result.newFailures[0].lastPassedRun).toBe('20260215-120000');
134
+ });
135
+
136
+ test('failure is known when test failed in all previous runs', () => {
137
+ const prev1 = makeManifest({
138
+ timestamp: '20260215-120000',
139
+ testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
140
+ });
141
+ const prev2 = makeManifest({
142
+ timestamp: '20260214-120000',
143
+ testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
144
+ });
145
+
146
+ const testRunsRoot = setupTestRunsDir([
147
+ { timestamp: '20260215-120000', manifest: prev1 },
148
+ { timestamp: '20260214-120000', manifest: prev2 },
149
+ ]);
150
+
151
+ const currentManifest = makeManifest({
152
+ timestamp: '20260216-120000',
153
+ testCases: [makeTestCase('TC-001-login', 'failed', 'timeout')],
154
+ });
155
+
156
+ const result = classifyFailures(currentManifest, testRunsRoot);
157
+
158
+ expect(result.newFailures).toHaveLength(0);
159
+ expect(result.knownFailures).toHaveLength(1);
160
+ expect(result.knownFailures[0].id).toBe('TC-001-login');
161
+ });
162
+
163
+ test('mixed new and known failures', () => {
164
+ const previousManifest = makeManifest({
165
+ timestamp: '20260215-120000',
166
+ testCases: [
167
+ makeTestCase('TC-001-login', 'passed'),
168
+ makeTestCase('TC-002-checkout', 'failed', 'always broken'),
169
+ ],
170
+ });
171
+
172
+ const testRunsRoot = setupTestRunsDir([
173
+ { timestamp: '20260215-120000', manifest: previousManifest },
174
+ ]);
175
+
176
+ const currentManifest = makeManifest({
177
+ timestamp: '20260216-120000',
178
+ testCases: [
179
+ makeTestCase('TC-001-login', 'failed', 'new regression'),
180
+ makeTestCase('TC-002-checkout', 'failed', 'still broken'),
181
+ ],
182
+ });
183
+
184
+ const result = classifyFailures(currentManifest, testRunsRoot);
185
+
186
+ expect(result.newFailures).toHaveLength(1);
187
+ expect(result.newFailures[0].id).toBe('TC-001-login');
188
+ expect(result.newFailures[0].lastPassedRun).toBe('20260215-120000');
189
+
190
+ expect(result.knownFailures).toHaveLength(1);
191
+ expect(result.knownFailures[0].id).toBe('TC-002-checkout');
192
+ });
193
+
194
+ test('new test not in history is treated as new failure', () => {
195
+ const previousManifest = makeManifest({
196
+ timestamp: '20260215-120000',
197
+ testCases: [makeTestCase('TC-001-login', 'passed')],
198
+ });
199
+
200
+ const testRunsRoot = setupTestRunsDir([
201
+ { timestamp: '20260215-120000', manifest: previousManifest },
202
+ ]);
203
+
204
+ const currentManifest = makeManifest({
205
+ timestamp: '20260216-120000',
206
+ testCases: [
207
+ makeTestCase('TC-003-new-feature', 'failed', 'new test fails'),
208
+ ],
209
+ });
210
+
211
+ const result = classifyFailures(currentManifest, testRunsRoot);
212
+
213
+ expect(result.newFailures).toHaveLength(1);
214
+ expect(result.newFailures[0].id).toBe('TC-003-new-feature');
215
+ expect(result.newFailures[0].lastPassedRun).toBeNull();
216
+ });
217
+
218
+ test('respects BUGZY_FAILURE_LOOKBACK env var', () => {
219
+ // Set lookback to 1
220
+ const origEnv = process.env.BUGZY_FAILURE_LOOKBACK;
221
+ process.env.BUGZY_FAILURE_LOOKBACK = '1';
222
+
223
+ try {
224
+ // Run 1: test passed
225
+ // Run 2: test failed
226
+ // Run 3 (current): test failed
227
+ // With lookback=1, only run 2 is checked (most recent)
228
+ const run1 = makeManifest({
229
+ timestamp: '20260213-120000',
230
+ testCases: [makeTestCase('TC-001-login', 'passed')],
231
+ });
232
+ const run2 = makeManifest({
233
+ timestamp: '20260214-120000',
234
+ testCases: [makeTestCase('TC-001-login', 'failed', 'broken')],
235
+ });
236
+
237
+ const testRunsRoot = setupTestRunsDir([
238
+ { timestamp: '20260213-120000', manifest: run1 },
239
+ { timestamp: '20260214-120000', manifest: run2 },
240
+ ]);
241
+
242
+ const currentManifest = makeManifest({
243
+ timestamp: '20260215-120000',
244
+ testCases: [makeTestCase('TC-001-login', 'failed', 'still broken')],
245
+ });
246
+
247
+ const result = classifyFailures(currentManifest, testRunsRoot);
248
+
249
+ // With lookback=1, only sees run2 where test failed → known failure
250
+ expect(result.knownFailures).toHaveLength(1);
251
+ expect(result.newFailures).toHaveLength(0);
252
+ } finally {
253
+ if (origEnv !== undefined) {
254
+ process.env.BUGZY_FAILURE_LOOKBACK = origEnv;
255
+ } else {
256
+ delete process.env.BUGZY_FAILURE_LOOKBACK;
257
+ }
258
+ }
259
+ });
260
+
261
+ test('handles timedOut status as failure', () => {
262
+ const previousManifest = makeManifest({
263
+ timestamp: '20260215-120000',
264
+ testCases: [makeTestCase('TC-001-login', 'passed')],
265
+ });
266
+
267
+ const testRunsRoot = setupTestRunsDir([
268
+ { timestamp: '20260215-120000', manifest: previousManifest },
269
+ ]);
270
+
271
+ const currentManifest = makeManifest({
272
+ timestamp: '20260216-120000',
273
+ testCases: [makeTestCase('TC-001-login', 'timedOut', 'Test timeout')],
274
+ });
275
+
276
+ const result = classifyFailures(currentManifest, testRunsRoot);
277
+
278
+ expect(result.newFailures).toHaveLength(1);
279
+ expect(result.newFailures[0].id).toBe('TC-001-login');
280
+ });
281
+
282
+ test('skips current run timestamp when reading previous manifests', () => {
283
+ // Only the current run exists - should be treated as first run
284
+ const currentManifest = makeManifest({
285
+ timestamp: '20260216-120000',
286
+ testCases: [makeTestCase('TC-001-login', 'failed', 'error')],
287
+ });
288
+
289
+ const testRunsRoot = setupTestRunsDir([
290
+ { timestamp: '20260216-120000', manifest: currentManifest },
291
+ ]);
292
+
293
+ const result = classifyFailures(currentManifest, testRunsRoot);
294
+
295
+ // First run - all failures are new
296
+ expect(result.newFailures).toHaveLength(1);
297
+ expect(result.knownFailures).toHaveLength(0);
298
+ });
299
+ });