@zohodesk/testinglibrary 4.1.6 → 4.1.10

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/README.md CHANGED
@@ -17,6 +17,13 @@
17
17
 
18
18
  - npm run report
19
19
 
20
+ ### v4.1.9/v3.3.5 - 03-07-2026
21
+
22
+ #### Feature
23
+ - PortalDomain option provided in the settings.json.
24
+ - This lets customer portal and related UAT flows resolve the portal URL from configuration instead of hardcoding environment-specific links.
25
+ - It simplifies switching between setups such as dev, QA, and UAT without changing test code.
26
+
20
27
 
21
28
  ### v4.1.5/v3.3.3 - 19-06-2026
22
29
 
@@ -23,13 +23,18 @@ class DataGenerator {
23
23
  async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable, page = null) {
24
24
  try {
25
25
  let generators;
26
+ let domainUrl;
26
27
  if (generatorType === 'API') {
27
28
  generators = await _assertClassBrand(_DataGenerator_brand, this, _generateAPIGenerator).call(this, generatorName);
28
29
  } else {
29
30
  generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this, testInfo, generatorName);
30
31
  }
32
+ const generatorOperationIds = generators.map(({
33
+ generatorOperationId
34
+ }) => generatorOperationId).filter(Boolean);
35
+ domainUrl = generatorOperationIds.some(operationId => operationId.startsWith('portal.')) ? process.env.portalDomain : process.env.domain;
31
36
  const processedGenerators = await (0, _DataGeneratorHelper.processGenerator)(generators, dataTable);
32
- const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo, page);
37
+ const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo, domainUrl, page);
33
38
  const response = await (0, _DataGeneratorHelper.makeRequest)(process.env.DG_SERVICE_DOMAIN + process.env.DG_SERVICE_API_PATH, apiPayload);
34
39
  _logger.Logger.log(_logger.Logger.INFO_TYPE, `Generated response for the generator: ${generatorName} for scenario: ${scenarioName}, Response: ${JSON.stringify(response)}`);
35
40
  return response;
@@ -73,7 +78,7 @@ async function _generateAPIGenerator(operationId) {
73
78
  name: operationId
74
79
  }];
75
80
  }
