@zohodesk/testinglibrary 0.0.83-n20-experimental → 0.0.85-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.
@@ -1,23 +1,41 @@
1
1
  "use strict";
2
2
 
3
- var _logCollectorConstants = require("../../../../../core/playwright/constants/logCollectorConstants");
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _fs = _interopRequireDefault(require("fs"));
5
+ var _os = _interopRequireDefault(require("os"));
6
+ var _path = _interopRequireDefault(require("path"));
4
7
  var toolPatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/toolPatterns"));
8
+ var casePatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/casePatterns"));
9
+ var _runtimeLogPath = require("../../../../../core/playwright/helpers/runtimeLogPath");
5
10
  var _failureTagClassifier = require("../../../../../core/playwright/helpers/failureTagClassifier");
11
+ var _failureTagConstants = require("../../../../../core/playwright/constants/failureTagConstants");
6
12
  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); }
7
- function failingDetail(events) {
8
- const attachments = [];
9
- if (events.length > 0) {
10
- attachments.push({
11
- name: _logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT,
12
- contentType: _logCollectorConstants.RUNTIME_LOG_EVENTS_CONTENT_TYPE,
13
- body: Buffer.from(`${events.map(e => JSON.stringify(e)).join('\n')}\n`)
14
- });
15
- }
13
+ const TEST_TITLE = 'environment-abort-test';
14
+ let tempDir;
15
+ function writeRuntimeLog(events, {
16
+ workerIndex = 0,
17
+ retry = 0,
18
+ title = TEST_TITLE
19
+ } = {}) {
20
+ const file = (0, _runtimeLogPath.resolveRuntimeLogFilePath)(tempDir, {
21
+ workerIndex,
22
+ retry,
23
+ title
24
+ });
25
+ _fs.default.mkdirSync(_path.default.dirname(file), {
26
+ recursive: true
27
+ });
28
+ _fs.default.writeFileSync(file, `${events.map(e => JSON.stringify(e)).join('\n')}\n`);
29
+ return file;
30
+ }
31
+ function failingDetail(events, options = {}) {
32
+ writeRuntimeLog(events, options);
16
33
  return {
17
34
  status: 'unexpected',
18
35
  results: [{
19
36
  status: 'failed',
20
- attachments
37
+ workerIndex: options.workerIndex ?? 0,
38
+ retry: options.retry ?? 0
21
39
  }]
22
40
  };
23
41
  }
@@ -35,12 +53,21 @@ function failingDetailWithStdio({
35
53
  }]
36
54
  };
37
55
  }
56
+ beforeEach(() => {
57
+ tempDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'failure-tag-'));
58
+ });
59
+ afterEach(() => {
60
+ _fs.default.rmSync(tempDir, {
61
+ recursive: true,
62
+ force: true
63
+ });
64
+ });
38
65
  describe('classifyFailureTags', () => {
39
66
  test('returns flaky and skips log analysis for flaky outcome', () => {
40
67
  const tags = (0, _failureTagClassifier.classifyFailureTags)([{
41
68
  status: 'flaky',
42
69
  results: []
43
- }]);
70
+ }], TEST_TITLE, tempDir);
44
71
  expect(tags).toEqual(['FLAKY']);
45
72
  });
46
73
  test('tags environment when a document abort is present', () => {
@@ -49,7 +76,7 @@ describe('classifyFailureTags', () => {
49
76
  url: 'https://app/events/list/commentsubtabevent',
50
77
  failure: 'net::ERR_ABORTED',
51
78
  resourceType: 'document'
52
- }])]);
79
+ }])], TEST_TITLE, tempDir);
53
80
  expect(tags).toEqual(['ENVIRONMENT']);
54
81
  });
55
82
  test('tags environment for a network/cert failure', () => {
@@ -58,7 +85,9 @@ describe('classifyFailureTags', () => {
58
85
  url: 'https://js/gshandler.js',
59
86
  failure: 'net::ERR_CERT_COMMON_NAME_INVALID',
60
87
  resourceType: 'script'
61
- }])]);
88
+ }], {
89
+ title: 'cert-test'
90
+ })], 'cert-test', tempDir);
62
91
  expect(tags).toEqual(['ENVIRONMENT']);
63
92
  });
