@zohodesk/testinglibrary 0.0.81-n20-experimental → 0.0.83-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.
@@ -25,20 +25,20 @@ class ActorContext {
25
25
  };
26
26
  const additionalActors = (0, _additionalProfiles.additionProfiles)($tags);
27
27
  await Promise.all(Object.entries(additionalActors).map(async ([role, actorInfo]) => {
28
- const additionalContext = await browser.newContext();
29
- const additionalPage = await additionalContext.newPage();
30
- const ctxTestDetails = {
31
- page: additionalPage,
28
+ let context = await browser.newContext();
29
+ let page = await context.newPage();
30
+ let ctxTestDetails = {
31
+ page,
32
32
  $tags,
33
- context: additionalContext,
33
+ context,
34
34
  ...actorInfo
35
35
  };
36
- await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(additionalContext, testInfo, ctxTestDetails, actorInfo);
36
+ await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(context, testInfo, ctxTestDetails, actorInfo);
37
37
  this.actorsObj[role] = {
38
38
  role,
39
39
  browser,
40
- context: additionalContext,
41
- page: additionalPage,
40
+ context,
41
+ page,
42
42
  executionContext: {
43
43
  actorInfo
44
44
  }
@@ -69,10 +69,7 @@ var _default = exports.default = {
69
69
  }, use, testInfo) => {
70
70
  const ctxObject = new ActorContext();
71
71
  await ctxObject.setup(browser, $tags, testInfo, context, page, executionContext);
72
- try {
73
- await use(ctxObject);
74
- } finally {
75
- await ctxObject.cleanup();
76
- }
72
+ await use(ctxObject);
73
+ await ctxObject.cleanup();
77
74
  }
78
75
  };
@@ -1,28 +1,16 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.default = void 0;
8
- var _path = _interopRequireDefault(require("path"));
9
7
  var _readConfigFile = require("../readConfigFile");
8
+ var _logCollectorConstants = require("../constants/logCollectorConstants");
10
9
  var _logCollector = require("../helpers/logCollector");
11
10
  const {
12
11
  testSetup,
13
- uatDirectory,
14
12
  browserLogs = {}
15
13
  } = (0, _readConfigFile.generateConfigFromFile)();
16
-
17
- // Keep runtime logs OUTSIDE reportPath (HTML reporter wipes it on onEnd) and
18
- // outside test-results (Playwright outputDir is cleaned each run).
19
- const LOGS_DIR = _path.default.join(uatDirectory, 'runtime-logs');
20
- const MAX_FILENAME_LENGTH = 120;
21
- function sanitizeForFilename(name) {
22
- if (!name) return 'test';
23
- const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, MAX_FILENAME_LENGTH);
24
- return cleaned || 'test';
25
- }
26
14
  async function performDefaultContextSteps({
27
15
  context
28
16
  }) {
@@ -44,28 +32,20 @@ var _default = exports.default = {
44
32
  });
45
33
  const collector = new _logCollector.LogCollector(browserLogs.limits);
46
34
  collector.attachToContext(context);
47
- testInfo.runtimeLogs = collector;
48
35
  try {
49
36
  await use(context);
50
37
  } finally {
51
38
  collector.detach();
52
39
  const failureOnly = browserLogs.mode === 'failure-only';
53
40
  const isFailure = testInfo.status && testInfo.status !== testInfo.expectedStatus;
54
- const shouldFlush = !failureOnly || isFailure;
55
- const summary = collector.toSummary();
56
- let filePath = null;
57
- if (shouldFlush) {
58
- const base = `w${testInfo.workerIndex}-${sanitizeForFilename(testInfo.title)}-${testInfo.retry}`;
59
- filePath = collector.flushToFile(LOGS_DIR, base);
41
+ const shouldAttach = !failureOnly || isFailure;
42
+ const logBody = shouldAttach ? collector.toJsonlBody() : null;
43
+ if (logBody) {
44
+ await testInfo.attach(_logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT, {
45
+ body: logBody,
46
+ contentType: _logCollectorConstants.RUNTIME_LOG_EVENTS_CONTENT_TYPE
47
+ });
60
48
  }
61
- await testInfo.attach('runtime-log', {
62
- contentType: 'application/json',
63
- body: Buffer.from(JSON.stringify({
64
- schemaVersion: 1,
65
- summary,
66
- file: filePath
67
- }))
68
- });
69
49
  }
70
50
  }
71
51
  };
@@ -32,6 +32,7 @@ var _default = exports.default = {
32
32
  } finally {
33
33
  await (0, _loginDefaultStepsHelper.performDefaultPageSteps)(testDetails);
34
34
  await use(page);
35
+ await context.close();
35
36
  }
36
37
  }
37
38
  };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.FAILURE_TAG_SUFFIX = exports.FAILURE_TAGS = void 0;