76
- async function _constructApiPayload(scenarioName, processedGenerators, actorInfo, page = null) {
81
+ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo, domainUrl, page = null) {
77
82
  const dataGeneratorObj = actorInfo['data-generator'];
78
83
  if (!dataGeneratorObj) {
79
84
  throw new _DataGeneratorError.DataGeneratorConfigurationError(`Data Generator configuration is missing for the profile: ${actorInfo['profile']}`);
@@ -91,8 +96,8 @@ async function _constructApiPayload(scenarioName, processedGenerators, actorInfo
91
96
  const environmentDetails = apiPayload.environmentDetails || {};
92
97
  if (environmentDetails) {
93
98
  environmentDetails.iam_url = process.env.DG_IAM_DOMAIN;
94
- const domainUrl = new URL(process.env.domain);
95
- environmentDetails.host = domainUrl.origin;
99
+ const selectedDomain = new URL(domainUrl || process.env.domain);
100
+ environmentDetails.host = selectedDomain.origin;
96
101
  }
97
102
  apiPayload.environmentDetails = environmentDetails;
98
103
 
@@ -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
- let context = await browser.newContext();
29
- let page = await context.newPage();
30
- let ctxTestDetails = {
31
- page,
28
+ const additionalContext = await browser.newContext();
29
+ const additionalPage = await additionalContext.newPage();
30
+ const ctxTestDetails = {
31
+ page: additionalPage,
32
32
  $tags,
33
- context,
33
+ context: additionalContext,
34
34
  ...actorInfo
35
35
  };
36
- await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(context, testInfo, ctxTestDetails, actorInfo);
36
+ await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(additionalContext, testInfo, ctxTestDetails, actorInfo);
37
37
  this.actorsObj[role] = {
38
38
  role,
39
39
  browser,
40
- context,
41
- page,
40
+ context: additionalContext,
41
+ page: additionalPage,
42
42
  executionContext: {
43
43
  actorInfo
44
44
  }
@@ -69,7 +69,10 @@ 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
- await use(ctxObject);
73
- await ctxObject.cleanup();
72
+ try {
73
+ await use(ctxObject);
74
+ } finally {
75
+ await ctxObject.cleanup();
76
+ }
74
77
  }
75
78
  };
@@ -1,13 +1,28 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
7
  exports.default = void 0;
8
+ var _path = _interopRequireDefault(require("path"));
7
9
  var _readConfigFile = require("../readConfigFile");
10
+ var _logCollector = require("../helpers/logCollector");
8
11
  const {
9
- testSetup
12
+ testSetup,
13
+ uatDirectory,
14
+ browserLogs = {}
10
15
  } = (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
+ }
11
26
  async function performDefaultContextSteps({
12
27
  context
13
28
  }) {
@@ -20,13 +35,37 @@ async function performDefaultContextSteps({
20
35
  var _default = exports.default = {
21
36
  context: async ({
22
37
  context
23
- }, use) => {
38
+ }, use, testInfo) => {
24
39
  await context.addInitScript(() =>
25
40
  // eslint-disable-next-line no-undef
26
41
  window.localStorage.setItem('isDnBannerHide', true));
27
42
  await performDefaultContextSteps({
28
43
  context
29
44
  });
30
- await use(context);
45
+ const collector = new _logCollector.LogCollector(browserLogs.limits);
46
+ collector.attachToContext(context);
47
+ testInfo.runtimeLogs = collector;
48
+ try {
49
+ await use(context);
50
+ } finally {
51
+ collector.detach();
52
+ const failureOnly = browserLogs.mode === 'failure-only';
53
+ 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);
60
+ }
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
+ }
31
70
  }
32
71
  };
@@ -32,7 +32,6 @@ var _default = exports.default = {
32
32
  } finally {
33
33
  await (0, _loginDefaultStepsHelper.performDefaultPageSteps)(testDetails);
34
34
  await use(page);
35
- await context.close();
36
35
  }
37
36
  }
38
37
  };
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.LogCollector = void 0;
8
+ var _path = _interopRequireDefault(require("path"));
9
+ var _fileUtils = require("../../../utils/fileUtils");
10
+ // 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.
13
+
14
+ const DEFAULT_CONFIG = {
15
+ 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]
20
+ };
21
+ class LogCollector {
22
+ constructor(config = {}) {
23
+ this.config = {
24
+ ...DEFAULT_CONFIG,
25
+ ...config
26
+ };
27
+ 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
+ this._bound = [];
39
+ }
40
+ _push(evt) {
41
+ if (this.events.length >= this.config.maxEvents) {
42
+ this.truncated = true;
43
+ this.events.shift();
44
+ }
45
+ this.events.push({
46
+ ts: Date.now(),
47
+ ...evt
48
+ });
49
+ }
50
+ _shouldIgnore(url) {
51
+ return this.config.ignoreUrlPatterns.some(re => re.test(url));
52
+ }
53
+ _shouldIgnoreMessage(message) {
54
+ if (!message) {
55
+ return false;
56
+ }
57
+ return this.config.ignoreMessagePatterns.some(re => re.test(message));
58
+ }
59
+ _bind(target, event, handler) {
60
+ target.on(event, handler);
61
+ this._bound.push([target, event, handler]);
62
+ }
63
+ attachToContext(context) {
64
+ context.pages().forEach(p => this._attachPage(p));
65
+ 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
+ this._bind(context, 'response', res => {
73
+ const url = res.url();
74
+ if (this._shouldIgnore(url)) {
75
+ return;
76
+ }
77
+ this.counts.response++;
78
+ if (res.status() >= 400) {
79
+ this._push({
80
+ kind: 'response',
81
+ status: res.status(),
82
+ method: res.request().method(),
83
+ url,
84
+ fromCache: res.fromServiceWorker()
85
+ });
86
+ }
87
+ });
88
+ this._bind(context, 'requestfailed', req => {
89
+ var _req$failure;
90
+ this.counts.failed++;
91
+ this._push({
92
+ kind: 'requestfailed',
93
+ method: req.method(),
94
+ url: req.url(),
95
+ failure: (_req$failure = req.failure()) === null || _req$failure === void 0 ? void 0 : _req$failure.errorText,
96
+ resourceType: req.resourceType()
97
+ });
98
+ });
99
+ }
100
+ _attachPage(page) {
101
+ this._bind(page, 'console', msg => {
102
+ const type = msg.type();
103
+ this.counts.console++;
104
+ if (type !== 'error') {
105
+ return;
106
+ }
107
+ const text = msg.text();
108
+ if (this._shouldIgnoreMessage(text)) {
109
+ return;
110
+ }
111
+ this.counts.consoleError++;
112
+ this._push({
113
+ kind: 'console',
114
+ level: type,
115
+ text,
116
+ location: msg.location()
117
+ });
118
+ });
119
+ this._bind(page, 'pageerror', err => {
120
+ if (this._shouldIgnoreMessage(err.message)) {
121
+ return;
122
+ }
123
+ this.counts.pageError++;
124
+ this._push({
125
+ kind: 'pageerror',
126
+ name: err.name,
127
+ message: err.message,
128
+ stack: err.stack
129
+ });
130
+ });
131
+ this._bind(page, 'crash', () => {
132
+ this.counts.crash++;
133
+ this._push({
134
+ kind: 'crash',
135
+ url: page.url()
136
+ });
137
+ });
138
+ }
139
+ detach() {
140
+ for (const [target, event, handler] of this._bound) {
141
+ try {
142
+ target.removeListener(event, handler);
143
+ } catch {
144
+ // context may already be closed
145
+ }
146
+ }
147
+ this._bound.length = 0;
148
+ }
149
+ toSummary() {
150
+ return {
151
+ ...this.counts,
152
+ total: this.events.length,
153
+ truncated: this.truncated
154
+ };
155
+ }
156
+ flushToFile(dir, baseName) {
157
+ if (this.events.length === 0) {
158
+ return null;
159
+ }
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;
165
+ }
166
+ }
167
+ exports.LogCollector = LogCollector;
@@ -56,7 +56,11 @@ function getDefaultConfig() {
56
56
  featureFilesFolder: 'feature-files',
57
57
  stepDefinitionsFolder: 'steps',
58
58
  testSetup: {},
59
- editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
59
+ editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
60
+ showCaseTimings: false,
61
+ browserLogs: {
62
+ mode: 'failure-only'
63
+ }
60
64
  };
