@zohodesk/testinglibrary 0.0.62-n20-experimental → 0.0.64-n20-experimental

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,19 @@
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
+ reportPath,
14
+ runtimeLogs = {}
10
15
  } = (0, _readConfigFile.generateConfigFromFile)();
16
+ const LOGS_DIR = _path.default.join(reportPath, 'runtime-logs');
11
17
  async function performDefaultContextSteps({
12
18
  context
13
19
  }) {
@@ -20,13 +26,37 @@ async function performDefaultContextSteps({
20
26
  var _default = exports.default = {
21
27
  context: async ({
22
28
  context
23
- }, use) => {
29
+ }, use, testInfo) => {
24
30
  await context.addInitScript(() =>
25
31
  // eslint-disable-next-line no-undef
26
32
  window.localStorage.setItem('isDnBannerHide', true));
27
33
  await performDefaultContextSteps({
28
34
  context
29
35
  });
30
- await use(context);
36
+ const collector = new _logCollector.LogCollector(runtimeLogs.limits);
37
+ collector.attachToContext(context);
38
+ testInfo.runtimeLogs = collector;
39
+ try {
40
+ await use(context);
41
+ } finally {
42
+ collector.detach();
43
+ const failureOnly = runtimeLogs.mode === 'failure-only';
44
+ const isFailure = testInfo.status && testInfo.status !== testInfo.expectedStatus;
45
+ const shouldFlush = !failureOnly || isFailure;
46
+ const summary = collector.toSummary();
47
+ let filePath = null;
48
+ if (shouldFlush) {
49
+ const base = `w${testInfo.workerIndex}-${testInfo.testId}-${testInfo.retry}`;
50
+ filePath = collector.flushToFile(LOGS_DIR, base);
51
+ }
52
+ await testInfo.attach('runtime-log', {
53
+ contentType: 'application/json',
54
+ body: Buffer.from(JSON.stringify({
55
+ schemaVersion: 1,
56
+ summary,
57
+ file: filePath ? _path.default.relative(reportPath, filePath) : null
58
+ }))
59
+ });
60
+ }
31
61
  }
32
62
  };
@@ -11,6 +11,5 @@ const stage = (0, _ConfigurationHelper.getRunStage)();
11
11
  class ReporterConstants {
12
12
  static DEFAULT_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/playwright-test-results.json`;
13
13
  static LAST_RUN_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/.last-run.json`;
14
- static DEFAULT_UNIT_TEST_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/unit-test/unit_reports/report.html`;
15
14
  }
16
15
  exports.default = ReporterConstants;
@@ -0,0 +1,154 @@
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 _fs = _interopRequireDefault(require("fs"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ const DEFAULT_LIMITS = {
11
+ maxEvents: 2000,
12
+ maxBodyBytes: 0,
13
+ captureHeaders: false,
14
+ ignoreUrlPatterns: [/\.(png|jpe?g|gif|svg|webp|woff2?|ttf|css|map)(\?|$)/i, /google-analytics\.com|googletagmanager\.com|sentry\.io/i]
15
+ };
16
+ class LogCollector {
17
+ constructor(opts = {}) {
18
+ this.limits = {
19
+ ...DEFAULT_LIMITS,
20
+ ...opts
21
+ };
22
+ this.events = [];
23
+ this.truncated = false;
24
+ this.counts = {
25
+ console: 0,
26
+ consoleError: 0,
27
+ pageError: 0,
28
+ request: 0,
29
+ response: 0,
30
+ failed: 0,
31
+ crash: 0
32
+ };
33
+ this._bound = [];
34
+ }
35
+ _push(evt) {
36
+ if (this.events.length >= this.limits.maxEvents) {
37
+ this.truncated = true;
38
+ this.events.shift();
39
+ }
40
+ this.events.push({
41
+ ts: Date.now(),
42
+ ...evt
43
+ });
44
+ }
45
+ _shouldIgnore(url) {
46
+ return this.limits.ignoreUrlPatterns.some(re => re.test(url));
47
+ }
48
+ _bind(target, event, handler) {
49
+ target.on(event, handler);
50
+ this._bound.push([target, event, handler]);
51
+ }
52
+ attachToContext(context) {
53
+ context.pages().forEach(p => this._attachPage(p));
54
+ this._bind(context, 'page', p => this._attachPage(p));
55
+ this._bind(context, 'request', req => {
56
+ if (this._shouldIgnore(req.url())) return;
57
+ this.counts.request++;
58
+ this._push({
59
+ kind: 'request',
60
+ method: req.method(),
61
+ url: req.url(),
62
+ resourceType: req.resourceType(),
63
+ ...(this.limits.captureHeaders ? {
64
+ headers: req.headers()
65
+ } : {})
66
+ });
67
+ });
68
+ this._bind(context, 'response', res => {
69
+ const url = res.url();
70
+ if (this._shouldIgnore(url)) return;
71
+ this.counts.response++;
72
+ if (res.status() >= 400) {
73
+ this._push({
74
+ kind: 'response',
75
+ status: res.status(),
76
+ method: res.request().method(),
77
+ url,
78
+ fromCache: res.fromServiceWorker()
79
+ });
80
+ }
81
+ });
82
+ this._bind(context, 'requestfailed', req => {
83
+ var _req$failure;
84
+ this.counts.failed++;
85
+ this._push({
86
+ kind: 'requestfailed',
87
+ method: req.method(),
88
+ url: req.url(),
89
+ failure: (_req$failure = req.failure()) === null || _req$failure === void 0 ? void 0 : _req$failure.errorText,
90
+ resourceType: req.resourceType()
91
+ });
92
+ });
93
+ }
94
+ _attachPage(page) {
95
+ this._bind(page, 'console', msg => {
96
+ const type = msg.type();
97
+ this.counts.console++;
98
+ if (type === 'error') this.counts.consoleError++;
99
+ if (type !== 'error' && type !== 'warning') return;
100
+ this._push({
101
+ kind: 'console',
102
+ level: type,
103
+ text: msg.text(),
104
+ location: msg.location()
105
+ });
106
+ });
107
+ this._bind(page, 'pageerror', err => {
108
+ this.counts.pageError++;
109
+ this._push({
110
+ kind: 'pageerror',
111
+ name: err.name,
112
+ message: err.message,
113
+ stack: err.stack
114
+ });
115
+ });
116
+ this._bind(page, 'crash', () => {
117
+ this.counts.crash++;
118
+ this._push({
119
+ kind: 'crash',
120
+ url: page.url()
121
+ });
122
+ });
123
+ }
124
+ detach() {
125
+ for (const [target, event, handler] of this._bound) {
126
+ try {
127
+ target.removeListener(event, handler);
128
+ } catch {
129
+ // context may already be closed
130
+ }
131
+ }
132
+ this._bound.length = 0;
133
+ }
134
+ toSummary() {
135
+ return {
136
+ ...this.counts,
137
+ total: this.events.length,
138
+ truncated: this.truncated
139
+ };
140
+ }
141
+ flushToFile(dir, baseName) {
142
+ if (this.events.length === 0) return null;
143
+ _fs.default.mkdirSync(dir, {
144
+ recursive: true
145
+ });
146
+ const file = _path.default.join(dir, `${baseName}.jsonl`);
147
+ const stream = _fs.default.createWriteStream(file);
148
+ for (const evt of this.events) stream.write(JSON.stringify(evt) + '\n');
149
+ stream.end();
150
+ this.events.length = 0;
151
+ return file;
152
+ }
153
+ }
154
+ exports.LogCollector = LogCollector;
@@ -55,7 +55,8 @@ function getDefaultConfig() {
55
55
  featureFilesFolder: 'feature-files',
56
56
  stepDefinitionsFolder: 'steps',
57
57
  testSetup: {},
58
- editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
58
+ editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
59
+ showCaseTimings: true
59
60
  };
60
61
  }
61
62
  function combineDefaultConfigWithUserConfig(userConfiguration) {
@@ -113,6 +114,7 @@ function combineDefaultConfigWithUserConfig(userConfiguration) {
113
114
  * @property {string} testIdAttribute: Change the default data-testid attribute. configure what attribute to search while calling getByTestId
114
115
  * @property {Array} editionOrder: Order in the form of larger editions in the back. Edition with the most privelages should be last
115
116
  * @property {testSetupConfig} testSetup: Specify page and context functions that will be called while intilaizing fixtures.
117
+ * @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.
116
118
  */
117
119
 
118
120
  /**
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = generateReport;
8
+ var _child_process = require("child_process");
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _logger = require("../../utils/logger");
11
+ var _rootPath = require("../../utils/rootPath");
12
+ var _readConfigFile = require("./readConfigFile");
13
+ async function generateReport() {
14
+ // await preProcessReport()
15
+ const userArgs = process.argv.slice(3);
16
+ const playwrightPath = _path.default.resolve((0, _rootPath.getExecutableBinaryPath)('playwright'));
17
+ const command = playwrightPath;
18
+ const {
19
+ reportPath: htmlPath
20
+ } = (0, _readConfigFile.generateConfigFromFile)();
21
+ const args = ['show-report', htmlPath].concat(userArgs);
22
+ const childProcess = (0, _child_process.spawn)(command, args, {
23
+ stdio: 'inherit'
24
+ });
25
+ childProcess.on('error', error => {
26
+ _logger.Logger.log(_logger.Logger.FAILURE_TYPE, error);
27
+ });
28
+ childProcess.on('exit', (code, signal) => {
29
+ _logger.Logger.log(_logger.Logger.FAILURE_TYPE, `Child Process Exited with Code ${code} and Signal ${signal}`);
30
+ process.exit();
31
+ });
32
+ process.on('exit', () => {
33
+ _logger.Logger.log(_logger.Logger.INFO_TYPE, 'Terminating Playwright Process...');
34
+ childProcess.kill();
35
+ return;
36
+ });
37
+ process.on('SIGINT', () => {
38
+ _logger.Logger.log(_logger.Logger.INFO_TYPE, 'Cleaning up...');
39
+ childProcess.kill();
40
+ process.exit();
41
+ });
42
+ }
@@ -46,7 +46,6 @@ class SpawnRunner extends _Runner.default {
46
46
  }
47
47
  _logger.Logger.log(_logger.Logger.INFO_TYPE, `Preprocessing failed (attempt ${attempt} of ${maxRetries + 1}). Retrying in ${delayMs}ms...`);
48
48
  await new Promise(resolve => setTimeout(resolve, delayMs));
49
- process.exitCode = 1; // Set exit code to 1 to indicate failure, but allow retries to proceed
50
49
  }
51
50
  }
52
51
  }
@@ -12,6 +12,22 @@ var _readConfigFile = require("../readConfigFile");
12
12
  var _logger = require("../../../utils/logger");
13
13
  var _configFileNameProvider = require("../helpers/configFileNameProvider");
14
14
  var _mergeAbortedTests = _interopRequireDefault(require("../reporter/helpers/mergeAbortedTests"));
15
+ function formatTime(date) {
16
+ return date.toLocaleTimeString('en-US', {
17
+ hour: 'numeric',
18
+ minute: '2-digit',
19
+ second: '2-digit',
20
+ hour12: true
21
+ });
22
+ }
23
+ function formatDuration(ms) {
24
+ if (ms < 1000) return `${ms}ms`;
25
+ const totalSeconds = ms / 1000;
26
+ if (totalSeconds < 60) return `${totalSeconds.toFixed(2)}s`;
27
+ const minutes = Math.floor(totalSeconds / 60);
28
+ const seconds = (totalSeconds - minutes * 60).toFixed(2);
29
+ return `${minutes}m ${seconds}s`;
30
+ }
15
31
  class JSONSummaryReporter {
16
32
  constructor() {
17
33
  this.durationInMS = -1;
@@ -26,11 +42,22 @@ class JSONSummaryReporter {
26
42
  this.failedSteps = [];
27
43
  this.status = 'unknown';
28
44
  this.startedAt = 0;
29
- this._open = (0, _readConfigFile.generateConfigFromFile)().openReportOn;
45
+ const config = (0, _readConfigFile.generateConfigFromFile)();
46
+ this._open = config.openReportOn;
47
+ this._showCaseTimings = config.showCaseTimings !== false;
48
+ this._caseTimings = [];
30
49
  }
31
50
  onBegin() {
32
51
  this.startedAt = Date.now();
33
52
  }
53
+ onTestBegin(test) {
54
+ if (!this._showCaseTimings) return;
55
+ const {
56
+ fullTitle
57
+ } = this.getTitle(test);
58
+ const startedAtLabel = formatTime(new Date());
59
+ _logger.Logger.log(_logger.Logger.INFO_TYPE, `▶ ${fullTitle} — started at ${startedAtLabel}`);
60
+ }
34
61
  getTitle(test) {
35
62
  const title = [];
36
63
  const fileName = [];
@@ -76,6 +103,55 @@ class JSONSummaryReporter {
76
103
  this[status].push(fileName);
77
104
  }
78
105
  this[status].push(fileName);
106
+ if (this._showCaseTimings && result.startTime) {
107
+ const startDate = new Date(result.startTime);
108
+ const endDate = new Date(startDate.getTime() + (result.duration || 0));
109
+ const startLabel = formatTime(startDate);
110
+ const endLabel = formatTime(endDate);
111
+ const durationLabel = formatDuration(result.duration || 0);
112
+ const statusGlyph = result.status === 'passed' ? '✓' : result.status === 'skipped' ? '○' : '✗';
113
+ const logType = result.status === 'passed' ? _logger.Logger.SUCCESS_TYPE : result.status === 'skipped' ? _logger.Logger.INFO_TYPE : _logger.Logger.FAILURE_TYPE;
114
+ _logger.Logger.log(logType, `${statusGlyph} ${fullTitle} — ended at ${endLabel} (started ${startLabel}, took ${durationLabel})`);
115
+ const isFailure = result.status !== 'passed' && result.status !== 'skipped';
116
+ if (isFailure) {
117
+ var _result$error;
118
+ const runtimeAttachment = (result.attachments || []).find(a => a.name === 'runtime-log' && a.body);
119
+ let runtime = null;
120
+ if (runtimeAttachment) {
121
+ try {
122
+ runtime = JSON.parse(runtimeAttachment.body.toString('utf8'));
123
+ } catch {
124
+ // malformed attachment; skip
125
+ }
126
+ }
127
+ this._caseTimings.push({
128
+ title: fullTitle,
129
+ fileName,
130
+ status: result.status,
131
+ retry: result.retry,
132
+ startTime: startDate.toISOString(),
133
+ endTime: endDate.toISOString(),
134
+ startTimeFormatted: startLabel,
135
+ endTimeFormatted: endLabel,
136
+ duration: result.duration || 0,
137
+ durationFormatted: durationLabel,
138
+ errorMessage: (_result$error = result.error) === null || _result$error === void 0 ? void 0 : _result$error.message,
139
+ failedSteps: (result.steps || []).filter(step => step.error).map(step => {
140
+ var _step$error;
141
+ return {
142
+ title: step.title,
143
+ error: (_step$error = step.error) === null || _step$error === void 0 ? void 0 : _step$error.message
144
+ };
145
+ }),
146
+ ...(runtime ? {
147
+ runtime: {
148
+ summary: runtime.summary,
149
+ logFile: runtime.file
150
+ }
151
+ } : {})
152
+ });
153
+ }
154
+ }
79
155
  }
80
156
  onError(error) {
81
157
  this.errored.push({
@@ -122,6 +198,16 @@ class JSONSummaryReporter {
122
198
  reportPath
123
199
  } = (0, _readConfigFile.generateConfigFromFile)();
124
200
  (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, './', (0, _configFileNameProvider.getReportFileName)()), JSON.stringify(this, null, ' '));
201
+ if (this._showCaseTimings && this._caseTimings.length > 0) {
202
+ const timingsPayload = {
203
+ suiteStartedAt: new Date(this.startedAt).toISOString(),
204
+ suiteEndedAt: new Date(this.startedAt + this.durationInMS).toISOString(),
205
+ suiteDurationMs: this.durationInMS,
206
+ failedCount: this._caseTimings.length,
207
+ cases: this._caseTimings
208
+ };
209
+ (0, _fileUtils.writeFileContents)(_path.default.join(reportPath, 'case-timings.json'), JSON.stringify(timingsPayload, null, ' '));
210
+ }
125
211
  }
126
212
  onExit() {
127
213
  // Update .last-run.json with aborted tests due to timing out or interruption
package/build/lib/cli.js CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
  var _testRunner = _interopRequireDefault(require("../core/playwright/test-runner"));
5
- var _unitTestingFramework = require("@zohodesk/unit-testing-framework");
6
- var _PlaywrightReporter = _interopRequireDefault(require("../core/playwright/reporter/PlaywrightReporter"));
7
- var _UnitReporter = _interopRequireDefault(require("../core/playwright/reporter/UnitReporter"));
5
+ var _reportGenerator = _interopRequireDefault(require("../core/playwright/report-generator"));
8
6
  var _codegen = _interopRequireDefault(require("../core/playwright/codegen"));
9
7
  var _logger = require("../utils/logger");
10
8
  var _setupProject = _interopRequireDefault(require("../setup-folder-structure/setupProject"));
@@ -13,25 +11,15 @@ var _clearCaches = _interopRequireDefault(require("../core/playwright/clear-cach
13
11
  var _helper = _interopRequireDefault(require("../setup-folder-structure/helper"));
14
12
  var _parseUserArgs = _interopRequireDefault(require("../core/playwright/helpers/parseUserArgs"));
15
13
  var _validateFeature = _interopRequireDefault(require("../core/playwright/validateFeature"));
16
- var _commonUtils = require("../utils/commonUtils");
14
+ // import createJestRunner from '../core/jest/runner/jest-runner';
15
+
17
16
  const [,, option, ...otherOptions] = process.argv;
18
17
  switch (option) {
19
18
  case 'test':
20
19
  {
21
20
  _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Running Tests..');
22
21
  (0, _testRunner.default)();
23
- break;
24
- }
25
- case 'unit-test':
26
- {
27
- const testFile = process.argv[3];
28
- _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Running Unit Tests..');
29
- const options = {};
30
- if (testFile) {
31
- _logger.Logger.log(_logger.Logger.INFO_TYPE, `Filtering tests with pattern: ${testFile}`);
32
- options.testPathPattern = testFile;
33
- }
34
- (0, _unitTestingFramework.createJestRunner)(options);
22
+ //createJestRunner();
35
23
  break;
36
24
  }
37
25
  case 'validate':
@@ -52,14 +40,9 @@ switch (option) {
52
40
  }
53
41
  case 'report':
54
42
  {
55
- _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Generating UAT Reports...');
56
- _PlaywrightReporter.default.generate();
57
- break;
58
- }
59
- case 'ut-report':
60
- {
61
- _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Generating Unit Test Reports...');
62
- _UnitReporter.default.generate();
43
+ // console.log('\x1b[36mGenerating Reports...\x1b[0m');
44
+ _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Generating Reports...');
45
+ (0, _reportGenerator.default)();
63
46
  break;
64
47
  }
65
48
  case 'codegen':
@@ -86,12 +69,6 @@ switch (option) {
86
69
  (0, _clearCaches.default)();
87
70
  break;
88
71
  }
89
- case 'stepsGenerator':
90
- {
91
- _logger.Logger.log(_logger.Logger.SUCCESS_TYPE, 'Created agents and prompts md files under .github folder...');
92
- (0, _commonUtils.copyGithubFolder)();
93
- break;
94
- }
95
72
  case 'help':
96
73
  default:
97
74
  {
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.copyCommonSpecs = copyCommonSpecs;
8
- exports.copyGithubFolder = copyGithubFolder;
9
8
  var _fileUtils = require("./fileUtils");
10
9
  var _path = _interopRequireDefault(require("path"));
11
10
  var _configConstants = _interopRequireDefault(require("../core/playwright/constants/configConstants"));
@@ -18,12 +17,4 @@ function copyCommonSpecs() {
18
17
  const destDirectory = _path.default.resolve(process.cwd(), _configConstants.default.TEST_SLICE_FOLDER, stage, 'modules', '.testingLib-common');
19
18
  (0, _fileUtils.deleteFolder)(destDirectory);
20
19
  (0, _fileUtils.copyDirectory)(commonSpecPath, destDirectory);
21
- }
22
- function copyGithubFolder() {
23
- const libraryPath = require.resolve("@zohodesk/testinglibrary");
24
- // libraryPath will be build/index.js, go two levels up to reach the package root where .github lives
25
- const githubSrcPath = _path.default.resolve(libraryPath, '../../', '.github');
26
- const destDirectory = _path.default.resolve(process.cwd(), '../../', '.github');
27
- (0, _fileUtils.deleteFolder)(destDirectory);
28
- (0, _fileUtils.copyDirectory)(githubSrcPath, destDirectory);
29
20
  }