64
93
  test('tags code when an app undefined-property error is present', () => {
@@ -66,7 +95,9 @@ describe('classifyFailureTags', () => {
66
95
  kind: 'pageerror',
67
96
  name: 'TypeError',
68
97
  message: "Cannot read properties of undefined (reading 'id')"
69
- }])]);
98
+ }], {
99
+ title: 'code-test'
100
+ })], 'code-test', tempDir);
70
101
  expect(tags).toEqual(['CODE']);
71
102
  });
72
103
  test('does not tag a non-document (xhr) abort as environment', () => {
@@ -75,27 +106,45 @@ describe('classifyFailureTags', () => {
75
106
  url: 'https://app/api/tickets',
76
107
  failure: 'net::ERR_ABORTED',
77
108
  resourceType: 'xhr'
78
- }])]);
109
+ }], {
110
+ title: 'xhr-test'
111
+ })], 'xhr-test', tempDir);
79
112
  expect(tags).toEqual([]);
80
113
  });
81
- test('tags tool when stderr reports a DATA_GENERATION_ERROR', () => {
114
+ test('tags case and code when stderr reports DATA_GENERATION_ERROR', () => {
82
115
  const detail = failingDetailWithStdio({
83
116
  stderr: [{
84
117
  text: 'status : DATA_GENERATION_ERROR\n generator: comments'
85
118
  }]
86
119
  });
87
- const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
88
- expect(tags).toEqual(['TOOL']);
120
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
121
+ expect(tags).toEqual(['CASE', 'CODE']);
89
122
  });
90
- test('tags tool for an "Error Type:" line in base64 stdout', () => {
123
+ test('tags tool for a known tool pattern in base64 stdout', () => {
91
124
  const detail = failingDetailWithStdio({
92
125
  stdout: [{
93
- buffer: Buffer.from('Error Type: Error').toString('base64')
126
+ buffer: Buffer.from('Error during login').toString('base64')
94
127
  }]
95
128
  });
96
- const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
129
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
97
130
  expect(tags).toEqual(['TOOL']);
98
131
  });
132
+ test('tags case and code when stderr matches a case pattern', () => {
133
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
134
+ try {
135
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
136
+ const detail = failingDetailWithStdio({
137
+ stderr: [{
138
+ text: 'Error: expect(received).toBe(expected)'
139
+ }]
140
+ });
141
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
142
+ expect(tags).toEqual(['CASE', 'CODE']);
143
+ } finally {
144
+ casePatterns.CASE_PATTERNS.length = 0;
145
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
146
+ }
147
+ });
99
148
  test('does not tag tool for benign stdout/stderr', () => {
100
149
  const detail = failingDetailWithStdio({
101
150
  stdout: [{
@@ -103,7 +152,7 @@ describe('classifyFailureTags', () => {
103
152
  }],
104
153
  stderr: []
105
154
  });
106
- const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
155
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
107
156
  expect(tags).toEqual([]);
108
157
  });
109
158
  test('does not throw for malformed stdio chunk entries', () => {
@@ -113,8 +162,8 @@ describe('classifyFailureTags', () => {
113
162
  }],
114
163
  stderr: [false]
115
164
  });
116
- expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail])).not.toThrow();
117
- expect((0, _failureTagClassifier.classifyFailureTags)([detail])).toEqual([]);
165
+ expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir)).not.toThrow();
166
+ expect((0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir)).toEqual([]);
118
167
  });
119
168
  test('leaves untagged when no environment or code pattern is present', () => {
120
169
  const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
@@ -129,10 +178,200 @@ describe('classifyFailureTags', () => {
129
178
  location: {
130
179
  url: 'https://app/page'
131
180
  }
132
- }])]);
181
+ }], {
182
+ title: 'neutral-test'
183
+ })], 'neutral-test', tempDir);
133
184
  expect(tags).toEqual([]);
134
185
  });
135
186
  });