7
+ const FAILURE_TAGS = exports.FAILURE_TAGS = {
8
+ CODE: 'CODE',
9
+ ENVIRONMENT: 'ENVIRONMENT',
10
+ FLAKY: 'FLAKY',
11
+ TOOL: 'TOOL'
12
+ };
13
+
14
+ // Suffix appended to a category when merging into the existing spec.tags array,
15
+ // e.g. "CODE" -> "CODE_failure". The flaky category is emitted as-is ("FLAKY").
16
+ const FAILURE_TAG_SUFFIX = exports.FAILURE_TAG_SUFFIX = '_failure';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RUNTIME_LOG_EVENTS_CONTENT_TYPE = exports.RUNTIME_LOG_EVENTS_ATTACHMENT = exports.IGNORE_URL_PATTERNS = exports.IGNORE_MESSAGE_PATTERNS = void 0;
7
+ // Ignore patterns for the runtime-log LogCollector. Matching events are dropped here so it is never recorded.
8
+
9
+ const RUNTIME_LOG_EVENTS_ATTACHMENT = exports.RUNTIME_LOG_EVENTS_ATTACHMENT = 'runtime-log-events';
10
+ const RUNTIME_LOG_EVENTS_CONTENT_TYPE = exports.RUNTIME_LOG_EVENTS_CONTENT_TYPE = 'text/plain';
11
+
12
+ // Matched against console-error text, pageerror messages and requestfailed
13
+
14
+ const IGNORE_MESSAGE_PATTERNS = exports.IGNORE_MESSAGE_PATTERNS = [/Access is denied for this document/i, /enableDarkTheme is not a function/i, /enableZohoSearchAskZia is not a function/i, /enableSMSBridge is not a function/i, /Unexpected token '<'[\s\S]*not valid JSON/i, /blocked by CORS policy/i, /ERR_BLOCKED_BY_ORB/i];
15
+
16
+ // Matched against request/response/requestfailed URLs and console-error source URLs.
17
+
18
+ const IGNORE_URL_PATTERNS = exports.IGNORE_URL_PATTERNS = [/\.(png|jpe?g|gif|svg|webp|woff2?|ttf|css|map)(\?|$)/i, /google-analytics\.com|googletagmanager\.com|sentry\.io/i, /getHamburgerMenu\.do/i, /_chat\/unreadzmsg\.do/i, /_chat\/chatbarsync\.do/i, /agentAvailabilityConfig/i, /notifyAvailabilityStatus/i, /murphysdk/i, /phonebridge/i, /inproductsdk/i, /zquartz-tracker/i, /zohosearch\/.*gshandler/i, /stratuscdn\.com/i, /zohochat/i];
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.classifyFailureTags = classifyFailureTags;
7
+ exports.mergeFailureTags = mergeFailureTags;
8
+ var _failureTagConstants = require("../constants/failureTagConstants");
9
+ var _patterns = require("../patterns");
10
+ var _runtimeLogReader = require("./runtimeLogReader");
11
+ // A failing test is tagged purely on presence: if its runtime log contains an
12
+ // environment pattern it gets `ENVIRONMENT`, if it contains a code pattern it
13
+ // gets `CODE`, and if its stdout/stderr contains a tool pattern it gets `TOOL`
14
+ // (any may apply). `FLAKY` comes from the Playwright retry outcome, not the log.
15
+
16
+ const FAILING_RESULT_STATUSES = new Set(['failed', 'timedOut', 'interrupted']);
17
+ // Playwright TestCase.outcome() value for a failing (non-flaky) test.
18
+ const UNEXPECTED_OUTCOME = 'unexpected';
19
+ const FLAKY_OUTCOME = 'flaky';
20
+
21
+ // Flattens a runtime-log event into a single string for pattern matching. Field
22
+ // order is fixed so patterns can rely on it (e.g. resourceType before failure).
23
+ function eventToText(event) {
24
+ var _event$location;
25
+ switch (event.kind) {
26
+ case 'pageerror':
27
+ return `pageerror ${event.name || ''} ${event.message || ''}`;
28
+ case 'console':
29
+ return `console ${event.text || ''} ${((_event$location = event.location) === null || _event$location === void 0 ? void 0 : _event$location.url) || ''}`;
30
+ case 'requestfailed':
31
+ return `requestfailed ${event.resourceType || ''} ${event.method || ''} ${event.url || ''} ${event.failure || ''}`;
32
+ case 'response':
33
+ return `response ${event.status || ''} ${event.method || ''} ${event.url || ''}`;
34
+ default:
35
+ return JSON.stringify(event);
36
+ }
37
+ }
38
+
39
+ // True when any event matches any of the given patterns.
40
+ function matchesAny(events, patterns) {
41
+ return events.some(event => {
42
+ const text = eventToText(event);
43
+ return patterns.some(pattern => {
44
+ if (pattern.global || pattern.sticky) {
45
+ pattern.lastIndex = 0;
46
+ }
47
+ return pattern.test(text);
48
+ });
49
+ });
50
+ }
51
+
52
+ // Flattens a result's stdout/stderr chunks into a single string. Each chunk is
53
+ // either a plain string, { text } or { buffer } (base64) as produced by the
54
+ // reporter's stdioEntry.
55
+ function stdioToText(entries = []) {
56
+ return entries.map(entry => {
57
+ if (typeof entry === 'string') {
58
+ return entry;
59
+ }
60
+ if (!entry || typeof entry !== 'object') {
61
+ return '';
62
+ }
63
+ if (entry.text) {
64
+ return entry.text;
65
+ }
66
+ if (entry.buffer) {
67
+ try {
68
+ return Buffer.from(entry.buffer, 'base64').toString('utf8');
69
+ } catch {
70
+ return '';
71
+ }
72
+ }
73
+ return '';
74
+ }).join('\n');
75
+ }
76
+
77
+ // Reads a failing result and reports which categories are present. environment
78
+ // and code come from the browser runtime log; tool comes from stdout/stderr.
79
+ function classifyResult(result) {
80
+ const events = (0, _runtimeLogReader.readRuntimeLogEventsFromResult)(result);
81
+ const stdio = `${stdioToText(result.stdout)}\n${stdioToText(result.stderr)}`;
82
+ return {
83
+ environment: matchesAny(events, _patterns.ENVIRONMENT_PATTERNS),
84
+ code: matchesAny(events, _patterns.CODE_PATTERNS),
85
+ tool: _patterns.TOOL_PATTERNS.some(pattern => {
86
+ if (pattern.global || pattern.sticky) {
87
+ pattern.lastIndex = 0;
88
+ }
89
+ return pattern.test(stdio);
90
+ })
91
+ };
92
+ }
93
+ function pickFailingResult(results = []) {
94
+ const failing = results.filter(result => FAILING_RESULT_STATUSES.has(result.status));
95
+ if (failing.length === 0) {
96
+ return null;
97
+ }
98
+ return failing[failing.length - 1];
99
+ }
100
+
101
+ // Returns the ordered list of bare failure tags for a spec's test details.
102
+ // `testDetails` is the array of { status, results } objects under a spec.
103
+ function classifyFailureTags(testDetails = []) {
104
+ const tags = new Set();
105
+ let environment = false;
106
+ let code = false;
107
+ let tool = false;
108
+ for (const detail of testDetails) {
109
+ if (detail.status === FLAKY_OUTCOME) {
110
+ tags.add(_failureTagConstants.FAILURE_TAGS.FLAKY);
111
+ continue;
112
+ }
113
+ if (detail.status !== UNEXPECTED_OUTCOME) {
114
+ continue;
115
+ }
116
+ const failingResult = pickFailingResult(detail.results);
117
+ if (!failingResult) {
118
+ continue;
119
+ }
120
+ const matched = classifyResult(failingResult);
121
+ environment = environment || matched.environment;
122
+ code = code || matched.code;
123
+ tool = tool || matched.tool;
124
+ }
125
+ if (environment) {
126
+ tags.add(_failureTagConstants.FAILURE_TAGS.ENVIRONMENT);
127
+ }
128
+ if (code) {
129
+ tags.add(_failureTagConstants.FAILURE_TAGS.CODE);
130
+ }
131
+ if (tool) {
132
+ tags.add(_failureTagConstants.FAILURE_TAGS.TOOL);
133
+ }
134
+
135
+ // Stable order: flaky, environment, code, tool.
136
+ const ordered = [];
137
+ if (tags.has(_failureTagConstants.FAILURE_TAGS.FLAKY)) ordered.push(_failureTagConstants.FAILURE_TAGS.FLAKY);
138
+ if (tags.has(_failureTagConstants.FAILURE_TAGS.ENVIRONMENT)) ordered.push(_failureTagConstants.FAILURE_TAGS.ENVIRONMENT);
139
+ if (tags.has(_failureTagConstants.FAILURE_TAGS.CODE)) ordered.push(_failureTagConstants.FAILURE_TAGS.CODE);
140
+ if (tags.has(_failureTagConstants.FAILURE_TAGS.TOOL)) ordered.push(_failureTagConstants.FAILURE_TAGS.TOOL);
141
+ return ordered;
142
+ }
143
+
144
+ // Formats a bare category into its emitted tag form (e.g. "CODE" -> "CODE_failure").
145
+ // The flaky category is a passed-on-retry outcome, not a failure, so it is emitted
146
+ // as "FLAKY" without a suffix.
147
+ function formatFailureTag(tag) {
148
+ if (tag === _failureTagConstants.FAILURE_TAGS.FLAKY) {
149
+ return tag;
150
+ }
151
+ return `${tag}${_failureTagConstants.FAILURE_TAG_SUFFIX}`;
152
+ }
153
+
154
+ // Merges derived failure tags into an existing tags array. Failure tags are
155
+ // placed first, followed by the existing tags, without removing or duplicating
156
+ // entries.
157
+ function mergeFailureTags(existingTags = [], failureTags = []) {
158
+ const formatted = failureTags.map(formatFailureTag);
159
+ const merged = [...formatted];
160
+ for (const tag of existingTags) {
161
+ if (!merged.includes(tag)) {
162
+ merged.push(tag);
163
+ }
164
+ }
165
+ return merged;
166
+ }
@@ -1,22 +1,18 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
6
  exports.LogCollector = void 0;
