@zohodesk/testinglibrary 0.0.85-n20-experimental → 0.0.87-n20-experimental
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/build/core/playwright/builtInFixtures/actorContext.js +13 -10
- package/build/core/playwright/builtInFixtures/page.js +0 -1
- package/build/core/playwright/constants/failureTagConstants.js +3 -5
- package/build/core/playwright/helpers/failureTagClassifier.js +67 -137
- package/build/core/playwright/helpers/logCollector.js +4 -1
- package/build/core/playwright/helpers/runtimeLogAnnotator.js +140 -0
- package/build/core/playwright/patterns/codePatterns.js +1 -1
- package/build/core/playwright/patterns/environmentPatterns.js +1 -1
- package/build/core/playwright/patterns/toolPatterns.js +1 -9
- package/build/core/playwright/setup/qc-custom-reporter.js +1 -2
- package/build/test/core/playwright/helpers/__tests__/failureTagClassifier.test.js +249 -67
- package/build/test/core/playwright/helpers/__tests__/logCollector.test.js +25 -1
- package/npm-shrinkwrap.json +39 -39
- package/package.json +1 -1
- package/unit_reports/unit-report.html +1 -1
|
@@ -6,18 +6,27 @@ var _os = _interopRequireDefault(require("os"));
|
|
|
6
6
|
var _path = _interopRequireDefault(require("path"));
|
|
7
7
|
var toolPatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/toolPatterns"));
|
|
8
8
|
var casePatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/casePatterns"));
|
|
9
|
+
var codePatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/codePatterns"));
|
|
10
|
+
var _readConfigFile = require("../../../../../core/playwright/readConfigFile");
|
|
9
11
|
var _runtimeLogPath = require("../../../../../core/playwright/helpers/runtimeLogPath");
|
|
10
12
|
var _failureTagClassifier = require("../../../../../core/playwright/helpers/failureTagClassifier");
|
|
13
|
+
var _runtimeLogAnnotator = require("../../../../../core/playwright/helpers/runtimeLogAnnotator");
|
|
11
14
|
var _failureTagConstants = require("../../../../../core/playwright/constants/failureTagConstants");
|
|
12
15
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
16
|
+
jest.mock('../../../../../core/playwright/readConfigFile', () => ({
|
|
17
|
+
generateConfigFromFile: jest.fn()
|
|
18
|
+
}));
|
|
13
19
|
const TEST_TITLE = 'environment-abort-test';
|
|
14
20
|
let tempDir;
|
|
21
|
+
function runtimeLogsDir() {
|
|
22
|
+
return _path.default.join(tempDir, 'runtime-logs');
|
|
23
|
+
}
|
|
15
24
|
function writeRuntimeLog(events, {
|
|
16
25
|
workerIndex = 0,
|
|
17
26
|
retry = 0,
|
|
18
27
|
title = TEST_TITLE
|
|
19
28
|
} = {}) {
|
|
20
|
-
const file = (0, _runtimeLogPath.resolveRuntimeLogFilePath)(
|
|
29
|
+
const file = (0, _runtimeLogPath.resolveRuntimeLogFilePath)(runtimeLogsDir(), {
|
|
21
30
|
workerIndex,
|
|
22
31
|
retry,
|
|
23
32
|
title
|
|
@@ -55,6 +64,9 @@ function failingDetailWithStdio({
|
|
|
55
64
|
}
|
|
56
65
|
beforeEach(() => {
|
|
57
66
|
tempDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'failure-tag-'));
|
|
67
|
+
_readConfigFile.generateConfigFromFile.mockReturnValue({
|
|
68
|
+
uatDirectory: tempDir
|
|
69
|
+
});
|
|
58
70
|
});
|
|
59
71
|
afterEach(() => {
|
|
60
72
|
_fs.default.rmSync(tempDir, {
|
|
@@ -63,12 +75,12 @@ afterEach(() => {
|
|
|
63
75
|
});
|
|
64
76
|
});
|
|
65
77
|
describe('classifyFailureTags', () => {
|
|
66
|
-
test('
|
|
78
|
+
test('leaves retry-passed (flaky) outcomes untagged', () => {
|
|
67
79
|
const tags = (0, _failureTagClassifier.classifyFailureTags)([{
|
|
68
80
|
status: 'flaky',
|
|
69
81
|
results: []
|
|
70
|
-
}], TEST_TITLE
|
|
71
|
-
expect(tags).toEqual([
|
|
82
|
+
}], TEST_TITLE);
|
|
83
|
+
expect(tags).toEqual([]);
|
|
72
84
|
});
|
|
73
85
|
test('tags environment when a document abort is present', () => {
|
|
74
86
|
const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
|
|
@@ -76,7 +88,7 @@ describe('classifyFailureTags', () => {
|
|
|
76
88
|
url: 'https://app/events/list/commentsubtabevent',
|
|
77
89
|
failure: 'net::ERR_ABORTED',
|
|
78
90
|
resourceType: 'document'
|
|
79
|
-
}])], TEST_TITLE
|
|
91
|
+
}])], TEST_TITLE);
|
|
80
92
|
expect(tags).toEqual(['ENVIRONMENT']);
|
|
81
93
|
});
|
|
82
94
|
test('tags environment for a network/cert failure', () => {
|
|
@@ -87,7 +99,7 @@ describe('classifyFailureTags', () => {
|
|
|
87
99
|
resourceType: 'script'
|
|
88
100
|
}], {
|
|
89
101
|
title: 'cert-test'
|
|
90
|
-
})], 'cert-test'
|
|
102
|
+
})], 'cert-test');
|
|
91
103
|
expect(tags).toEqual(['ENVIRONMENT']);
|
|
92
104
|
});
|
|
93
105
|
test('tags code when an app undefined-property error is present', () => {
|
|
@@ -97,10 +109,10 @@ describe('classifyFailureTags', () => {
|
|
|
97
109
|
message: "Cannot read properties of undefined (reading 'id')"
|
|
98
110
|
}], {
|
|
99
111
|
title: 'code-test'
|
|
100
|
-
})], 'code-test'
|
|
112
|
+
})], 'code-test');
|
|
101
113
|
expect(tags).toEqual(['CODE']);
|
|
102
114
|
});
|
|
103
|
-
test('
|
|
115
|
+
test('defaults to code (not environment) for a non-document xhr abort', () => {
|
|
104
116
|
const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
|
|
105
117
|
kind: 'requestfailed',
|
|
106
118
|
url: 'https://app/api/tickets',
|
|
@@ -108,17 +120,17 @@ describe('classifyFailureTags', () => {
|
|
|
108
120
|
resourceType: 'xhr'
|
|
109
121
|
}], {
|
|
110
122
|
title: 'xhr-test'
|
|
111
|
-
})], 'xhr-test'
|
|
112
|
-
expect(tags).toEqual([]);
|
|
123
|
+
})], 'xhr-test');
|
|
124
|
+
expect(tags).toEqual(['CODE']);
|
|
113
125
|
});
|
|
114
|
-
test('tags case
|
|
126
|
+
test('tags case when stderr reports DATA_GENERATION_ERROR', () => {
|
|
115
127
|
const detail = failingDetailWithStdio({
|
|
116
128
|
stderr: [{
|
|
117
129
|
text: 'status : DATA_GENERATION_ERROR\n generator: comments'
|
|
118
130
|
}]
|
|
119
131
|
});
|
|
120
|
-
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
121
|
-
expect(tags).toEqual(['CASE'
|
|
132
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
133
|
+
expect(tags).toEqual(['CASE']);
|
|
122
134
|
});
|
|
123
135
|
test('tags tool for a known tool pattern in base64 stdout', () => {
|
|
124
136
|
const detail = failingDetailWithStdio({
|
|
@@ -126,10 +138,10 @@ describe('classifyFailureTags', () => {
|
|
|
126
138
|
buffer: Buffer.from('Error during login').toString('base64')
|
|
127
139
|
}]
|
|
128
140
|
});
|
|
129
|
-
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
141
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
130
142
|
expect(tags).toEqual(['TOOL']);
|
|
131
143
|
});
|
|
132
|
-
test('tags case
|
|
144
|
+
test('tags case when stderr matches a case pattern', () => {
|
|
133
145
|
const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
|
|
134
146
|
try {
|
|
135
147
|
casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
|
|
@@ -138,22 +150,37 @@ describe('classifyFailureTags', () => {
|
|
|
138
150
|
text: 'Error: expect(received).toBe(expected)'
|
|
139
151
|
}]
|
|
140
152
|
});
|
|
141
|
-
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
142
|
-
expect(tags).toEqual(['CASE'
|
|
153
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
154
|
+
expect(tags).toEqual(['CASE']);
|
|
143
155
|
} finally {
|
|
144
156
|
casePatterns.CASE_PATTERNS.length = 0;
|
|
145
157
|
casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
|
|
146
158
|
}
|
|
147
159
|
});
|
|
148
|
-
test('
|
|
160
|
+
test('emits only the highest-priority tag (CODE) when CODE and ENVIRONMENT both match', () => {
|
|
161
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
|
|
162
|
+
kind: 'requestfailed',
|
|
163
|
+
url: 'https://app/events/list/commentsubtabevent',
|
|
164
|
+
failure: 'net::ERR_ABORTED',
|
|
165
|
+
resourceType: 'document'
|
|
166
|
+
}, {
|
|
167
|
+
kind: 'pageerror',
|
|
168
|
+
name: 'TypeError',
|
|
169
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
170
|
+
}], {
|
|
171
|
+
title: 'code-over-env-test'
|
|
172
|
+
})], 'code-over-env-test');
|
|
173
|
+
expect(tags).toEqual(['CODE']);
|
|
174
|
+
});
|
|
175
|
+
test('defaults to code (not tool) for benign stdout/stderr', () => {
|
|
149
176
|
const detail = failingDetailWithStdio({
|
|
150
177
|
stdout: [{
|
|
151
178
|
text: 'Generated 5 records successfully'
|
|
152
179
|
}],
|
|
153
180
|
stderr: []
|
|
154
181
|
});
|
|
155
|
-
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
156
|
-
expect(tags).toEqual([]);
|
|
182
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
183
|
+
expect(tags).toEqual(['CODE']);
|
|
157
184
|
});
|
|
158
185
|
test('does not throw for malformed stdio chunk entries', () => {
|
|
159
186
|
const detail = failingDetailWithStdio({
|
|
@@ -162,10 +189,10 @@ describe('classifyFailureTags', () => {
|
|
|
162
189
|
}],
|
|
163
190
|
stderr: [false]
|
|
164
191
|
});
|
|
165
|
-
expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
166
|
-
expect((0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
192
|
+
expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE)).not.toThrow();
|
|
193
|
+
expect((0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE)).toEqual(['CODE']);
|
|
167
194
|
});
|
|
168
|
-
test('
|
|
195
|
+
test('defaults to code when a failure matches no pattern', () => {
|
|
169
196
|
const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
|
|
170
197
|
kind: 'response',
|
|
171
198
|
status: 200,
|
|
@@ -180,7 +207,18 @@ describe('classifyFailureTags', () => {
|
|
|
180
207
|
}
|
|
181
208
|
}], {
|
|
182
209
|
title: 'neutral-test'
|
|
183
|
-
})], 'neutral-test'
|
|
210
|
+
})], 'neutral-test');
|
|
211
|
+
expect(tags).toEqual(['CODE']);
|
|
212
|
+
});
|
|
213
|
+
test('leaves a passing spec untagged (no default code)', () => {
|
|
214
|
+
const tags = (0, _failureTagClassifier.classifyFailureTags)([{
|
|
215
|
+
status: 'expected',
|
|
216
|
+
results: [{
|
|
217
|
+
status: 'passed',
|
|
218
|
+
workerIndex: 0,
|
|
219
|
+
retry: 0
|
|
220
|
+
}]
|
|
221
|
+
}], 'passing-test');
|
|
184
222
|
expect(tags).toEqual([]);
|
|
185
223
|
});
|
|
186
224
|
});
|
|
@@ -191,7 +229,7 @@ describe('classifyFailureDetails', () => {
|
|
|
191
229
|
url: 'https://app/events/list/commentsubtabevent',
|
|
192
230
|
failure: 'net::ERR_ABORTED',
|
|
193
231
|
resourceType: 'document'
|
|
194
|
-
}])], TEST_TITLE
|
|
232
|
+
}])], TEST_TITLE);
|
|
195
233
|
expect(details.tags).toEqual(['ENVIRONMENT']);
|
|
196
234
|
expect(details.matches).toHaveLength(1);
|
|
197
235
|
expect(details.matches[0]).toMatchObject({
|
|
@@ -214,25 +252,17 @@ describe('classifyFailureDetails', () => {
|
|
|
214
252
|
source: 'runtime-log'
|
|
215
253
|
});
|
|
216
254
|
});
|
|
217
|
-
test('
|
|
218
|
-
const details = (0, _failureTagClassifier.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}], TEST_TITLE, tempDir);
|
|
222
|
-
expect(details.tags).toEqual(['FLAKY']);
|
|
223
|
-
expect(details.matches[0]).toEqual({
|
|
224
|
-
tag: 'FLAKY',
|
|
225
|
-
pattern: null,
|
|
226
|
-
source: 'retry-outcome',
|
|
227
|
-
text: null
|
|
228
|
-
});
|
|
255
|
+
test('defaults to code with no matches when events carry no recognised pattern', () => {
|
|
256
|
+
const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([]);
|
|
257
|
+
expect(details.tags).toEqual(['CODE']);
|
|
258
|
+
expect(details.matches).toEqual([]);
|
|
229
259
|
});
|
|
230
260
|
test('records tool pattern from stdio', () => {
|
|
231
261
|
const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
|
|
232
262
|
stderr: [{
|
|
233
263
|
text: 'Error during login for actor admin'
|
|
234
264
|
}]
|
|
235
|
-
})], TEST_TITLE
|
|
265
|
+
})], TEST_TITLE);
|
|
236
266
|
expect(details.tags).toEqual(['TOOL']);
|
|
237
267
|
expect(details.matches[0]).toMatchObject({
|
|
238
268
|
tag: 'TOOL',
|
|
@@ -240,7 +270,7 @@ describe('classifyFailureDetails', () => {
|
|
|
240
270
|
source: 'stdio'
|
|
241
271
|
});
|
|
242
272
|
});
|
|
243
|
-
test('records case pattern from stdio
|
|
273
|
+
test('records case pattern from stdio without implying code', () => {
|
|
244
274
|
const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
|
|
245
275
|
try {
|
|
246
276
|
casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
|
|
@@ -248,26 +278,20 @@ describe('classifyFailureDetails', () => {
|
|
|
248
278
|
stderr: [{
|
|
249
279
|
text: 'Error: expect(received).toBe(expected)'
|
|
250
280
|
}]
|
|
251
|
-
})], TEST_TITLE
|
|
252
|
-
expect(details.tags).toEqual(['CASE'
|
|
253
|
-
expect(details.matches).toHaveLength(
|
|
281
|
+
})], TEST_TITLE);
|
|
282
|
+
expect(details.tags).toEqual(['CASE']);
|
|
283
|
+
expect(details.matches).toHaveLength(1);
|
|
254
284
|
expect(details.matches[0]).toMatchObject({
|
|
255
285
|
tag: 'CASE',
|
|
256
286
|
pattern: 'expect\\(received\\)\\.toBe',
|
|
257
287
|
source: 'stdio'
|
|
258
288
|
});
|
|
259
|
-
expect(details.matches[1]).toMatchObject({
|
|
260
|
-
tag: 'CODE',
|
|
261
|
-
pattern: null,
|
|
262
|
-
source: 'case-implied'
|
|
263
|
-
});
|
|
264
|
-
expect(details.matches[1].text).toContain('expect(received).toBe(expected)');
|
|
265
289
|
} finally {
|
|
266
290
|
casePatterns.CASE_PATTERNS.length = 0;
|
|
267
291
|
casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
|
|
268
292
|
}
|
|
269
293
|
});
|
|
270
|
-
test('
|
|
294
|
+
test('resolves to CODE when a runtime-log code match outranks a stdio case match', () => {
|
|
271
295
|
const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
|
|
272
296
|
try {
|
|
273
297
|
casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
|
|
@@ -288,13 +312,14 @@ describe('classifyFailureDetails', () => {
|
|
|
288
312
|
text: 'Error: expect(received).toBe(expected)'
|
|
289
313
|
}]
|
|
290
314
|
}]
|
|
291
|
-
}], 'case-with-runtime-code'
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
expect(details.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
expect(details.matches
|
|
315
|
+
}], 'case-with-runtime-code');
|
|
316
|
+
|
|
317
|
+
// CODE outranks CASE, so only CODE surfaces as the tag and as matches.
|
|
318
|
+
expect(details.tags).toEqual(['CODE']);
|
|
319
|
+
expect(details.matches).toHaveLength(1);
|
|
320
|
+
expect(details.matches.find(match => match.tag === 'CASE')).toBeUndefined();
|
|
321
|
+
expect(details.matches[0]).toMatchObject({
|
|
322
|
+
tag: 'CODE',
|
|
298
323
|
source: 'runtime-log',
|
|
299
324
|
pattern: 'Cannot read properties of (undefined|null)'
|
|
300
325
|
});
|
|
@@ -303,7 +328,7 @@ describe('classifyFailureDetails', () => {
|
|
|
303
328
|
casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
|
|
304
329
|
}
|
|
305
330
|
});
|
|
306
|
-
test('
|
|
331
|
+
test('keeps case as the tag and does not add code when no runtime log is present', () => {
|
|
307
332
|
const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
|
|
308
333
|
try {
|
|
309
334
|
casePatterns.CASE_PATTERNS.push(/Timed out \d+ms waiting for/i);
|
|
@@ -311,17 +336,174 @@ describe('classifyFailureDetails', () => {
|
|
|
311
336
|
stderr: [{
|
|
312
337
|
text: 'Timed out 5000ms waiting for locator("#save")'
|
|
313
338
|
}]
|
|
314
|
-
})], TEST_TITLE
|
|
315
|
-
expect(details.tags).toEqual(['CASE'
|
|
316
|
-
expect(details.matches.find(match => match.tag === 'CODE')).
|
|
317
|
-
source: 'case-implied',
|
|
318
|
-
pattern: null
|
|
319
|
-
});
|
|
339
|
+
})], TEST_TITLE);
|
|
340
|
+
expect(details.tags).toEqual(['CASE']);
|
|
341
|
+
expect(details.matches.find(match => match.tag === 'CODE')).toBeUndefined();
|
|
320
342
|
} finally {
|
|
321
343
|
casePatterns.CASE_PATTERNS.length = 0;
|
|
322
344
|
casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
|
|
323
345
|
}
|
|
324
346
|
});
|
|
347
|
+
test('records every CODE pattern hit from the runtime log', () => {
|
|
348
|
+
const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
|
|
349
|
+
kind: 'pageerror',
|
|
350
|
+
name: 'TypeError',
|
|
351
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
352
|
+
}, {
|
|
353
|
+
kind: 'pageerror',
|
|
354
|
+
name: 'TypeError',
|
|
355
|
+
message: 'foo is not a function'
|
|
356
|
+
}, {
|
|
357
|
+
kind: 'console',
|
|
358
|
+
level: 'error',
|
|
359
|
+
text: 'bar is not defined',
|
|
360
|
+
location: {
|
|
361
|
+
url: 'https://app/page'
|
|
362
|
+
}
|
|
363
|
+
}]);
|
|
364
|
+
expect(details.tags).toEqual(['CODE']);
|
|
365
|
+
expect(details.matches.filter(match => match.tag === 'CODE')).toHaveLength(3);
|
|
366
|
+
expect(details.matches.map(match => match.pattern)).toEqual(expect.arrayContaining(['Cannot read properties of (undefined|null)', '\\bis not a function\\b', '\\bis not defined\\b']));
|
|
367
|
+
});
|
|
368
|
+
test('keeps only winning CODE matches when CODE and ENVIRONMENT both match', () => {
|
|
369
|
+
const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetail([{
|
|
370
|
+
kind: 'requestfailed',
|
|
371
|
+
url: 'https://app/events/list/commentsubtabevent',
|
|
372
|
+
failure: 'net::ERR_ABORTED',
|
|
373
|
+
resourceType: 'document'
|
|
374
|
+
}, {
|
|
375
|
+
kind: 'requestfailed',
|
|
376
|
+
url: 'https://cdn/app.js',
|
|
377
|
+
failure: 'net::ERR_CONNECTION_REFUSED',
|
|
378
|
+
resourceType: 'script'
|
|
379
|
+
}, {
|
|
380
|
+
kind: 'pageerror',
|
|
381
|
+
name: 'TypeError',
|
|
382
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
383
|
+
}, {
|
|
384
|
+
kind: 'pageerror',
|
|
385
|
+
name: 'TypeError',
|
|
386
|
+
message: 'handler is not a function'
|
|
387
|
+
}], {
|
|
388
|
+
title: 'multi-code-env-test'
|
|
389
|
+
})], 'multi-code-env-test');
|
|
390
|
+
expect(details.tags).toEqual(['CODE']);
|
|
391
|
+
expect(details.matches).toHaveLength(2);
|
|
392
|
+
expect(details.matches.every(match => match.tag === 'CODE')).toBe(true);
|
|
393
|
+
expect(details.matches.find(match => match.tag === 'ENVIRONMENT')).toBeUndefined();
|
|
394
|
+
});
|
|
395
|
+
test('dedupes identical winning-category matches', () => {
|
|
396
|
+
const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
|
|
397
|
+
kind: 'pageerror',
|
|
398
|
+
name: 'TypeError',
|
|
399
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
400
|
+
}, {
|
|
401
|
+
kind: 'pageerror',
|
|
402
|
+
name: 'TypeError',
|
|
403
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
404
|
+
}, {
|
|
405
|
+
kind: 'pageerror',
|
|
406
|
+
name: 'TypeError',
|
|
407
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
408
|
+
}, {
|
|
409
|
+
kind: 'pageerror',
|
|
410
|
+
name: 'TypeError',
|
|
411
|
+
message: 'a is not a function'
|
|
412
|
+
}, {
|
|
413
|
+
kind: 'pageerror',
|
|
414
|
+
name: 'TypeError',
|
|
415
|
+
message: 'e.apply is not a function'
|
|
416
|
+
}, {
|
|
417
|
+
kind: 'pageerror',
|
|
418
|
+
name: 'TypeError',
|
|
419
|
+
message: 'a is not a function'
|
|
420
|
+
}]);
|
|
421
|
+
expect(details.tags).toEqual(['CODE']);
|
|
422
|
+
expect(details.matches).toHaveLength(3);
|
|
423
|
+
expect(details.matches.map(match => match.text)).toEqual(["pageerror TypeError Cannot read properties of undefined (reading 'id')", 'pageerror TypeError a is not a function', 'pageerror TypeError e.apply is not a function']);
|
|
424
|
+
});
|
|
425
|
+
test('dedupes annotated dual-match JSONL lines before classifying from disk', () => {
|
|
426
|
+
writeRuntimeLog([{
|
|
427
|
+
pattern: 'CODE',
|
|
428
|
+
kind: 'pageerror',
|
|
429
|
+
name: 'TypeError',
|
|
430
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
431
|
+
}, {
|
|
432
|
+
pattern: 'ENVIRONMENT',
|
|
433
|
+
kind: 'pageerror',
|
|
434
|
+
name: 'TypeError',
|
|
435
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
436
|
+
}], {
|
|
437
|
+
title: 'annotated-dual-test'
|
|
438
|
+
});
|
|
439
|
+
const details = (0, _failureTagClassifier.classifyFailureDetails)([{
|
|
440
|
+
status: 'unexpected',
|
|
441
|
+
results: [{
|
|
442
|
+
status: 'failed',
|
|
443
|
+
workerIndex: 0,
|
|
444
|
+
retry: 0
|
|
445
|
+
}]
|
|
446
|
+
}], 'annotated-dual-test');
|
|
447
|
+
expect(details.tags).toEqual(['CODE']);
|
|
448
|
+
expect(details.matches.filter(match => match.tag === 'CODE')).toHaveLength(1);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
describe('annotateRuntimeLogEvents', () => {
|
|
452
|
+
test('adds pattern CODE on matching events and leaves unmatched events unchanged', () => {
|
|
453
|
+
const lines = (0, _runtimeLogAnnotator.annotateRuntimeLogEvents)([{
|
|
454
|
+
ts: 1,
|
|
455
|
+
kind: 'pageerror',
|
|
456
|
+
name: 'TypeError',
|
|
457
|
+
message: "Cannot read properties of undefined (reading 'id')"
|
|
458
|
+
}, {
|
|
459
|
+
ts: 2,
|
|
460
|
+
kind: 'console',
|
|
461
|
+
level: 'error',
|
|
462
|
+
text: 'Some benign log line',
|
|
463
|
+
location: {
|
|
464
|
+
url: 'https://app/page'
|
|
465
|
+
}
|
|
466
|
+
}]);
|
|
467
|
+
expect(lines).toHaveLength(2);
|
|
468
|
+
expect(lines[0]).toMatchObject({
|
|
469
|
+
pattern: 'CODE',
|
|
470
|
+
ts: 1,
|
|
471
|
+
kind: 'pageerror'
|
|
472
|
+
});
|
|
473
|
+
expect(lines[1]).toEqual({
|
|
474
|
+
ts: 2,
|
|
475
|
+
kind: 'console',
|
|
476
|
+
level: 'error',
|
|
477
|
+
text: 'Some benign log line',
|
|
478
|
+
location: {
|
|
479
|
+
url: 'https://app/page'
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
expect(lines[1].pattern).toBeUndefined();
|
|
483
|
+
});
|
|
484
|
+
test('emits one line per matched category when an event hits CODE and ENVIRONMENT', () => {
|
|
485
|
+
const originalCode = codePatterns.CODE_PATTERNS.slice();
|
|
486
|
+
try {
|
|
487
|
+
// Force a dual match on one environment event by also treating ERR_ABORTED as CODE.
|
|
488
|
+
codePatterns.CODE_PATTERNS.push(/ERR_ABORTED/i);
|
|
489
|
+
const event = {
|
|
490
|
+
ts: 1783961014561,
|
|
491
|
+
kind: 'requestfailed',
|
|
492
|
+
url: 'https://app/events/list/commentsubtabevent',
|
|
493
|
+
failure: 'net::ERR_ABORTED',
|
|
494
|
+
resourceType: 'document',
|
|
495
|
+
method: 'GET'
|
|
496
|
+
};
|
|
497
|
+
const lines = (0, _runtimeLogAnnotator.annotateRuntimeLogEvents)([event]);
|
|
498
|
+
expect(lines).toHaveLength(2);
|
|
499
|
+
expect(lines.map(line => line.pattern).sort()).toEqual(['CODE', 'ENVIRONMENT']);
|
|
500
|
+
expect(lines[0].ts).toBe(1783961014561);
|
|
501
|
+
expect(lines[1].ts).toBe(1783961014561);
|
|
502
|
+
} finally {
|
|
503
|
+
codePatterns.CODE_PATTERNS.length = 0;
|
|
504
|
+
codePatterns.CODE_PATTERNS.push(...originalCode);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
325
507
|
});
|
|
326
508
|
describe('failure tag metadata attachment', () => {
|
|
327
509
|
test('builds a small JSON attachment when tags are present', () => {
|
|
@@ -376,8 +558,8 @@ describe('mergeFailureTags', () => {
|
|
|
376
558
|
test('places suffixed failure tags first, then existing tags', () => {
|
|
377
559
|
expect((0, _failureTagClassifier.mergeFailureTags)(['@comments', '@calls'], ['CODE', 'ENVIRONMENT'])).toEqual(['CODE_failure', 'ENVIRONMENT_failure', '@comments', '@calls']);
|
|
378
560
|
});
|
|
379
|
-
test('
|
|
380
|
-
expect((0, _failureTagClassifier.mergeFailureTags)([], ['
|
|
561
|
+
test('formats all failure tags with suffix', () => {
|
|
562
|
+
expect((0, _failureTagClassifier.mergeFailureTags)([], ['CODE', 'TOOL'])).toEqual(['CODE_failure', 'TOOL_failure']);
|
|
381
563
|
});
|
|
382
564
|
test('does not duplicate an already-present failure tag', () => {
|
|
383
565
|
expect((0, _failureTagClassifier.mergeFailureTags)(['CODE_failure'], ['CODE'])).toEqual(['CODE_failure']);
|
|
@@ -399,8 +581,8 @@ describe('regex safety', () => {
|
|
|
399
581
|
text: 'status : DATA_GENERATION_ERROR'
|
|
400
582
|
}]
|
|
401
583
|
});
|
|
402
|
-
const first = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
403
|
-
const second = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE
|
|
584
|
+
const first = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
585
|
+
const second = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
|
|
404
586
|
expect(first).toEqual(['TOOL']);
|
|
405
587
|
expect(second).toEqual(['TOOL']);
|
|
406
588
|
} finally {
|
|
@@ -114,10 +114,34 @@ describe('LogCollector baseline-noise stripping', () => {
|
|
|
114
114
|
});
|
|
115
115
|
const filePath = collector.flushToFile(flushDir, 'w0-test-0');
|
|
116
116
|
const contents = _fs.default.readFileSync(filePath, 'utf8');
|
|
117
|
+
const line = JSON.parse(contents.trim());
|
|
117
118
|
expect(filePath).toContain('w0-test-0.jsonl');
|
|
118
|
-
expect(
|
|
119
|
+
expect(line).toMatchObject({
|
|
120
|
+
pattern: 'CODE',
|
|
121
|
+
kind: 'pageerror'
|
|
122
|
+
});
|
|
119
123
|
expect(contents).toContain("Cannot read properties of undefined (reading 'id')");
|
|
120
124
|
expect(contents.endsWith('\n')).toBe(true);
|
|
125
|
+
// In-memory events stay unannotated.
|
|
126
|
+
expect(collector.events[0].pattern).toBeUndefined();
|
|
127
|
+
} finally {
|
|
128
|
+
_fs.default.rmSync(flushDir, {
|
|
129
|
+
recursive: true,
|
|
130
|
+
force: true
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
test('flushes ENVIRONMENT pattern annotation for document aborts', () => {
|
|
135
|
+
const flushDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'log-collector-flush-'));
|
|
136
|
+
try {
|
|
137
|
+
context.emit('requestfailed', failedRequest('https://app/agent/events/list/commentsubtabevent', 'net::ERR_ABORTED', 'document'));
|
|
138
|
+
const filePath = collector.flushToFile(flushDir, 'w0-env-0');
|
|
139
|
+
const line = JSON.parse(_fs.default.readFileSync(filePath, 'utf8').trim());
|
|
140
|
+
expect(line).toMatchObject({
|
|
141
|
+
pattern: 'ENVIRONMENT',
|
|
142
|
+
kind: 'requestfailed',
|
|
143
|
+
resourceType: 'document'
|
|
144
|
+
});
|
|
121
145
|
} finally {
|
|
122
146
|
_fs.default.rmSync(flushDir, {
|
|
123
147
|
recursive: true,
|