61
65
  }
62
66
  function combineDefaultConfigWithUserConfig(userConfiguration) {
@@ -115,6 +119,8 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
115
119
  * @property {string} testIdAttribute: Change the default data-testid attribute. configure what attribute to search while calling getByTestId
116
120
  * @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
117
121
  * @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
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' }.
118
124
  */
119
125
 
120
126
  /**
@@ -24,6 +24,22 @@ function getProjectName(test) {
24
24
  }
25
25
  return '';
26
26
  }
27
+ function formatTime(date) {
28
+ return date.toLocaleTimeString('en-US', {
29
+ hour: 'numeric',
30
+ minute: '2-digit',
31
+ second: '2-digit',
32
+ hour12: true
33
+ });
34
+ }
35
+ function formatDuration(ms) {
36
+ if (ms < 1000) return `${ms}ms`;
37
+ const totalSeconds = ms / 1000;
38
+ if (totalSeconds < 60) return `${totalSeconds.toFixed(2)}s`;
39
+ const minutes = Math.floor(totalSeconds / 60);
40
+ const seconds = (totalSeconds - minutes * 60).toFixed(2);
41
+ return `${minutes}m ${seconds}s`;
42
+ }
27
43
  class JSONSummaryReporter {
28
44
  constructor() {
29
45
  this.durationInMS = -1;
@@ -38,12 +54,23 @@ class JSONSummaryReporter {
38
54
  this.failedSteps = [];
39
55
  this.status = 'unknown';
40
56
  this.startedAt = 0;
41
- this._open = (0, _readConfigFile.generateConfigFromFile)().openReportOn;
57
+ const config = (0, _readConfigFile.generateConfigFromFile)();
58
+ this._open = config.openReportOn;
59
+ this._showCaseTimings = config.showCaseTimings !== false;
60
+ this._caseTimings = [];
42
61
  this.failedProject = null;
43
62
  }
44
63
  onBegin() {
45
64
  this.startedAt = Date.now();
46
65
  }
66
+ onTestBegin(test) {
67
+ if (!this._showCaseTimings) return;
68
+ const {
69
+ fullTitle
70
+ } = this.getTitle(test);
71
+ const startedAtLabel = formatTime(new Date());
72
+ _logger.Logger.log(_logger.Logger.INFO_TYPE, `▶ ${fullTitle} — started at ${startedAtLabel}`);
73
+ }
47
74
  getTitle(test) {
48
75
  const title = [];
49
76
  const fileName = [];
@@ -89,6 +116,55 @@ class JSONSummaryReporter {
89
116
  this[status].push(fileName);
90
117
  }
91
118
  this[status].push(fileName);
119
+ if (this._showCaseTimings && result.startTime) {
120
+ const startDate = new Date(result.startTime);
121
+ const endDate = new Date(startDate.getTime() + (result.duration || 0));
122
+ const startLabel = formatTime(startDate);
123
+ const endLabel = formatTime(endDate);
124
+ const durationLabel = formatDuration(result.duration || 0);
125
+ const statusGlyph = result.status === 'passed' ? '✓' : result.status === 'skipped' ? '○' : '✗';
126
+ const logType = result.status === 'passed' ? _logger.Logger.SUCCESS_TYPE : result.status === 'skipped' ? _logger.Logger.INFO_TYPE : _logger.Logger.FAILURE_TYPE;
127
+ _logger.Logger.log(logType, `${statusGlyph} ${fullTitle} — ended at ${endLabel} (started ${startLabel}, took ${durationLabel})`);
128
+ const isFailure = result.status !== 'passed' && result.status !== 'skipped';
129
+ if (isFailure) {
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
+ this._caseTimings.push({
141
+ title: fullTitle,
142
+ fileName,
143
+ status: result.status,
144
+ retry: result.retry,
145
+ startTime: startDate.toISOString(),
146
+ endTime: endDate.toISOString(),
147
+ startTimeFormatted: startLabel,
148
+ endTimeFormatted: endLabel,
149
+ duration: result.duration || 0,
150
+ durationFormatted: durationLabel,
151
+ errorMessage: (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.message,
152
+ failedSteps: (result.steps || []).filter(step => step.error).map(step => {
153
+ var _step$error;
154
+ return {
155
+ title: step.title,
156
+ error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
157
+ };
158
+ }),
159
+ ...(runtime ? {
160
+ runtime: {
161
+ summary: runtime.summary,
162
+ logFile: runtime.file
163
+ }
164
+ } : {})
165
+ });
166
+ }
167
+ }
92
168
  const isFailure = result.status !== 'passed' && result.status !== 'skipped';
93
169
  if (isFailure && !this.failedProject) {
94
170
  const projectName = getProjectName(test);
@@ -145,6 +221,16 @@ class JSONSummaryReporter {
145
221
  reportPath
146
222
  } = (0, _readConfigFile.generateConfigFromFile)();
147
223
  (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, './', (0, _configFileNameProvider.getReportFileName)()), JSON.stringify(this, null, ' '));
224
+ if (this._showCaseTimings && this._caseTimings.length > 0) {
225
+ const timingsPayload = {
226
+ suiteStartedAt: new Date(this.startedAt).toISOString(),
227
+ suiteEndedAt: new Date(this.startedAt + this.durationInMS).toISOString(),
228
+ suiteDurationMs: this.durationInMS,
229
+ failedCount: this._caseTimings.length,
230
+ cases: this._caseTimings
231
+ };
232
+ (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, 'case-timings.json'), JSON.stringify(timingsPayload, null, ' '));
233
+ }
148
234
  }
149
235
  onExit() {
150
236
  // Update .last-run.json with aborted tests due to timing out or interruption
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@zohodesk/testinglibrary",
3
- "version": "4.1.6",
3
+ "version": "4.1.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@zohodesk/testinglibrary",
9
- "version": "4.1.6",
9
+ "version": "4.1.10",
10
10
  "hasInstallScript": true,
11
11
  "license": "ISC",
12
12
  "dependencies": {
@@ -4265,9 +4265,9 @@
4265
4265
  "peer": true
4266
4266
  },
4267
4267
  "node_modules/@types/node": {
4268
- "version": "26.0.0",
4269
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
4270
- "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
4268
+ "version": "26.1.0",
4269
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz",
4270
+ "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==",
4271
4271
  "license": "MIT",
4272
4272
  "dependencies": {
4273
4273
  "undici-types": "~8.3.0"
@@ -5518,9 +5518,9 @@
5518
5518
  }
5519
5519
  },
5520
5520
  "node_modules/baseline-browser-mapping": {
5521
- "version": "2.10.38",
5522
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
5523
- "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
5521
+ "version": "2.10.42",
5522
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz",
5523
+ "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==",
5524
5524
  "license": "Apache-2.0",
5525
5525
  "bin": {
5526
5526
  "baseline-browser-mapping": "dist/cli.cjs"
@@ -5544,9 +5544,9 @@
5544
5544
  }
5545
5545
  },
5546
5546
  "node_modules/brace-expansion": {
5547
- "version": "5.0.6",
5548
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
5549
- "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
5547
+ "version": "5.0.7",
5548
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
5549
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
5550
5550
  "license": "MIT",
5551
5551
  "peer": true,
5552
5552
  "dependencies": {
@@ -5695,9 +5695,9 @@
5695
5695
  }
5696
5696
  },
5697
5697
  "node_modules/caniuse-lite": {
5698
- "version": "1.0.30001799",
5699
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
5700
- "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
5698
+ "version": "1.0.30001802",
5699
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz",
5700
+ "integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==",
5701
5701
  "funding": [
5702
5702
  {
5703
5703
  "type": "opencollective",
@@ -6996,9 +6996,9 @@
6996
6996
  }
6997
6997
  },
6998
6998
  "node_modules/electron-to-chromium": {
6999
- "version": "1.5.378",
7000
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz",
7001
- "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==",
6999
+ "version": "1.5.387",
7000
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz",
7001
+ "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==",
7002
7002
  "license": "ISC"
7003
7003
  },
7004
7004
  "node_modules/emittery": {
@@ -7201,13 +7201,14 @@
7201
7201
  }
7202
7202
  },
7203
7203
  "node_modules/es-to-primitive": {
7204
- "version": "1.3.1",
7205
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.1.tgz",
7206
- "integrity": "sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g==",
7204
+ "version": "1.3.4",
7205
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz",
7206
+ "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==",
7207
7207
  "dev": true,
7208
7208
  "license": "MIT",
7209
7209
  "dependencies": {
7210
7210
  "es-abstract-get": "^1.0.0",
7211
+ "es-define-property": "^1.0.1",
7211
7212
  "es-errors": "^1.3.0",
7212
7213
  "is-callable": "^1.2.7",
7213
7214
  "is-date-object": "^1.1.0",
@@ -7264,9 +7265,9 @@
7264
7265
  }
7265
7266
  },
7266
7267
  "node_modules/eslint": {
7267
- "version": "10.5.0",
7268
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz",
7269
- "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==",
7268
+ "version": "10.6.0",
7269
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz",
7270
+ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==",
7270
7271
  "license": "MIT",
7271
7272
  "peer": true,
7272
7273
  "workspaces": [
@@ -10931,9 +10932,9 @@
10931
10932
  }
10932
10933
  },
10933
10934
  "node_modules/jest-util/node_modules/picomatch": {
10934
- "version": "4.0.4",
10935
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
10936
- "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
10935
+ "version": "4.0.5",
10936
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
10937
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
10937
10938
  "license": "MIT",
10938
10939
  "engines": {
10939
10940
  "node": ">=12"
@@ -11921,9 +11922,9 @@
11921
11922
  "license": "MIT"
11922
11923
  },
11923
11924
  "node_modules/js-yaml": {
11924
- "version": "3.14.2",
11925
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
11926
- "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
11925
+ "version": "3.15.0",
11926
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz",
11927
+ "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==",
11927
11928
  "license": "MIT",
11928
11929
  "dependencies": {
11929
11930
  "argparse": "^1.0.7",
@@ -12384,21 +12385,21 @@
12384
12385
  }
12385
12386
  },
12386
12387
  "node_modules/msw/node_modules/tldts": {
12387
- "version": "7.4.4",
12388
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.4.tgz",
12389
- "integrity": "sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==",
12388
+ "version": "7.4.6",
12389
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.6.tgz",
12390
+ "integrity": "sha512-rbP0Gyx8b3Ae9yO//CU2wbSnQNoQ66m1nJdSbSHmnwKwzkkz/u8mERYU8T2rmlmy+bJvRNn84yNCW8gYqox44Q==",
12390
12391
  "license": "MIT",
12391
12392
  "dependencies": {
12392
- "tldts-core": "^7.4.4"
12393
+ "tldts-core": "^7.4.6"
12393
12394
  },
12394
12395
  "bin": {
12395
12396
  "tldts": "bin/cli.js"
12396
12397
  }
12397
12398
  },
12398
12399
  "node_modules/msw/node_modules/tldts-core": {
12399
- "version": "7.4.4",
12400
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.4.tgz",
12401
- "integrity": "sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==",
12400
+ "version": "7.4.6",
12401
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.6.tgz",
12402
+ "integrity": "sha512-TkQNGJIhlEphpHCjKodMTSe23egUZr/g+flI2qkLgiJ/maAzSgXypSLRTNH3nCmqgayEmtcJBiLcfODSAr1xoA==",
12402
12403
  "license": "MIT"
12403
12404
  },
12404
12405
  "node_modules/msw/node_modules/tough-cookie": {
@@ -12483,9 +12484,9 @@
12483
12484
  "license": "MIT"
12484
12485
  },
12485
12486
  "node_modules/node-releases": {
12486
- "version": "2.0.49",
12487
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz",
12488
- "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==",
12487
+ "version": "2.0.50",
12488
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
12489
+ "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
12489
12490
  "license": "MIT",
12490
12491
  "engines": {
12491
12492
  "node": ">=18"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/testinglibrary",
3
- "version": "4.1.6",
3
+ "version": "4.1.10",
4
4
  "main": "./build/index.js",
5
5
  "scripts": {
6
6
  "postinstall": "node bin/postinstall.js",
@@ -28,9 +28,9 @@
28
28
  "@reportportal/agent-js-playwright": "5.2.2",
29
29
  "@testing-library/jest-dom": "6.9.1",
30
30
  "@testing-library/react": "12.1.5",
31
+ "@types/jest": "29.5.12",
31
32
  "@types/react": "16.14.70",
32
33
  "@types/react-dom": "16.9.25",
33
- "@types/jest": "29.5.12",
34
34
  "@zohodesk/unit-testing-framework": "0.0.35-experimental",
35
35
  "babel-jest": "29.7.0",
36
36
  "babel-plugin-transform-dynamic-import": "2.1.0",
@@ -63,6 +63,6 @@
63
63
  "@babel/preset-env": "7.29.0",
64
64
  "@babel/preset-typescript": "7.26.0",
65
65
  "jest-html-reporter": "4.3.0",
66
- "ts-jest": "29.3.2"
66
+ "ts-jest": "29.3.2"
67
67
  }
68
- }
68
+ }
@@ -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-06-03 18:32:48</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 (12)</div><div class="summary-passed ">12 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/buildInFixtures/__tests__/caseTimeout.test.js</div><div class="suite-time">0.367s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">resolveCaseTimeoutMs</div><div class="test-title">returns null when no timeout tag 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">resolveCaseTimeoutMs</div><div class="test-title">returns null when tags is not an array</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">resolveCaseTimeoutMs</div><div class="test-title">parses @timeout_&lt;n&gt; as seconds for Chrome (factor 1)</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">resolveCaseTimeoutMs</div><div class="test-title">applies Firefox 2x multiplier</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">resolveCaseTimeoutMs</div><div class="test-title">returns 0 (no timeout) when tag is @timeout_0, regardless of browser</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">resolveCaseTimeoutMs</div><div class="test-title">uses the first matching tag and logs a warning when multiple are 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">resolveCaseTimeoutMs</div><div class="test-title">ignores malformed timeout-like 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">resolveCaseTimeoutMs</div><div class="test-title">ignores non-string entries in tags</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">caseTimeout fixture</div><div class="test-title">is registered as an auto fixture</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">caseTimeout fixture</div><div class="test-title">calls testInfo.setTimeout with scaled value when tag matches</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">caseTimeout fixture</div><div class="test-title">does not call testInfo.setTimeout when no tag 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">caseTimeout fixture</div><div class="test-title">passes 0 through to testInfo.setTimeout for @timeout_0</div><div class="test-status">passed</div><div class="test-duration">0.001s</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 14:02: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 (41)</div><div class="summary-passed ">41 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.428s</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.002s</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 case and code when stderr reports DATA_GENERATION_ERROR</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 tool for a known tool pattern 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">tags case and code when stderr matches a case 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">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">classifyFailureDetails</div><div class="test-title">records the matching pattern for environment failures</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">classifyFailureDetails</div><div class="test-title">records the matching pattern for code failures from events</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">classifyFailureDetails</div><div class="test-title">records flaky outcome without a regex 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">classifyFailureDetails</div><div class="test-title">records tool pattern from stdio</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">classifyFailureDetails</div><div class="test-title">records case pattern from stdio and implies code</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">classifyFailureDetails</div><div class="test-title">keeps runtime-log code match when case also matches</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">classifyFailureDetails</div><div class="test-title">implies code from case when no runtime log 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">failure tag metadata attachment</div><div class="test-title">builds a small JSON attachment when tags are 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">failure tag metadata attachment</div><div class="test-title">returns null when no tags are 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">failure tag metadata attachment</div><div class="test-title">replaces an existing metadata attachment</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"> </div></div></div><div class="test-result passed"><div class="test-info"><div class="test-suitename">mergeFailureTags</div><div class="test-title">formats case and code failure tags with 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">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">0.001s</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__/logCollector.test.js</div><div class="suite-time">0.049s</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"> </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">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">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">flushes captured events to a jsonl file</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 flushToFile 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><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__/runtimeLogReader.test.js</div><div class="suite-time">0.043s</div></summary><div class="suite-tests"><div class="test-result passed"><div class="test-info"><div class="test-suitename">readRuntimeLogEventsForResult</div><div class="test-title">reads project-side runtime log for a failing result</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">readRuntimeLogEventsForResult</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">readRuntimeLogEventsForResult</div><div class="test-title">returns an empty array when the runtime log file is missing</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">readRuntimeLogEventsForResult</div><div class="test-title">returns an empty array when logsDir is missing</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">readRuntimeLogEvents</div><div class="test-title">reads events from a jsonl file path</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">readRuntimeLogEvents</div><div class="test-title">returns an empty array for a missing file</div><div class="test-status">passed</div><div class="test-duration"> </div></div></div></div></details></main></body></html>