8
- var _path = _interopRequireDefault(require("path"));
9
- var _fileUtils = require("../../../utils/fileUtils");
7
+ var _logCollectorConstants = require("../constants/logCollectorConstants");
10
8
  // Per-context Playwright runtime event collector — network failures,
11
- // console errors, page errors and crashes. Buffered in-memory and flushed
12
- // once per test via writeFileContents.
9
+ // console errors, page errors and crashes. Buffered in-memory and attached
10
+ // once per test via testInfo.attach.
13
11
 
14
12
  const DEFAULT_CONFIG = {
15
13
  maxEvents: 2000,
16
- maxBodyBytes: 0,
17
- captureHeaders: false,
18
- ignoreMessagePatterns: [/n\.enableDarkTheme is not a function/],
19
- ignoreUrlPatterns: [/\.(png|jpe?g|gif|svg|webp|woff2?|ttf|css|map)(\?|$)/i, /google-analytics\.com|googletagmanager\.com|sentry\.io/i]
14
+ ignoreMessagePatterns: _logCollectorConstants.IGNORE_MESSAGE_PATTERNS,
15
+ ignoreUrlPatterns: _logCollectorConstants.IGNORE_URL_PATTERNS
20
16
  };
21
17
  class LogCollector {
22
18
  constructor(config = {}) {
@@ -25,21 +21,10 @@ class LogCollector {
25
21
  ...config
26
22
  };
27
23
  this.events = [];
28
- this.truncated = false;
29
- this.counts = {
30
- console: 0,
31
- consoleError: 0,
32
- pageError: 0,
33
- request: 0,
34
- response: 0,
35
- failed: 0,
36
- crash: 0
37
- };
38
24
  this._bound = [];
39
25
  }
40
26
  _push(evt) {
41
27
  if (this.events.length >= this.config.maxEvents) {
42
- this.truncated = true;
43
28
  this.events.shift();
44
29
  }
45
30
  this.events.push({
@@ -48,6 +33,9 @@ class LogCollector {
48
33
  });
49
34
  }
50
35
  _shouldIgnore(url) {
36
+ if (!url) {
37
+ return false;
38
+ }
51
39
  return this.config.ignoreUrlPatterns.some(re => re.test(url));
52
40
  }
53
41
  _shouldIgnoreMessage(message) {
@@ -63,18 +51,11 @@ class LogCollector {
63
51
  attachToContext(context) {
64
52
  context.pages().forEach(p => this._attachPage(p));
65
53
  this._bind(context, 'page', p => this._attachPage(p));
66
- this._bind(context, 'request', req => {
67
- if (this._shouldIgnore(req.url())) {
68
- return;
69
- }
70
- this.counts.request++;
71
- });
72
54
  this._bind(context, 'response', res => {
73
55
  const url = res.url();
74
56
  if (this._shouldIgnore(url)) {
75
57
  return;
76
58
  }
77
- this.counts.response++;
78
59
  if (res.status() >= 400) {
79
60
  this._push({
80
61
  kind: 'response',
@@ -87,12 +68,16 @@ class LogCollector {
87
68
  });
88
69
  this._bind(context, 'requestfailed', req => {
89
70
  var _req$failure;
90
- this.counts.failed++;
71
+ const url = req.url();
72
+ const failure = (_req$failure = req.failure()) === null || _req$failure === void 0 ? void 0 : _req$failure.errorText;
73
+ if (this._shouldIgnore(url) || this._shouldIgnoreMessage(failure)) {
74
+ return;
75
+ }
91
76
  this._push({
92
77
  kind: 'requestfailed',
93
78
  method: req.method(),
94
- url: req.url(),
95
- failure: (_req$failure = req.failure()) === null || _req$failure === void 0 ? void 0 : _req$failure.errorText,
79
+ url,
80
+ failure,
96
81
  resourceType: req.resourceType()
97
82
  });
98
83
  });
@@ -100,27 +85,25 @@ class LogCollector {
100
85
  _attachPage(page) {
101
86
  this._bind(page, 'console', msg => {
102
87
  const type = msg.type();
103
- this.counts.console++;
104
88
  if (type !== 'error') {
105
89
  return;
106
90
  }
107
91
  const text = msg.text();
108
- if (this._shouldIgnoreMessage(text)) {
92
+ const location = msg.location();
93
+ if (this._shouldIgnoreMessage(text) || this._shouldIgnore(location === null || location === void 0 ? void 0 : location.url)) {
109
94
  return;
110
95
  }
111
- this.counts.consoleError++;
112
96
  this._push({
113
97
  kind: 'console',
114
98
  level: type,
115
99
  text,
116
- location: msg.location()
100
+ location
117
101
  });
118
102
  });
119
103
  this._bind(page, 'pageerror', err => {
120
104
  if (this._shouldIgnoreMessage(err.message)) {
121
105
  return;
122
106
  }
123
- this.counts.pageError++;
124
107
  this._push({
125
108
  kind: 'pageerror',
126
109
  name: err.name,
@@ -129,7 +112,6 @@ class LogCollector {
129
112
  });
130
113
  });
131
114
  this._bind(page, 'crash', () => {
132
- this.counts.crash++;
133
115
  this._push({
134
116
  kind: 'crash',
135
117
  url: page.url()
@@ -146,22 +128,11 @@ class LogCollector {
146
128
  }
147
129
  this._bound.length = 0;
148
130
  }
149
- toSummary() {
150
- return {
151
- ...this.counts,
152
- total: this.events.length,
153
- truncated: this.truncated
154
- };
155
- }
156
- flushToFile(dir, baseName) {
131
+ toJsonlBody() {
157
132
  if (this.events.length === 0) {
158
133
  return null;
159
134
  }
160
- const file = _path.default.join(dir, `${baseName}.jsonl`);
161
- const body = this.events.map(e => JSON.stringify(e)).join('\n') + '\n';
162
- (0, _fileUtils.writeFileContents)(file, body);
163
- this.events.length = 0;
164
- return file;
135
+ return this.events.map(e => JSON.stringify(e)).join('\n') + '\n';
165
136
  }
166
137
  }
167
138
  exports.LogCollector = LogCollector;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.readRuntimeLogEventsFromResult = readRuntimeLogEventsFromResult;
7
+ var _logCollectorConstants = require("../constants/logCollectorConstants");
8
+ // Reads runtime-log-events attachments produced by the context fixture.
9
+ // Used by the failure-tag classifier to correlate browser events with a test failure.
10
+
11
+ function attachmentBodyToString(body) {
12
+ if (!body) {
13
+ return '';
14
+ }
15
+ if (Buffer.isBuffer(body)) {
16
+ return body.toString('utf8');
17
+ }
18
+ return String(body);
19
+ }
20
+ function parseJsonlContent(raw) {
21
+ const trimmed = raw.trim();
22
+ if (!trimmed) {
23
+ return [];
24
+ }
25
+ const events = [];
26
+ for (const line of trimmed.split('\n')) {
27
+ const lineTrimmed = line.trim();
28
+ if (!lineTrimmed) {
29
+ continue;
30
+ }
31
+ try {
32
+ events.push(JSON.parse(lineTrimmed));
33
+ } catch {
34
+ // skip malformed line
35
+ }
36
+ }
37
+ return events;
38
+ }
39
+ function readRuntimeLogEventsFromResult(result) {
40
+ const eventsAttachment = ((result === null || result === void 0 ? void 0 : result.attachments) || []).find(item => item.name === _logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT);
41
+ if (!(eventsAttachment !== null && eventsAttachment !== void 0 && eventsAttachment.body)) {
42
+ return [];
43
+ }
44
+ return parseJsonlContent(attachmentBodyToString(eventsAttachment.body));
45
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CODE_PATTERNS = void 0;
7
+ // Code patterns: real application defects surfacing as JS exceptions in the
8
+ // browser. Matched against a flattened text form of each runtime-log event.
9
+ const CODE_PATTERNS = exports.CODE_PATTERNS = [/Cannot read properties of (undefined|null)/i, /\bis not a function\b/i, /\bis not defined\b/i, /\bis not iterable\b/i];
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ENVIRONMENT_PATTERNS = void 0;
7
+ // Environment patterns: infra / network / server breakage. Matched against a
8
+ // flattened text form of each runtime-log event (see eventToText).
9
+ const ENVIRONMENT_PATTERNS = exports.ENVIRONMENT_PATTERNS = [
10
+ // Main document navigation was aborted (page never loaded).
11
+ /^requestfailed document\b[\s\S]*ERR_ABORTED/i,
12
+ // Network / DNS / certificate / connection failures.
13
+ /ERR_TUNNEL_CONNECTION_FAILED|ERR_CERT_|ERR_CONNECTION_|ERR_NAME_NOT_RESOLVED|ERR_INTERNET_DISCONNECTED|ERR_TIMED_OUT|ERR_ADDRESS_UNREACHABLE/i,
14
+ // Server-side 5xx responses.
15
+ /^response 5\d\d\b/i, /status of 5\d\d/i];
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "CODE_PATTERNS", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _codePatterns.CODE_PATTERNS;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ENVIRONMENT_PATTERNS", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _environmentPatterns.ENVIRONMENT_PATTERNS;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "TOOL_PATTERNS", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _toolPatterns.TOOL_PATTERNS;
22
+ }
23
+ });
24
+ var _environmentPatterns = require("./environmentPatterns");
25
+ var _codePatterns = require("./codePatterns");
26
+ var _toolPatterns = require("./toolPatterns");
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TOOL_PATTERNS = void 0;
7
+ // Tool patterns: failures originating from the test tooling / data-generation
8
+ // layer (not the app under test). Matched against the failing result's
9
+ // stdout + stderr text. Add new patterns here to recognise more tool errors.
10
+ const TOOL_PATTERNS = exports.TOOL_PATTERNS = [/status\s*:\s*DATA_GENERATION_ERROR/i, /Error Type\s*:\s*Error/i, /Error during login/i,
11
+ // Generator
12
+ /GeneratorError: Generator/i, /DataGeneratorConfigurationError: Data Generator configuration is missing for the profile/i, /HTTP error! status:/i,
13
+ // Configuration
14
+ /Config File Not Exists\. Please provide a/i, /Exception while getting the uat file from the application/i, /There is no beta feature configured with the name/i, /No config mapping found for stage:/i,
15
+ // Edition
16
+ /edition configured\./i, /portal configured in/i, /profile configured in/i,
17
+ // Actor
18
+ /file missing\./i, /Error loading actor configuration from/i, /The actors data is missing\./i, /not found in user pages\./i,
19
+ // Module
20
+ /does not exist\. We have not triggered the execution for this module/i,
21
+ // Auth
22
+ /Error while parsing cookies/i,
23
+ // FileMutex
24
+ /Watch timeout exceeded/i,
25
+ // Runner
26
+ /Method 'run\(\)' must be implemented\./i, /Invalid runner type/i,
27
+ // Process
28
+ /Child Process Exited with Code/i, /Terminating Playwright Process\.\.\./i, /Cleaning up\.\.\./i,
29
+ // TestData
30
+ /Error appending or creating the test data file:/i, /Error\(err\)/i, /Error while deleting the test data file:/i,
31
+ // FeatureFile
32
+ /Error while parsing feature files\. Please fix the above issues before running test cases/i,
33
+ // RPC
34
+ /Error: HTTP /i, /Invalid payload\. It must be a non-null object\./i, /Invalid payload\.arguments\.entityId\./i,
35
+ // Search
36
+ /Invalid or missing data in dataTable/i, /JSONPath query/i];
@@ -57,7 +57,7 @@ function getDefaultConfig() {
57
57
  stepDefinitionsFolder: 'steps',
58
58
  testSetup: {},
59
59
  editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
60
- showCaseTimings: true,
60
+ showCaseTimings: false,
61
61
  browserLogs: {
62
62
  mode: 'failure-only'
63
63
  }
@@ -120,7 +120,7 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
120
120
  * @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
121
121
  * @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
122
122
  * @property {boolean} showCaseTimings: When true, the console reporter prints per-case start/end wall-clock times (h:mm:ss AM/PM) and a case-timings.json sidecar is written to reportPath for the HTML report addon. Default: true.
123
- * @property {Object} browserLogs: Runtime browser log capture. { mode: 'failure-only' | 'always', limits?: { maxEvents, captureHeaders, ignoreUrlPatterns, ignoreMessagePatterns } }. ignoreMessagePatterns is a list of RegExp matched against console-error text and pageerror messages; matching events are skipped. Default: { mode: 'failure-only' }.
123
+ * @property {Object} browserLogs: Runtime browser log capture. { mode: 'failure-only' | 'always', limits?: { maxEvents, ignoreUrlPatterns, ignoreMessagePatterns } }. ignoreMessagePatterns is a list of RegExp matched against console-error text and pageerror messages; matching events are skipped. Default: { mode: 'failure-only' }.
124
124
  */
125
125
 
126
126
  /**
@@ -128,15 +128,6 @@ class JSONSummaryReporter {
128
128
  const isFailure = result.status !== 'passed' && result.status !== 'skipped';
129
129
  if (isFailure) {
130
130
  var _result$error;
131
- const runtimeAttachment = (result.attachments || []).find(a => a.name === 'runtime-log' && a.body);
132
- let runtime = null;
133
- if (runtimeAttachment) {
134
- try {
135
- runtime = JSON.parse(runtimeAttachment.body.toString('utf8'));
136
- } catch {
137
- // malformed attachment; skip
138
- }
139
- }
140
131
  this._caseTimings.push({
141
132
  title: fullTitle,
142
133
  fileName,
@@ -155,13 +146,7 @@ class JSONSummaryReporter {
155
146
  title: step.title,
156
147
  error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
157
148
  };
158
- }),
159
- ...(runtime ? {
160
- runtime: {
161
- summary: runtime.summary,
162
- logFile: runtime.file
163
- }
164
- } : {})
149
+ })
165
150
  });
166
151
  }
167
152
  }
@@ -10,7 +10,21 @@ var _path = _interopRequireDefault(require("path"));
10
10
  var _codeFrame = require("@babel/code-frame");
11
11
  var _configConstants = _interopRequireDefault(require("../constants/configConstants"));
12
12
  var _ConfigurationHelper = require("../configuration/ConfigurationHelper");
13
+ var _failureTagClassifier = require("../helpers/failureTagClassifier");
13
14
  const stage = (0, _ConfigurationHelper.getRunStage)();
15
+
16
+ // Walks the built suite tree and appends derived failure tags (CODE_failure, ENVIRONMENT_failure) into each spec's tags array.
17
+ const applyFailureTags = node => {
18
+ for (const spec of node.specs ?? []) {
19
+ const failureTags = (0, _failureTagClassifier.classifyFailureTags)(spec.tests);
20
+ if (failureTags.length > 0) {
21
+ spec.tags = (0, _failureTagClassifier.mergeFailureTags)(spec.tags, failureTags);
22
+ }
23
+ }
24
+ for (const child of node.suites ?? []) {
25
+ applyFailureTags(child);
26
+ }
27
+ };
14
28
  class CustomJsonReporter {
15
29
  constructor({
16
30
  outputFile = 'test-results.json'
@@ -52,6 +66,7 @@ class CustomJsonReporter {
52
66
  const extracted = suite.suites.map(suite => extractMergedSuite(this.report.config.rootDir, suite, this.testResultsById));
53
67
  this.report.suites.push(...extracted);
54
68
  });
69
+ this.report.suites.forEach(suite => applyFailureTags(suite));
55
70
  const writableStream = _fs.default.createWriteStream(this.outputFile);
56
71
  writableStream.write('{\n');
57
72
  writableStream.write(` "config": ${JSON.stringify(this.report.config, null, 2)},\n`);
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+
3
+ var _logCollectorConstants = require("../../../../../core/playwright/constants/logCollectorConstants");
4
+ var toolPatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/toolPatterns"));
5
+ var _failureTagClassifier = require("../../../../../core/playwright/helpers/failureTagClassifier");
6
+ 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
+ }
16
+ return {
17
+ status: 'unexpected',
18
+ results: [{
19
+ status: 'failed',
20
+ attachments
21
+ }]
22
+ };
23
+ }
24
+ function failingDetailWithStdio({
25
+ stdout = [],
26
+ stderr = []
27
+ }) {
28
+ return {
29
+ status: 'unexpected',
30
+ results: [{
31
+ status: 'failed',
32
+ attachments: [],
33
+ stdout,
34
+ stderr
35
+ }]
36
+ };
37
+ }
38
+ describe('classifyFailureTags', () => {
39
+ test('returns flaky and skips log analysis for flaky outcome', () => {
40
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([{
41
+ status: 'flaky',
42
+ results: []
43
+ }]);
44
+ expect(tags).toEqual(['FLAKY']);
45
+ });
46
+ test('tags environment when a document abort is present', () => {
47
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
48
+ kind: 'requestfailed',
49
+ url: 'https://app/events/list/commentsubtabevent',
50
+ failure: 'net::ERR_ABORTED',
51
+ resourceType: 'document'
52
+ }])]);
53
+ expect(tags).toEqual(['ENVIRONMENT']);
54
+ });
55
+ test('tags environment for a network/cert failure', () => {
56
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
57
+ kind: 'requestfailed',
58
+ url: 'https://js/gshandler.js',
59
+ failure: 'net::ERR_CERT_COMMON_NAME_INVALID',
60
+ resourceType: 'script'
61
+ }])]);
62
+ expect(tags).toEqual(['ENVIRONMENT']);
63
+ });
64
+ test('tags code when an app undefined-property error is present', () => {
65
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
66
+ kind: 'pageerror',
67
+ name: 'TypeError',
68
+ message: "Cannot read properties of undefined (reading 'id')"
69
+ }])]);
70
+ expect(tags).toEqual(['CODE']);
71
+ });
72
+ test('does not tag a non-document (xhr) abort as environment', () => {
73
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
74
+ kind: 'requestfailed',
75
+ url: 'https://app/api/tickets',
76
+ failure: 'net::ERR_ABORTED',
77
+ resourceType: 'xhr'
78
+ }])]);
79
+ expect(tags).toEqual([]);
80
+ });
81
+ test('tags tool when stderr reports a DATA_GENERATION_ERROR', () => {
82
+ const detail = failingDetailWithStdio({
83
+ stderr: [{
84
+ text: 'status : DATA_GENERATION_ERROR\n generator: comments'
85
+ }]
86
+ });
87
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
88
+ expect(tags).toEqual(['TOOL']);
89
+ });
90
+ test('tags tool for an "Error Type:" line in base64 stdout', () => {
91
+ const detail = failingDetailWithStdio({
92
+ stdout: [{
93
+ buffer: Buffer.from('Error Type: Error').toString('base64')
94
+ }]
95
+ });
96
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
97
+ expect(tags).toEqual(['TOOL']);
98
+ });
99
+ test('does not tag tool for benign stdout/stderr', () => {
100
+ const detail = failingDetailWithStdio({
101
+ stdout: [{
102
+ text: 'Generated 5 records successfully'
103
+ }],
104
+ stderr: []
105
+ });
106
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail]);
107
+ expect(tags).toEqual([]);
108
+ });
109
+ test('does not throw for malformed stdio chunk entries', () => {
110
+ const detail = failingDetailWithStdio({
111
+ stdout: [null, undefined, 42, {
112
+ text: 'still fine'
113
+ }],
114
+ stderr: [false]
115
+ });
116
+ expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail])).not.toThrow();
117
+ expect((0, _failureTagClassifier.classifyFailureTags)([detail])).toEqual([]);
118
+ });
119
+ test('leaves untagged when no environment or code pattern is present', () => {
120
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
121
+ kind: 'response',
122
+ status: 200,
123
+ method: 'GET',
124
+ url: 'https://app/ok'
125
+ }, {
126
+ kind: 'console',
127
+ level: 'error',
128
+ text: 'Some benign log line',
129
+ location: {
130
+ url: 'https://app/page'
131
+ }
132
+ }])]);
133
+ expect(tags).toEqual([]);
134
+ });
135
+ });
136
+ describe('mergeFailureTags', () => {
137
+ test('places suffixed failure tags first, then existing tags', () => {
138
+ expect((0, _failureTagClassifier.mergeFailureTags)(['@comments', '@calls'], ['CODE', 'ENVIRONMENT'])).toEqual(['CODE_failure', 'ENVIRONMENT_failure', '@comments', '@calls']);
139
+ });
140
+ test('emits the flaky category as FLAKY without a suffix', () => {
141
+ expect((0, _failureTagClassifier.mergeFailureTags)([], ['FLAKY', 'CODE'])).toEqual(['FLAKY', 'CODE_failure']);
142
+ });
143
+ test('does not duplicate an already-present failure tag', () => {
144
+ expect((0, _failureTagClassifier.mergeFailureTags)(['CODE_failure'], ['CODE'])).toEqual(['CODE_failure']);
145
+ });
146
+ });
147
+ describe('regex safety', () => {
148
+ test('resets stateful regex for tool pattern checks', () => {
149
+ const originalToolPatterns = toolPatterns.TOOL_PATTERNS.slice();
150
+ try {
151
+ toolPatterns.TOOL_PATTERNS.length = 0;
152
+ toolPatterns.TOOL_PATTERNS.push(/DATA_GENERATION_ERROR/g);
153
+ const detail = failingDetailWithStdio({
154
+ stderr: [{
155
+ text: 'status : DATA_GENERATION_ERROR'
156
+ }]
157
+ });
158
+ const first = (0, _failureTagClassifier.classifyFailureTags)([detail]);
159
+ const second = (0, _failureTagClassifier.classifyFailureTags)([detail]);
160
+ expect(first).toEqual(['TOOL']);
161
+ expect(second).toEqual(['TOOL']);
162
+ } finally {
163
+ toolPatterns.TOOL_PATTERNS.length = 0;
164
+ toolPatterns.TOOL_PATTERNS.push(...originalToolPatterns);
165
+ }
166
+ });
167
+ });
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+
3
+ var _logCollector = require("../../../../../core/playwright/helpers/logCollector");
4
+ function makeEmitter() {
5
+ const handlers = {};
6
+ return {
7
+ handlers,
8
+ on(event, handler) {
9
+ (handlers[event] = handlers[event] || []).push(handler);
10
+ },
11
+ removeListener() {},
12
+ emit(event, arg) {
13
+ (handlers[event] || []).forEach(handler => handler(arg));
14
+ }
15
+ };
16
+ }
17
+ function makeContext() {
18
+ const page = makeEmitter();
19
+ const emitter = makeEmitter();
20
+ return {
21
+ page,
22
+ context: {
23
+ ...emitter,
24
+ pages: () => [page]
25
+ }
26
+ };
27
+ }
28
+ const failedRequest = (url, errorText, resourceType = 'script', method = 'GET') => ({
29
+ url: () => url,
30
+ failure: () => ({
31
+ errorText
32
+ }),
33
+ method: () => method,
34
+ resourceType: () => resourceType
35
+ });
36
+ const response = (url, status, method = 'GET') => ({
37
+ url: () => url,
38
+ status: () => status,
39
+ request: () => ({
40
+ method: () => method
41
+ }),
42
+ fromServiceWorker: () => false
43
+ });
44
+ const consoleMessage = (text, url) => ({
45
+ type: () => 'error',
46
+ text: () => text,
47
+ location: () => ({
48
+ url
49
+ })
50
+ });
51
+ describe('LogCollector baseline-noise stripping', () => {
52
+ let collector;
53
+ let context;
54
+ let page;
55
+ beforeEach(() => {
56
+ collector = new _logCollector.LogCollector();
57
+ const ctx = makeContext();
58
+ context = ctx.context;
59
+ page = ctx.page;
60
+ collector.attachToContext(context);
61
+ });
62
+ test('drops ORB-blocked request failures (message pattern)', () => {
63
+ context.emit('requestfailed', failedRequest('https://app/some/script.js', 'net::ERR_BLOCKED_BY_ORB'));
64
+ expect(collector.events).toHaveLength(0);
65
+ });
66
+ test('drops known noisy third-party URLs (url pattern)', () => {
67
+ context.emit('requestfailed', failedRequest('https://cdn/murphysdk/3.5.0/murphy.min.js', 'net::ERR_ABORTED'));
68
+ expect(collector.events).toHaveLength(0);
69
+ });
70
+ test('drops noisy 4xx responses by URL (e.g. getHamburgerMenu)', () => {
71
+ context.emit('response', response('https://app/biz/getHamburgerMenu.do?version=stable', 404));
72
+ expect(collector.events).toHaveLength(0);
73
+ });
74
+ test('drops CORS console errors (message pattern)', () => {
75
+ page.emit('console', consoleMessage("Access to font at 'https://x/f.woff' has been blocked by CORS policy: ...", 'https://app/page'));
76
+ expect(collector.events).toHaveLength(0);
77
+ });
78
+ test('drops baseline pageerrors (Access is denied / enable* not a function)', () => {
79
+ page.emit('pageerror', {
80
+ name: 'SecurityError',
81
+ message: 'Failed to read the localStorage: Access is denied for this document.',
82
+ stack: ''
83
+ });
84
+ page.emit('pageerror', {
85
+ name: 'TypeError',
86
+ message: 'n.enableDarkTheme is not a function',
87
+ stack: ''
88
+ });
89
+ expect(collector.events).toHaveLength(0);
90
+ });
91
+ test('keeps a real application pageerror', () => {
92
+ page.emit('pageerror', {
93
+ name: 'TypeError',
94
+ message: "Cannot read properties of undefined (reading 'id')",
95
+ stack: 'TypeError: ...'
96
+ });
97
+ expect(collector.events).toHaveLength(1);
98
+ expect(collector.events[0]).toMatchObject({
99
+ kind: 'pageerror',
100
+ name: 'TypeError'
101
+ });
102
+ });
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);
113
+ });
114
+ test('returns null from toJsonlBody when no events were captured', () => {
115
+ expect(collector.toJsonlBody()).toBeNull();
116
+ });
117
+ test('keeps a document-navigation abort (real environment signal)', () => {
118
+ context.emit('requestfailed', failedRequest('https://app/agent/events/list/commentsubtabevent', 'net::ERR_ABORTED', 'document'));
119
+ expect(collector.events).toHaveLength(1);
120
+ expect(collector.events[0]).toMatchObject({
121
+ kind: 'requestfailed',
122
+ resourceType: 'document'
123
+ });
124
+ });
125
+ });
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ var _logCollectorConstants = require("../../../../../core/playwright/constants/logCollectorConstants");
4
+ var _runtimeLogReader = require("../../../../../core/playwright/helpers/runtimeLogReader");
5
+ function jsonlBody(events) {
6
+ return Buffer.from(`${events.map(e => JSON.stringify(e)).join('\n')}\n`);
7
+ }
8
+ describe('readRuntimeLogEventsFromResult', () => {
9
+ test('reads inline runtime-log-events body', () => {
10
+ const inlineEvents = [{
11
+ kind: 'pageerror',
12
+ message: 'inline boom'
13
+ }];
14
+ const result = {
15
+ attachments: [{
16
+ name: _logCollectorConstants.RUNTIME_LOG_EVENTS_ATTACHMENT,
17
+ contentType: _logCollectorConstants.RUNTIME_LOG_EVENTS_CONTENT_TYPE,
18
+ body: jsonlBody(inlineEvents)
19
+ }]
20
+ };
21
+ expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)(result)).toEqual(inlineEvents);
22
+ });
23
+ test('skips malformed jsonl lines', () => {
24
+ const raw = [JSON.stringify({
25
+ kind: 'console',
26
+ text: 'ok'
27
+ }), 'not-json', JSON.stringify({
28
+ kind: 'pageerror',
29
+ message: 'err'
30
+ })].join('\n');
31
+ 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
+ }]
37
+ };
38
+ expect((0, _runtimeLogReader.readRuntimeLogEventsFromResult)(result)).toEqual([{
39
+ kind: 'console',
40
+ text: 'ok'
41
+ }, {
42
+ kind: 'pageerror',
43
+ message: 'err'
44
+ }]);
45
+ });
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([]);
51
+ });
52
+ });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zohodesk/testinglibrary",
3
- "version": "0.0.81-n20-experimental",
3
+ "version": "0.0.83-n20-experimental",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zohodesk/testinglibrary",
9
- "version": "0.0.81-n20-experimental",
9
+ "version": "0.0.83-n20-experimental",
10
10
  "hasInstallScript": true,
11
11
  "license": "ISC",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/testinglibrary",
3
- "version": "0.0.81-n20-experimental",
3
+ "version": "0.0.83-n20-experimental",
4
4
  "main": "./build/index.js",
5
5
  "scripts": {
6
6
  "postinstall": "node bin/postinstall.js",
@@ -274,4 +274,4 @@ header {
274
274
  font-size: 1rem;
275
275
  padding: 0 0.5rem;
276
276
  }
277
- </style></head><body><main class="jesthtml-content"><header><h1 id="title">Unit Report</h1></header><section id="metadata-container"><div id="timestamp">Started: 2026-07-03 13:32:15</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (1)</div><div class="summary-passed ">1 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (14)</div><div class="summary-passed ">14 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></section><details id="suite-1" class="suite-container" open=""><summary class="suite-info"><div class="suite-path">/Users/muthu-19817/git/testing-framework/src/test/core/playwright/helpers/__tests__/failureTagClassifier.test.js</div><div class="suite-time">0.498s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">returns flaky and skips log analysis for flaky outcome</div><div class="test-status">passed</div><div class="test-duration">0.002s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags environment when a document abort is present</div><div class="test-status">passed</div><div class="test-duration">0.002s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags environment for a network/cert failure</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags code when an app undefined-property error is present</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not tag a non-document (xhr) abort as environment</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags tool when stderr reports a DATA_GENERATION_ERROR</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags tool for an "Error Type:" line in base64 stdout</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not tag tool for benign stdout/stderr</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not throw for malformed stdio chunk entries</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">leaves untagged when no environment or code pattern is present</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">places suffixed failure tags first, then existing tags</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">emits the flaky category as FLAKY without a suffix</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">does not duplicate an already-present failure tag</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">regex safety</div><div class="test-title">resets stateful regex for tool pattern checks</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details></main></body></html>
277
+ </style></head><body><main class="jesthtml-content"><header><h1 id="title">Unit Report</h1></header><section id="metadata-container"><div id="timestamp">Started: 2026-07-05 00:43:44</div><div id="summary"><div id="suite-summary"><div class="summary-total">Suites (3)</div><div class="summary-passed ">3 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div><div id="test-summary"><div class="summary-total">Tests (26)</div><div class="summary-passed ">26 passed</div><div class="summary-failed summary-empty">0 failed</div><div class="summary-pending summary-empty">0 pending</div></div></div></section><details id="suite-1" class="suite-container" open=""><summary class="suite-info"><div class="suite-path">/Users/muthu-19817/git/testing-framework/src/test/core/playwright/helpers/__tests__/runtimeLogReader.test.js</div><div class="suite-time">0.459s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">readRuntimeLogEventsFromResult</div><div class="test-title">reads inline runtime-log-events body</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">readRuntimeLogEventsFromResult</div><div class="test-title">skips malformed jsonl lines</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">readRuntimeLogEventsFromResult</div><div class="test-title">returns an empty array when runtime-log-events is missing</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details><details id="suite-2" class="suite-container" open=""><summary class="suite-info"><div class="suite-path">/Users/muthu-19817/git/testing-framework/src/test/core/playwright/helpers/__tests__/failureTagClassifier.test.js</div><div class="suite-time">0.11s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">returns flaky and skips log analysis for flaky outcome</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags environment when a document abort is present</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags environment for a network/cert failure</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags code when an app undefined-property error is present</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not tag a non-document (xhr) abort as environment</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags tool when stderr reports a DATA_GENERATION_ERROR</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">tags tool for an "Error Type:" line in base64 stdout</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not tag tool for benign stdout/stderr</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">does not throw for malformed stdio chunk entries</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">classifyFailureTags</div><div class="test-title">leaves untagged when no environment or code pattern is present</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">places suffixed failure tags first, then existing tags</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">emits the flaky category as FLAKY without a suffix</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">does not duplicate an already-present failure tag</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">regex safety</div><div class="test-title">resets stateful regex for tool pattern checks</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details><details id="suite-3" class="suite-container" open=""><summary class="suite-info"><div class="suite-path">/Users/muthu-19817/git/testing-framework/src/test/core/playwright/helpers/__tests__/logCollector.test.js</div><div class="suite-time">0.082s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">drops ORB-blocked request failures (message pattern)</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">drops known noisy third-party URLs (url pattern)</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">drops noisy 4xx responses by URL (e.g. getHamburgerMenu)</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">drops CORS console errors (message pattern)</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">drops baseline pageerrors (Access is denied / enable* not a function)</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">keeps a real application pageerror</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">serializes captured events as jsonl</div><div class="test-status">passed</div><div class="test-duration">0.001s</div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">returns null from toJsonlBody when no events were captured</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">LogCollector baseline-noise stripping</div><div class="test-title">keeps a document-navigation abort (real environment signal)</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details></main></body></html>