187
+ describe('classifyFailureDetails', () => {
188
+ test('records the matching pattern for environment failures', () => {
189
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetail([{
190
+ kind: 'requestfailed',
191
+ url: 'https://app/events/list/commentsubtabevent',
192
+ failure: 'net::ERR_ABORTED',
193
+ resourceType: 'document'
194
+ }])], TEST_TITLE, tempDir);
195
+ expect(details.tags).toEqual(['ENVIRONMENT']);
196
+ expect(details.matches).toHaveLength(1);
197
+ expect(details.matches[0]).toMatchObject({
198
+ tag: 'ENVIRONMENT',
199
+ pattern: '^requestfailed document\\b[\\s\\S]*ERR_ABORTED',
200
+ source: 'runtime-log'
201
+ });
202
+ expect(details.matches[0].text).toContain('ERR_ABORTED');
203
+ });
204
+ test('records the matching pattern for code failures from events', () => {
205
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
206
+ kind: 'pageerror',
207
+ name: 'TypeError',
208
+ message: "Cannot read properties of undefined (reading 'id')"
209
+ }]);
210
+ expect(details.tags).toEqual(['CODE']);
211
+ expect(details.matches[0]).toMatchObject({
212
+ tag: 'CODE',
213
+ pattern: 'Cannot read properties of (undefined|null)',
214
+ source: 'runtime-log'
215
+ });
216
+ });
217
+ test('records flaky outcome without a regex pattern', () => {
218
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([{
219
+ status: 'flaky',
220
+ results: []
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
+ });
229
+ });
230
+ test('records tool pattern from stdio', () => {
231
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
232
+ stderr: [{
233
+ text: 'Error during login for actor admin'
234
+ }]
235
+ })], TEST_TITLE, tempDir);
236
+ expect(details.tags).toEqual(['TOOL']);
237
+ expect(details.matches[0]).toMatchObject({
238
+ tag: 'TOOL',
239
+ pattern: 'Error during login',
240
+ source: 'stdio'
241
+ });
242
+ });
243
+ test('records case pattern from stdio and implies code', () => {
244
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
245
+ try {
246
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
247
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
248
+ stderr: [{
249
+ text: 'Error: expect(received).toBe(expected)'
250
+ }]
251
+ })], TEST_TITLE, tempDir);
252
+ expect(details.tags).toEqual(['CASE', 'CODE']);
253
+ expect(details.matches).toHaveLength(2);
254
+ expect(details.matches[0]).toMatchObject({
255
+ tag: 'CASE',
256
+ pattern: 'expect\\(received\\)\\.toBe',
257
+ source: 'stdio'
258
+ });
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
+ } finally {
266
+ casePatterns.CASE_PATTERNS.length = 0;
267
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
268
+ }
269
+ });
270
+ test('keeps runtime-log code match when case also matches', () => {
271
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
272
+ try {
273
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
274
+ writeRuntimeLog([{
275
+ kind: 'pageerror',
276
+ name: 'TypeError',
277
+ message: "Cannot read properties of undefined (reading 'id')"
278
+ }], {
279
+ title: 'case-with-runtime-code'
280
+ });
281
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([{
282
+ status: 'unexpected',
283
+ results: [{
284
+ status: 'failed',
285
+ workerIndex: 0,
286
+ retry: 0,
287
+ stderr: [{
288
+ text: 'Error: expect(received).toBe(expected)'
289
+ }]
290
+ }]
291
+ }], 'case-with-runtime-code', tempDir);
292
+ expect(details.tags).toEqual(['CASE', 'CODE']);
293
+ expect(details.matches).toHaveLength(2);
294
+ expect(details.matches.find(match => match.tag === 'CASE')).toMatchObject({
295
+ source: 'stdio'
296
+ });
297
+ expect(details.matches.find(match => match.tag === 'CODE')).toMatchObject({
298
+ source: 'runtime-log',
299
+ pattern: 'Cannot read properties of (undefined|null)'
300
+ });
301
+ } finally {
302
+ casePatterns.CASE_PATTERNS.length = 0;
303
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
304
+ }
305
+ });
306
+ test('implies code from case when no runtime log is present', () => {
307
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
308
+ try {
309
+ casePatterns.CASE_PATTERNS.push(/Timed out \d+ms waiting for/i);
310
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
311
+ stderr: [{
312
+ text: 'Timed out 5000ms waiting for locator("#save")'
313
+ }]
314
+ })], TEST_TITLE, tempDir);
315
+ expect(details.tags).toEqual(['CASE', 'CODE']);
316
+ expect(details.matches.find(match => match.tag === 'CODE')).toMatchObject({
317
+ source: 'case-implied',
318
+ pattern: null
319
+ });
320
+ } finally {
321
+ casePatterns.CASE_PATTERNS.length = 0;
322
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
323
+ }
324
+ });
325
+ });
326
+ describe('failure tag metadata attachment', () => {
327
+ test('builds a small JSON attachment when tags are present', () => {
328
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
329
+ kind: 'pageerror',
330
+ name: 'TypeError',
331
+ message: "Cannot read properties of undefined (reading 'id')"
332
+ }]);
333
+ const attachment = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)(details);
334
+ expect(attachment).toMatchObject({
335
+ name: _failureTagConstants.FAILURE_TAG_METADATA_ATTACHMENT,
336
+ contentType: 'application/json'
337
+ });
338
+ expect(JSON.parse(attachment.body.toString('utf8'))).toEqual(details);
339
+ });
340
+ test('returns null when no tags are present', () => {
341
+ expect((0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
342
+ tags: [],
343
+ matches: []
344
+ })).toBeNull();
345
+ });
346
+ test('replaces an existing metadata attachment', () => {
347
+ const first = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
348
+ tags: ['CODE'],
349
+ matches: [{
350
+ tag: 'CODE',
351
+ pattern: 'old',
352
+ source: 'runtime-log',
353
+ text: 'old'
354
+ }]
355
+ });
356
+ const second = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
357
+ tags: ['TOOL'],
358
+ matches: [{
359
+ tag: 'TOOL',
360
+ pattern: 'new',
361
+ source: 'stdio',
362
+ text: 'new'
363
+ }]
364
+ });
365
+ const merged = (0, _failureTagClassifier.upsertFailureTagMetadataAttachment)([{
366
+ name: 'screenshot',
367
+ path: '/tmp/a.png'
368
+ }, first], JSON.parse(second.body.toString('utf8')));
369
+ expect(merged).toHaveLength(2);
370
+ expect(merged[0].name).toBe('screenshot');
371
+ expect(merged[1].name).toBe(_failureTagConstants.FAILURE_TAG_METADATA_ATTACHMENT);
372
+ expect(JSON.parse(merged[1].body.toString('utf8')).matches[0].pattern).toBe('new');
373
+ });
374
+ });
136
375
  describe('mergeFailureTags', () => {
137
376
  test('places suffixed failure tags first, then existing tags', () => {
138
377
  expect((0, _failureTagClassifier.mergeFailureTags)(['@comments', '@calls'], ['CODE', 'ENVIRONMENT'])).toEqual(['CODE_failure', 'ENVIRONMENT_failure', '@comments', '@calls']);
@@ -143,25 +382,32 @@ describe('mergeFailureTags', () => {
143
382
  test('does not duplicate an already-present failure tag', () => {
144
383
  expect((0, _failureTagClassifier.mergeFailureTags)(['CODE_failure'], ['CODE'])).toEqual(['CODE_failure']);
145
384
  });
385
+ test('formats case and code failure tags with suffix', () => {
386
+ expect((0, _failureTagClassifier.mergeFailureTags)(['@smoke'], ['CASE', 'CODE'])).toEqual(['CASE_failure', 'CODE_failure', '@smoke']);
387
+ });
146
388
  });
147
389
  describe('regex safety', () => {
148
390
  test('resets stateful regex for tool pattern checks', () => {
149
391
  const originalToolPatterns = toolPatterns.TOOL_PATTERNS.slice();
392
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
150
393
  try {
151
394
  toolPatterns.TOOL_PATTERNS.length = 0;
152
395
  toolPatterns.TOOL_PATTERNS.push(/DATA_GENERATION_ERROR/g);
396
+ casePatterns.CASE_PATTERNS.length = 0;
153
397
  const detail = failingDetailWithStdio({
154
398
  stderr: [{
155
399
  text: 'status : DATA_GENERATION_ERROR'
156
400
  }]
157
401
  });
158
- const first = (0, _failureTagClassifier.classifyFailureTags)([detail]);
159
- const second = (0, _failureTagClassifier.classifyFailureTags)([detail]);
402
+ const first = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
403
+ const second = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE, tempDir);
160
404
  expect(first).toEqual(['TOOL']);
161
405
  expect(second).toEqual(['TOOL']);
162
406
  } finally {
163
407
  toolPatterns.TOOL_PATTERNS.length = 0;
164
408
  toolPatterns.TOOL_PATTERNS.push(...originalToolPatterns);
409
+ casePatterns.CASE_PATTERNS.length = 0;
410
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
165
411
  }
166
412
  });
167
413
  });
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _fs = _interopRequireDefault(require("fs"));
5
+ var _os = _interopRequireDefault(require("os"));
6
+ var _path = _interopRequireDefault(require("path"));
3
7
  var _logCollector = require("../../../../../core/playwright/helpers/logCollector");
4
8
  function makeEmitter() {
5
9
  const handlers = {};
@@ -100,19 +104,29 @@ describe('LogCollector baseline-noise stripping', () => {
100
104
  name: 'TypeError'
101
105
  });
102
106
  });
103
- test('serializes captured events as jsonl', () => {
104
- page.emit('pageerror', {
105
- name: 'TypeError',
106
- message: "Cannot read properties of undefined (reading 'id')",
107
- stack: 'TypeError: ...'
108
- });
109
- const body = collector.toJsonlBody();
110
- expect(body).toContain('"kind":"pageerror"');
111
- expect(body).toContain("Cannot read properties of undefined (reading 'id')");
112
- expect(body === null || body === void 0 ? void 0 : body.endsWith('\n')).toBe(true);
107
+ test('flushes captured events to a jsonl file', () => {
108
+ const flushDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'log-collector-flush-'));
109
+ try {
110
+ page.emit('pageerror', {
111
+ name: 'TypeError',
112
+ message: "Cannot read properties of undefined (reading 'id')",
113
+ stack: 'TypeError: ...'
114
+ });
115
+ const filePath = collector.flushToFile(flushDir, 'w0-test-0');
116
+ const contents = _fs.default.readFileSync(filePath, 'utf8');
117
+ expect(filePath).toContain('w0-test-0.jsonl');
118
+ expect(contents).toContain('"kind":"pageerror"');
119
+ expect(contents).toContain("Cannot read properties of undefined (reading 'id')");
120
+ expect(contents.endsWith('\n')).toBe(true);
121
+ } finally {
122
+ _fs.default.rmSync(flushDir, {
123
+ recursive: true,
124
+ force: true
125
+ });
126
+ }
113
127
  });
114
- test('returns null from toJsonlBody when no events were captured', () => {
115
- expect(collector.toJsonlBody()).toBeNull();
128
+ test('returns null from flushToFile when no events were captured', () => {
129
+ expect(collector.flushToFile('/tmp', 'empty')).toBeNull();
116
130
  });
117
131
  test('keeps a document-navigation abort (real environment signal)', () => {
118
132
  context.emit('requestfailed', failedRequest('https://app/agent/events/list/commentsubtabevent', 'net::ERR_ABORTED', 'document'));
@@ -1,41 +1,103 @@
1
1
  "use strict";
2
2
 
3
- var _logCollectorConstants = require("../../../../../core/playwright/constants/logCollectorConstants");
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _fs = _interopRequireDefault(require("fs"));
5
+ var _os = _interopRequireDefault(require("os"));
6
+ var _path = _interopRequireDefault(require("path"));
7
+ var _runtimeLogPath = require("../../../../../core/playwright/helpers/runtimeLogPath");
4
8
  var _runtimeLogReader = require("../../../../../core/playwright/helpers/runtimeLogReader");
5
- function jsonlBody(events) {
6
- return Buffer.from(`${events.map(e => JSON.stringify(e)).join('\n')}\n`);
9
+ const TEST_TITLE = 'runtime-log-reader-test';
10
+ let tempDir;
11
+ function writeRuntimeLog(events, {
12
+ workerIndex = 0,
13
+ retry = 0,
14
+ title = TEST_TITLE
15
+ } = {}) {
16
+ const file = (0, _runtimeLogPath.resolveRuntimeLogFilePath)(tempDir, {
17
+ workerIndex,
18
+ retry,
19
+ title
20
+ });
21
+ _fs.default.mkdirSync(_path.default.dirname(file), {
22
+ recursive: true
23
+ });
24
+ _fs.default.writeFileSync(file, `${events.map(e => JSON.stringify(e)).join('\n')}\n`);
25
+ return file;
7
26
  }
8
- describe('readRuntimeLogEventsFromResult', () => {
9
- test('reads inline runtime-log-events body', () => {
10
- const inlineEvents = [{
27
+ beforeEach(() => {
28
+ tempDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'runtime-log-reader-'));
29
+ });
30
+ afterEach(() => {
31
+ _fs.default.rmSync(tempDir, {
32
+ recursive: true,
33
+ force: true
34
+ });
35
+ });
36
+ describe('readRuntimeLogEventsForResult', () => {
37
+ test('reads project-side runtime log for a failing result', () => {
38
+ const events = [{
11
39
  kind: 'pageerror',
12
40
  message: 'inline boom'
13
41
  }];
42
+ writeRuntimeLog(events);
14
43
  const result = {
15
- attachments: [{
16
- name: _logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT,
17
- contentType: _logCollectorConstants.RUNTIME_LOG_EVENTS_CONTENT_TYPE,
18
- body: jsonlBody(inlineEvents)
19
- }]
44
+ status: 'failed',
45
+ workerIndex: 0,
46
+ retry: 0
20
47
  };
21
- expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)(result)).toEqual(inlineEvents);
48
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, TEST_TITLE, tempDir)).toEqual(events);
22
49
  });
23
50
  test('skips malformed jsonl lines', () => {
24
- const raw = [JSON.stringify({
51
+ const file = writeRuntimeLog([]);
52
+ _fs.default.writeFileSync(file, [JSON.stringify({
25
53
  kind: 'console',
26
54
  text: 'ok'
27
55
  }), 'not-json', JSON.stringify({
28
56
  kind: 'pageerror',
29
57
  message: 'err'
30
- })].join('\n');
58
+ })].join('\n') + '\n');
59
+ const result = {
60
+ status: 'failed',
61
+ workerIndex: 0,
62
+ retry: 0
63
+ };
64
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, TEST_TITLE, tempDir)).toEqual([{
65
+ kind: 'console',
66
+ text: 'ok'
67
+ }, {
68
+ kind: 'pageerror',
69
+ message: 'err'
70
+ }]);
71
+ });
72
+ test('returns an empty array when the runtime log file is missing', () => {
73
+ const result = {
74
+ status: 'failed',
75
+ workerIndex: 0,
76
+ retry: 0
77
+ };
78
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, 'missing-test', tempDir)).toEqual([]);
79
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(null, TEST_TITLE, tempDir)).toEqual([]);
80
+ });
81
+ test('returns an empty array when logsDir is missing', () => {
31
82
  const result = {
32
- attachments: [{
33
- name: _logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT,
34
- contentType: _logCollectorConstants.RUNTIME_LOG_EVENTS_CONTENT_TYPE,
35
- body: Buffer.from(`${raw}\n`)
36
- }]
83
+ status: 'failed',
84
+ workerIndex: 0,
85
+ retry: 0
37
86
  };
38
- expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)(result)).toEqual([{
87
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, TEST_TITLE)).toEqual([]);
88
+ expect((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, TEST_TITLE, undefined)).toEqual([]);
89
+ });
90
+ });
91
+ describe('readRuntimeLogEvents', () => {
92
+ test('reads events from a jsonl file path', () => {
93
+ const file = writeRuntimeLog([{
94
+ kind: 'console',
95
+ text: 'ok'
96
+ }, {
97
+ kind: 'pageerror',
98
+ message: 'err'
99
+ }]);
100
+ expect((0, _runtimeLogReader.readRuntimeLogEvents)(file)).toEqual([{
39
101
  kind: 'console',
40
102
  text: 'ok'
41
103
  }, {
@@ -43,10 +105,8 @@ describe('readRuntimeLogEventsFromResult', () => {
43
105
  message: 'err'
44
106
  }]);
45
107
  });
46
- test('returns an empty array when runtime-log-events is missing', () => {
47
- expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)({
48
- attachments: []
49
- })).toEqual([]);
50
- expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)(null)).toEqual([]);
108
+ test('returns an empty array for a missing file', () => {
109
+ expect((0, _runtimeLogReader.readRuntimeLogEvents)(null)).toEqual([]);
110
+ expect((0, _runtimeLogReader.readRuntimeLogEvents)(_path.default.join(tempDir, 'missing.jsonl'))).toEqual([]);
51
111
  });
52
112
  });