datagrok-tools 4.14.72 → 5.0.0
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/CHANGELOG.md +6 -0
- package/bin/commands/test.js +105 -70
- package/bin/utils/test-utils.js +393 -128
- package/package.json +3 -4
package/CHANGELOG.md
CHANGED
package/bin/commands/test.js
CHANGED
|
@@ -14,12 +14,11 @@ var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
|
14
14
|
var Papa = _interopRequireWildcard(require("papaparse"));
|
|
15
15
|
var _testUtils = _interopRequireWildcard(require("../utils/test-utils"));
|
|
16
16
|
var testUtils = _testUtils;
|
|
17
|
-
var _orderFunctions = require("../utils/order-functions");
|
|
18
17
|
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); }
|
|
19
18
|
/* eslint-disable max-len */
|
|
20
19
|
|
|
21
20
|
const testInvocationTimeout = 3600000;
|
|
22
|
-
const availableCommandOptions = ['host', 'package', 'csv', 'gui', 'catchUnhandled', 'platform', 'core', 'report', 'skip-build', 'skip-publish', 'path', 'record', 'verbose', 'benchmark', 'category', 'test', 'stress-test', 'link', 'tag', 'ci-cd', 'debug'];
|
|
21
|
+
const availableCommandOptions = ['host', 'package', 'csv', 'gui', 'catchUnhandled', 'platform', 'core', 'report', 'skip-build', 'skip-publish', 'path', 'record', 'verbose', 'benchmark', 'category', 'test', 'stress-test', 'link', 'tag', 'ci-cd', 'debug', 'no-retry'];
|
|
23
22
|
const curDir = process.cwd();
|
|
24
23
|
const grokDir = _path.default.join(_os.default.homedir(), '.grok');
|
|
25
24
|
const confPath = _path.default.join(grokDir, 'config.yaml');
|
|
@@ -47,7 +46,11 @@ async function test(args) {
|
|
|
47
46
|
}
|
|
48
47
|
process.env.TARGET_PACKAGE = packageName;
|
|
49
48
|
const res = await runTesting(args);
|
|
50
|
-
if (args.csv)
|
|
49
|
+
if (args.csv) {
|
|
50
|
+
res.csv = (0, _testUtils.addColumnToCsv)(res.csv, 'stress_test', args['stress-test'] ?? false);
|
|
51
|
+
res.csv = (0, _testUtils.addColumnToCsv)(res.csv, 'benchmark', args.benchmark ?? false);
|
|
52
|
+
(0, _testUtils.saveCsvResults)([res.csv], csvReportDir);
|
|
53
|
+
}
|
|
51
54
|
(0, _testUtils.printBrowsersResult)(res, args.verbose);
|
|
52
55
|
if (res.failed) {
|
|
53
56
|
if (res.verboseFailed === 'Package not found') testUtils.exitWithCode(0);
|
|
@@ -60,62 +63,51 @@ function isArgsValid(args) {
|
|
|
60
63
|
if (args['_'].length > 1 || options.length > availableCommandOptions.length || options.length > 0 && !options.every(op => availableCommandOptions.includes(op))) return false;
|
|
61
64
|
return true;
|
|
62
65
|
}
|
|
66
|
+
|
|
67
|
+
// Retry state - persists across test runs in the session
|
|
68
|
+
const retriedTests = new Set();
|
|
69
|
+
let totalRetries = 0;
|
|
70
|
+
const MAX_RETRIES_PER_SESSION = 10;
|
|
71
|
+
let retryEnabled = true;
|
|
63
72
|
async function runTesting(args) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (args['stress-test'] || args.benchmark) {
|
|
68
|
-
for (const element of loadedTests) {
|
|
69
|
-
if (args.benchmark && !element.options.benchmark || args['stress-test'] && !element.options.stressTest) continue;
|
|
70
|
-
testsObj.push(element);
|
|
71
|
-
}
|
|
72
|
-
} else testsObj = loadedTests;
|
|
73
|
-
const parsed = (0, _orderFunctions.setAlphabeticalOrder)(testsObj, 1, 1);
|
|
74
|
-
if (parsed.length == 0) {
|
|
75
|
-
return {
|
|
76
|
-
failed: true,
|
|
77
|
-
error: '',
|
|
78
|
-
verbosePassed: 'Package not found',
|
|
79
|
-
verboseSkipped: 'Package not found',
|
|
80
|
-
verboseFailed: 'Package not found',
|
|
81
|
-
passedAmount: 0,
|
|
82
|
-
skippedAmount: 0,
|
|
83
|
-
failedAmount: 0,
|
|
84
|
-
csv: ''
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
let organized = parsed[0].map(testObj => ({
|
|
88
|
-
package: testObj.packageName,
|
|
73
|
+
retryEnabled = args['retry'] ?? true;
|
|
74
|
+
let organized = {
|
|
75
|
+
package: process.env.TARGET_PACKAGE ?? '',
|
|
89
76
|
params: {
|
|
90
|
-
category:
|
|
91
|
-
test:
|
|
77
|
+
category: args.category ?? '',
|
|
78
|
+
test: args.test ?? '',
|
|
92
79
|
options: {
|
|
93
80
|
catchUnhandled: args.catchUnhandled,
|
|
94
81
|
report: args.report
|
|
95
82
|
}
|
|
96
83
|
}
|
|
97
|
-
}
|
|
98
|
-
const filtered = [];
|
|
99
|
-
const categoryRegex = new RegExp(`${args.category?.replaceAll(' ', '')}.*`);
|
|
100
|
-
if (args.category) {
|
|
101
|
-
for (const element of organized) {
|
|
102
|
-
if (categoryRegex.test(element.params.category.replaceAll(' ', ''))) {
|
|
103
|
-
if (element.params.test === args.test || !args.test) filtered.push(element);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
organized = filtered;
|
|
107
|
-
}
|
|
108
|
-
if (args.verbose) {
|
|
109
|
-
console.log(organized);
|
|
110
|
-
console.log(`Tests total: ${organized.length}`);
|
|
111
|
-
}
|
|
84
|
+
};
|
|
112
85
|
color.info('Starting tests...');
|
|
113
86
|
const testsResults = [];
|
|
114
87
|
let r;
|
|
115
88
|
let browserId = 1;
|
|
89
|
+
let retrySupported = undefined; // Will be set after first run
|
|
90
|
+
let browserSession = undefined;
|
|
116
91
|
await (0, _testUtils.timeout)(async () => {
|
|
117
|
-
|
|
118
|
-
|
|
92
|
+
let shouldRetry = true;
|
|
93
|
+
let currentSkipToCategory = undefined;
|
|
94
|
+
let currentSkipToTest = undefined;
|
|
95
|
+
let oneLastTry = false;
|
|
96
|
+
while (shouldRetry || oneLastTry) {
|
|
97
|
+
shouldRetry = false;
|
|
98
|
+
oneLastTry = false;
|
|
99
|
+
// On first run, assume retry is supported; after first run, use actual value
|
|
100
|
+
const useRetry = retryEnabled && (retrySupported === undefined || retrySupported);
|
|
101
|
+
const testParams = {
|
|
102
|
+
...organized,
|
|
103
|
+
params: {
|
|
104
|
+
...organized.params,
|
|
105
|
+
skipToCategory: currentSkipToCategory,
|
|
106
|
+
skipToTest: currentSkipToTest,
|
|
107
|
+
returnOnFail: useRetry
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
r = await (0, _testUtils.runBrowser)(testParams, {
|
|
119
111
|
benchmark: args.benchmark ?? false,
|
|
120
112
|
stressTest: args['stress-test'] ?? false,
|
|
121
113
|
catchUnhandled: args.catchUnhandled ?? false,
|
|
@@ -124,34 +116,77 @@ async function runTesting(args) {
|
|
|
124
116
|
report: args.report ?? false,
|
|
125
117
|
verbose: args.verbose ?? false,
|
|
126
118
|
ciCd: args['ci-cd'] ?? false,
|
|
127
|
-
stopOnTimeout:
|
|
128
|
-
debug: args['debug'] ?? false
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (testsToReproduce.length > 0) {
|
|
137
|
-
const reproduced = await reproducedTest(args, testsToReproduce);
|
|
138
|
-
for (const test of testsToReproduce) {
|
|
139
|
-
const reproducedTest = reproduced.get(test);
|
|
140
|
-
if (reproducedTest && !reproducedTest.failed) r = await updateResultsByReproduced(r, reproducedTest, test);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
r.csv = (0, _testUtils.addColumnToCsv)(r.csv, 'stress_test', args['stress-test'] ?? false);
|
|
144
|
-
r.csv = (0, _testUtils.addColumnToCsv)(r.csv, 'benchmark', args.benchmark ?? false);
|
|
145
|
-
testsResults.push(r);
|
|
146
|
-
organized = testsLeft;
|
|
147
|
-
browserId++;
|
|
119
|
+
stopOnTimeout: false,
|
|
120
|
+
debug: args['debug'] ?? false,
|
|
121
|
+
skipToCategory: currentSkipToCategory,
|
|
122
|
+
skipToTest: currentSkipToTest,
|
|
123
|
+
keepBrowserOpen: useRetry // Keep browser open if retry is enabled
|
|
124
|
+
}, browserId, testInvocationTimeout, browserSession);
|
|
125
|
+
|
|
126
|
+
// Store browser session for potential reuse
|
|
127
|
+
if (r.browserSession) browserSession = r.browserSession;
|
|
148
128
|
if (r.error) {
|
|
149
129
|
console.log(`\nexecution error:`);
|
|
150
130
|
console.log(r.error);
|
|
151
|
-
|
|
131
|
+
// Close browser on error
|
|
132
|
+
if (browserSession?.browser) await browserSession.browser.close();
|
|
133
|
+
// Add the failed result before returning
|
|
134
|
+
testsResults.push(r);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Check retry support from first run result
|
|
138
|
+
if (useRetry && r.failed && r.lastFailedTest && retrySupported === undefined) {
|
|
139
|
+
retrySupported = r.retrySupported === true;
|
|
140
|
+
if (!retrySupported) color.warn('Retry not supported: test() function does not have skipToCategory parameter');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if we should retry on failure
|
|
144
|
+
if (useRetry && r.failed && r.lastFailedTest && retryEnabled && retrySupported) {
|
|
145
|
+
const testKey = `${r.lastFailedTest.category}::${r.lastFailedTest.test}`;
|
|
146
|
+
if (totalRetries >= MAX_RETRIES_PER_SESSION) {
|
|
147
|
+
color.warn(`Maximum retries (${MAX_RETRIES_PER_SESSION}) reached. Disabling retries.`);
|
|
148
|
+
retryEnabled = false;
|
|
149
|
+
oneLastTry = true;
|
|
150
|
+
} else {
|
|
151
|
+
// Retry this test
|
|
152
|
+
retriedTests.add(testKey);
|
|
153
|
+
totalRetries++;
|
|
154
|
+
console.log('Refreshing page for retry...');
|
|
155
|
+
color.info(`Retrying from "${r.lastFailedTest.category}: ${r.lastFailedTest.test}" (retry ${totalRetries}/${MAX_RETRIES_PER_SESSION})...`);
|
|
156
|
+
|
|
157
|
+
// Store results from previous run
|
|
158
|
+
testsResults.push(r);
|
|
159
|
+
|
|
160
|
+
// Set skip params for next run
|
|
161
|
+
currentSkipToCategory = r.lastFailedTest.category;
|
|
162
|
+
currentSkipToTest = r.lastFailedTest.test;
|
|
163
|
+
shouldRetry = true;
|
|
164
|
+
browserId++;
|
|
165
|
+
}
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
if (!shouldRetry) {
|
|
168
|
+
testsResults.push(r);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Close browser after all retries are done
|
|
173
|
+
if (browserSession?.browser) await browserSession.browser.close();
|
|
154
174
|
}, testInvocationTimeout);
|
|
175
|
+
|
|
176
|
+
// Handle empty results (shouldn't happen but safety check)
|
|
177
|
+
if (testsResults.length === 0) {
|
|
178
|
+
return {
|
|
179
|
+
failed: true,
|
|
180
|
+
verbosePassed: '',
|
|
181
|
+
verboseSkipped: '',
|
|
182
|
+
verboseFailed: 'No test results collected',
|
|
183
|
+
passedAmount: 0,
|
|
184
|
+
skippedAmount: 0,
|
|
185
|
+
failedAmount: 0,
|
|
186
|
+
csv: '',
|
|
187
|
+
error: 'No test results collected'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
155
190
|
return await (0, _testUtils.mergeBrowsersResults)(testsResults);
|
|
156
191
|
}
|
|
157
192
|
async function reproducedTest(args, testsToReproduce) {
|
package/bin/utils/test-utils.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.getBrowserPage = getBrowserPage;
|
|
|
14
14
|
exports.getDevKey = getDevKey;
|
|
15
15
|
exports.getToken = getToken;
|
|
16
16
|
exports.getWebUrl = getWebUrl;
|
|
17
|
+
exports.getWebUrlFromPage = getWebUrlFromPage;
|
|
17
18
|
exports.loadPackage = loadPackage;
|
|
18
19
|
exports.loadPackages = loadPackages;
|
|
19
20
|
exports.loadTestsList = loadTestsList;
|
|
@@ -61,6 +62,12 @@ async function getWebUrl(url, token) {
|
|
|
61
62
|
const json = await response.json();
|
|
62
63
|
return json.settings.webRoot;
|
|
63
64
|
}
|
|
65
|
+
function getWebUrlFromPage(page) {
|
|
66
|
+
const url = page.url();
|
|
67
|
+
// Extract the base URL (protocol + host)
|
|
68
|
+
const urlObj = new URL(url);
|
|
69
|
+
return `${urlObj.protocol}//${urlObj.host}`;
|
|
70
|
+
}
|
|
64
71
|
function getDevKey(hostKey) {
|
|
65
72
|
const config = _jsYaml.default.load(_fs.default.readFileSync(confPath, 'utf8'));
|
|
66
73
|
let host = hostKey == '' ? config.default : hostKey;
|
|
@@ -310,23 +317,26 @@ function addLogsToFile(filePath, stringToSave) {
|
|
|
310
317
|
_fs.default.appendFileSync(filePath, `${stringToSave}`);
|
|
311
318
|
}
|
|
312
319
|
function printBrowsersResult(browserResult, verbose = false) {
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
// Skip detailed summary if modernOutput was used (already printed per-category)
|
|
321
|
+
if (!browserResult.modernOutput) {
|
|
322
|
+
if (verbose) {
|
|
323
|
+
if ((browserResult.passedAmount ?? 0) > 0 && (browserResult.verbosePassed ?? []).length > 0) {
|
|
324
|
+
console.log('Passed: ');
|
|
325
|
+
console.log(browserResult.verbosePassed);
|
|
326
|
+
}
|
|
327
|
+
if ((browserResult.skippedAmount ?? 0) > 0 && (browserResult.verboseSkipped ?? []).length > 0) {
|
|
328
|
+
console.log('Skipped: ');
|
|
329
|
+
console.log(browserResult.verboseSkipped);
|
|
330
|
+
}
|
|
317
331
|
}
|
|
318
|
-
if ((browserResult.
|
|
319
|
-
console.log('
|
|
320
|
-
console.log(browserResult.
|
|
332
|
+
if ((browserResult.failedAmount ?? 0) > 0 && (browserResult.verboseFailed ?? []).length > 0) {
|
|
333
|
+
console.log('Failed: ');
|
|
334
|
+
console.log(browserResult.verboseFailed);
|
|
321
335
|
}
|
|
336
|
+
console.log('Passed amount: ' + browserResult?.passedAmount);
|
|
337
|
+
console.log('Skipped amount: ' + browserResult?.skippedAmount);
|
|
338
|
+
console.log('Failed amount: ' + browserResult?.failedAmount);
|
|
322
339
|
}
|
|
323
|
-
if ((browserResult.failedAmount ?? 0) > 0 && (browserResult.verboseFailed ?? []).length > 0) {
|
|
324
|
-
console.log('Failed: ');
|
|
325
|
-
console.log(browserResult.verboseFailed);
|
|
326
|
-
}
|
|
327
|
-
console.log('Passed amount: ' + browserResult?.passedAmount);
|
|
328
|
-
console.log('Skipped amount: ' + browserResult?.skippedAmount);
|
|
329
|
-
console.log('Failed amount: ' + browserResult?.failedAmount);
|
|
330
340
|
if (browserResult.failed) {
|
|
331
341
|
if (browserResult.verboseFailed === 'Package not found') color.fail('Tests not found');else color.fail('Tests failed.');
|
|
332
342
|
} else color.success('Tests passed.');
|
|
@@ -339,146 +349,256 @@ function saveCsvResults(stringToSave, csvReportDir) {
|
|
|
339
349
|
_fs.default.writeFileSync(csvReportDir, modifiedStrings.join('\n'), 'utf8');
|
|
340
350
|
color.info('Saved `test-report.csv`\n');
|
|
341
351
|
}
|
|
342
|
-
async function runTests(
|
|
352
|
+
async function runTests(testParams) {
|
|
343
353
|
let failed = false;
|
|
344
354
|
let verbosePassed = '';
|
|
345
355
|
let verboseSkipped = '';
|
|
346
356
|
let verboseFailed = '';
|
|
347
|
-
let error = '';
|
|
348
357
|
let countPassed = 0;
|
|
349
358
|
let countSkipped = 0;
|
|
350
359
|
let countFailed = 0;
|
|
351
|
-
let resultDF = undefined;
|
|
352
|
-
let lastTest = null;
|
|
353
|
-
let res = '';
|
|
354
360
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
if (
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
df.columns.setOrder(['date', 'category', 'name', 'success', 'result', 'ms', 'skipped', 'logs', 'owner', 'package', 'widgetsDifference', 'flaking']);
|
|
367
|
-
// addColumn('flaking', (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount), df);
|
|
368
|
-
// addColumn('package', (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount), df);
|
|
369
|
-
// if (!df.getCol('flaking')) {
|
|
370
|
-
// const flakingCol = (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount);
|
|
371
|
-
// df.columns.add(flakingCol);
|
|
372
|
-
// }
|
|
373
|
-
// if (!df.getCol('package')) {
|
|
374
|
-
// const packageNameCol =
|
|
375
|
-
// (<any>window).DG.Column.fromList((<any>window).DG.COLUMN_TYPE.STRING, 'package', Array(df.rowCount).fill(testParam.package));
|
|
376
|
-
// df.columns.add(packageNameCol);
|
|
377
|
-
// }
|
|
378
|
-
if (df.rowCount === 0) {
|
|
379
|
-
verboseFailed += `Test result : Invocation Fail : ${testParam.params.category}: ${testParam.params.test}\n`;
|
|
380
|
-
countFailed += 1;
|
|
381
|
-
failed = true;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
let row = df.rows.get(0);
|
|
385
|
-
console.log(`DEBUG: runTests: IN A LOOP: rows in df ${df.rowCount}`);
|
|
386
|
-
if (df.rowCount > 1) {
|
|
387
|
-
const unhandledErrorRow = df.rows.get(1);
|
|
388
|
-
if (!unhandledErrorRow.get('success')) {
|
|
389
|
-
unhandledErrorRow['category'] = row.get('category');
|
|
390
|
-
unhandledErrorRow['name'] = row.get('name');
|
|
391
|
-
row = unhandledErrorRow;
|
|
392
|
-
}
|
|
361
|
+
// Check if retry is supported by looking for skipToCategory parameter
|
|
362
|
+
let retrySupported = false;
|
|
363
|
+
try {
|
|
364
|
+
const funcs = window.DG.Func.find({
|
|
365
|
+
package: testParams.package,
|
|
366
|
+
name: 'test'
|
|
367
|
+
});
|
|
368
|
+
if (funcs && funcs.length > 0) {
|
|
369
|
+
const testFunc = funcs[0];
|
|
370
|
+
retrySupported = testFunc.inputs?.some(input => input.name === 'skipToCategory') === true;
|
|
393
371
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
|
|
372
|
+
} catch (e) {
|
|
373
|
+
retrySupported = false;
|
|
374
|
+
}
|
|
375
|
+
const testCallParams = {};
|
|
376
|
+
if (testParams.params?.category) testCallParams.category = testParams.params.category;
|
|
377
|
+
if (testParams.params?.test) testCallParams.test = testParams.params.test;
|
|
378
|
+
// Only pass retry-related params if supported
|
|
379
|
+
if (retrySupported) {
|
|
380
|
+
if (testParams.params?.skipToCategory) testCallParams.skipToCategory = testParams.params.skipToCategory;
|
|
381
|
+
if (testParams.params?.skipToTest) testCallParams.skipToTest = testParams.params.skipToTest;
|
|
382
|
+
if (testParams.params?.returnOnFail) testCallParams.returnOnFail = testParams.params.returnOnFail;
|
|
383
|
+
}
|
|
384
|
+
const df = await window.grok.functions.call(testParams.package + ':test', Object.keys(testCallParams).length > 0 ? testCallParams : undefined);
|
|
385
|
+
if (!df.getCol('flaking')) {
|
|
386
|
+
const flakingCol = window.DG.Column.fromType(window.DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount);
|
|
387
|
+
df.columns.add(flakingCol);
|
|
388
|
+
}
|
|
389
|
+
if (!df.getCol('package')) {
|
|
390
|
+
const packageNameCol = window.DG.Column.fromList(window.DG.COLUMN_TYPE.STRING, 'package', Array(df.rowCount).fill(testParams.package));
|
|
391
|
+
df.columns.add(packageNameCol);
|
|
392
|
+
}
|
|
393
|
+
df.columns.setOrder(['date', 'category', 'name', 'success', 'result', 'ms', 'skipped', 'logs', 'owner', 'package', 'widgetsDifference', 'flaking']);
|
|
394
|
+
df.changeColumnType('result', window.DG.COLUMN_TYPE.STRING);
|
|
395
|
+
df.changeColumnType('logs', window.DG.COLUMN_TYPE.STRING);
|
|
396
|
+
if (df.col('widgetsDifference')) df.changeColumnType('widgetsDifference', window.DG.COLUMN_TYPE.STRING);
|
|
397
|
+
// df.changeColumnType('memoryDelta', (<any>window).DG.COLUMN_TYPE.BIG_INT);
|
|
405
398
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
console.log(`DEBUG: COLUMN TYPES IN RESULT_DF: ${resultDF.columns.names().map(c => `${c}: ${resultDF.col(c)?.type}`)}`);
|
|
409
|
-
console.log(`DEBUG: COLUMN NAMES IN DF: ${df.columns.names()}`);
|
|
410
|
-
console.log(`DEBUG: COLUMN TYPES IN DF: ${df.columns.names().map(c => `${c}: ${df.col(c)?.type}`)}`);
|
|
411
|
-
resultDF = resultDF.append(df);
|
|
412
|
-
}
|
|
399
|
+
let lastFailedTest = undefined;
|
|
400
|
+
for (let row of df.rows) {
|
|
413
401
|
if (row['skipped']) {
|
|
414
|
-
verboseSkipped +=
|
|
402
|
+
verboseSkipped += `${row['category']}: ${row['name']} (${row['ms']} ms) : ${row['result']}\n`;
|
|
415
403
|
countSkipped += 1;
|
|
416
404
|
} else if (row['success']) {
|
|
417
|
-
verbosePassed +=
|
|
405
|
+
verbosePassed += `${row['category']}: ${row['name']} (${row['ms']} ms) : ${row['result']}\n`;
|
|
418
406
|
countPassed += 1;
|
|
419
407
|
} else {
|
|
420
|
-
verboseFailed +=
|
|
408
|
+
verboseFailed += `${row['category']}: ${row['name']} (${row['ms']} ms) : ${row['result']}\n`;
|
|
421
409
|
countFailed += 1;
|
|
422
410
|
failed = true;
|
|
411
|
+
if (row['category'] != 'Unhandled exceptions' && row['name'] != 'before' && row['name'] != 'after') lastFailedTest = {
|
|
412
|
+
category: row['category'],
|
|
413
|
+
test: row['name']
|
|
414
|
+
};
|
|
423
415
|
}
|
|
424
|
-
if (success !== true && skipped !== true && stopOnFail) {
|
|
425
|
-
break;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (window.DG.Test.isInDebug) {
|
|
429
|
-
console.log('on browser closing debug point');
|
|
430
|
-
debugger;
|
|
431
|
-
}
|
|
432
|
-
res = '';
|
|
433
|
-
console.log(`DEBUG: runTests: AFTER THE LOOP: rows in resultDF ${resultDF?.rowCount}`);
|
|
434
|
-
if (resultDF) {
|
|
435
|
-
const bs = window.DG.BitSet.create(resultDF.rowCount);
|
|
436
|
-
bs.setAll(true);
|
|
437
|
-
for (let i = 0; i < resultDF.rowCount; i++) {
|
|
438
|
-
if (resultDF.rows.get(i).get('category') === 'Unhandled exceptions') bs.set(i, false);
|
|
439
|
-
}
|
|
440
|
-
resultDF = resultDF.clone(bs);
|
|
441
|
-
console.log(`DEBUG: runTests: IN IF CONDITION: rows in resultDF ${resultDF.rowCount}`);
|
|
442
|
-
res = resultDF.toCsv();
|
|
443
|
-
console.log(`DEBUG: runTests: IN IF CONDITION: csv length ${res?.length}`);
|
|
444
416
|
}
|
|
417
|
+
return {
|
|
418
|
+
verbosePassed: verbosePassed,
|
|
419
|
+
verboseSkipped: verboseSkipped,
|
|
420
|
+
verboseFailed: verboseFailed,
|
|
421
|
+
passedAmount: countPassed,
|
|
422
|
+
skippedAmount: countSkipped,
|
|
423
|
+
failedAmount: countFailed,
|
|
424
|
+
csv: df.toCsv(),
|
|
425
|
+
failed: failed,
|
|
426
|
+
lastFailedTest: lastFailedTest,
|
|
427
|
+
retrySupported: retrySupported
|
|
428
|
+
// df: resultDF?.toJson()
|
|
429
|
+
};
|
|
445
430
|
} catch (e) {
|
|
446
|
-
failed = true;
|
|
447
431
|
console.log(`DEBUG: runTests: IN CATCH: ERROR: ${e}`);
|
|
448
|
-
|
|
432
|
+
return {
|
|
433
|
+
failed: true,
|
|
434
|
+
retrySupported: false,
|
|
435
|
+
error: `${e}, ${await window.DG.Logger.translateStackTrace(e.stack)}`
|
|
436
|
+
};
|
|
449
437
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
438
|
+
|
|
439
|
+
/*
|
|
440
|
+
for (const testParam of testsParams) {
|
|
441
|
+
lastTest = testParam;
|
|
442
|
+
console.log(testParam);
|
|
443
|
+
const df: DG.DataFrame = await (<any>window).grok.functions.call(testParam.package + ':test', testParam.params);
|
|
444
|
+
if (!df.getCol('flaking')) {
|
|
445
|
+
const flakingCol = (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount);
|
|
446
|
+
df.columns.add(flakingCol);
|
|
447
|
+
}
|
|
448
|
+
if (!df.getCol('package')) {
|
|
449
|
+
const packageNameCol =
|
|
450
|
+
(<any>window).DG.Column.fromList((<any>window).DG.COLUMN_TYPE.STRING, 'package', Array(df.rowCount).fill(testParam.package));
|
|
451
|
+
df.columns.add(packageNameCol);
|
|
452
|
+
}
|
|
453
|
+
df.columns
|
|
454
|
+
.setOrder([
|
|
455
|
+
'date', 'category', 'name', 'success', 'result', 'ms', 'skipped', 'logs', 'owner', 'package', 'widgetsDifference', 'flaking']);
|
|
456
|
+
// addColumn('flaking', (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount), df);
|
|
457
|
+
// addColumn('package', (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount), df);
|
|
458
|
+
// if (!df.getCol('flaking')) {
|
|
459
|
+
// const flakingCol = (<any>window).DG.Column.fromType((<any>window).DG.COLUMN_TYPE.BOOL, 'flaking', df.rowCount);
|
|
460
|
+
// df.columns.add(flakingCol);
|
|
461
|
+
// }
|
|
462
|
+
// if (!df.getCol('package')) {
|
|
463
|
+
// const packageNameCol =
|
|
464
|
+
// (<any>window).DG.Column.fromList((<any>window).DG.COLUMN_TYPE.STRING, 'package', Array(df.rowCount).fill(testParam.package));
|
|
465
|
+
// df.columns.add(packageNameCol);
|
|
466
|
+
// }
|
|
467
|
+
if (df.rowCount === 0) {
|
|
468
|
+
verboseFailed += `Test result : Invocation Fail : ${testParam.params.category}: ${testParam.params.test}\n`;
|
|
469
|
+
countFailed += 1;
|
|
470
|
+
failed = true;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
let row = df.rows.get(0);
|
|
474
|
+
console.log(`DEBUG: runTests: IN A LOOP: rows in df ${df.rowCount}`);
|
|
475
|
+
if (df.rowCount > 1) {
|
|
476
|
+
const unhandledErrorRow = df.rows.get(1);
|
|
477
|
+
if (!unhandledErrorRow.get('success')) {
|
|
478
|
+
unhandledErrorRow['category'] = row.get('category');
|
|
479
|
+
unhandledErrorRow['name'] = row.get('name');
|
|
480
|
+
row = unhandledErrorRow;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const category = row.get('category');
|
|
484
|
+
const testName = row.get('name');
|
|
485
|
+
const time = row.get('ms');
|
|
486
|
+
const result = row.get('result');
|
|
487
|
+
const success = row.get('success');
|
|
488
|
+
const skipped = row.get('skipped');
|
|
489
|
+
row['flaking'] = success && (<any>window).DG.Test.isReproducing;
|
|
490
|
+
df.changeColumnType('result', (<any>window).DG.COLUMN_TYPE.STRING);
|
|
491
|
+
df.changeColumnType('logs', (<any>window).DG.COLUMN_TYPE.STRING);
|
|
492
|
+
if (df.col('widgetsDifference'))
|
|
493
|
+
df.changeColumnType('widgetsDifference', (<any>window).DG.COLUMN_TYPE.STRING);
|
|
494
|
+
// df.changeColumnType('memoryDelta', (<any>window).DG.COLUMN_TYPE.BIG_INT);
|
|
495
|
+
if (resultDF === undefined)
|
|
496
|
+
resultDF = df;
|
|
497
|
+
else {
|
|
498
|
+
console.log(`DEBUG: COLUMN NAMES IN RESULT_DF: ${resultDF.columns.names()}`);
|
|
499
|
+
console.log(`DEBUG: COLUMN TYPES IN RESULT_DF: ${resultDF.columns.names().map((c) => `${c}: ${resultDF.col(c)?.type}`)}`);
|
|
500
|
+
console.log(`DEBUG: COLUMN NAMES IN DF: ${df.columns.names()}`);
|
|
501
|
+
console.log(`DEBUG: COLUMN TYPES IN DF: ${df.columns.names().map((c) => `${c}: ${df.col(c)?.type}`)}`);
|
|
502
|
+
resultDF = resultDF.append(df);
|
|
503
|
+
}
|
|
504
|
+
if (row['skipped']) {
|
|
505
|
+
verboseSkipped += `Test result : Skipped : ${time} : ${category}: ${testName} : ${result}\n`;
|
|
506
|
+
countSkipped += 1;
|
|
507
|
+
} else if (row['success']) {
|
|
508
|
+
verbosePassed += `Test result : Success : ${time} : ${category}: ${testName} : ${result}\n`;
|
|
509
|
+
countPassed += 1;
|
|
510
|
+
} else {
|
|
511
|
+
verboseFailed += `Test result : Failed : ${time} : ${category}: ${testName} : ${result}\n`;
|
|
512
|
+
countFailed += 1;
|
|
513
|
+
failed = true;
|
|
514
|
+
}
|
|
515
|
+
if ((success !== true && skipped !== true) && stopOnFail) {
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if ((<any>window).DG.Test.isInDebug) {
|
|
520
|
+
console.log('on browser closing debug point');
|
|
521
|
+
debugger;
|
|
522
|
+
}
|
|
523
|
+
res = '';
|
|
524
|
+
console.log(`DEBUG: runTests: AFTER THE LOOP: rows in resultDF ${resultDF?.rowCount}`);
|
|
525
|
+
if (resultDF) {
|
|
526
|
+
const bs = (<any>window).DG.BitSet.create(resultDF.rowCount);
|
|
527
|
+
bs.setAll(true);
|
|
528
|
+
for (let i = 0; i < resultDF.rowCount; i++) {
|
|
529
|
+
if (resultDF.rows.get(i).get('category') === 'Unhandled exceptions')
|
|
530
|
+
bs.set(i, false);
|
|
531
|
+
}
|
|
532
|
+
resultDF = resultDF.clone(bs);
|
|
533
|
+
console.log(`DEBUG: runTests: IN IF CONDITION: rows in resultDF ${resultDF.rowCount}`);
|
|
534
|
+
res = resultDF.toCsv();
|
|
535
|
+
console.log(`DEBUG: runTests: IN IF CONDITION: csv length ${res?.length}`);
|
|
536
|
+
}
|
|
537
|
+
} catch (e) {
|
|
538
|
+
failed = true;
|
|
539
|
+
console.log(`DEBUG: runTests: IN CATCH: ERROR: ${e}`);
|
|
540
|
+
error = lastTest ?
|
|
541
|
+
`category: ${
|
|
542
|
+
lastTest.params.category}, name: ${
|
|
543
|
+
lastTest.params.test}, error: ${e}, ${await (<any>window).DG.Logger.translateStackTrace((e as any).stack)}` :
|
|
544
|
+
`test: null, error: ${e}, ${await (<any>window).DG.Logger.translateStackTrace((e as any).stack)}`;
|
|
545
|
+
}
|
|
546
|
+
console.log(`DEBUG: runTests: BEFORE RETURN: RES: ${res.length}, FAILED: ${failed}, ERROR: ${error}`);
|
|
547
|
+
return {
|
|
548
|
+
failed: failed,
|
|
549
|
+
verbosePassed: verbosePassed,
|
|
550
|
+
verboseSkipped: verboseSkipped,
|
|
551
|
+
verboseFailed: verboseFailed,
|
|
552
|
+
passedAmount: countPassed,
|
|
553
|
+
skippedAmount: countSkipped,
|
|
554
|
+
failedAmount: countFailed,
|
|
555
|
+
error: error,
|
|
556
|
+
csv: res,
|
|
557
|
+
// df: resultDF?.toJson()
|
|
558
|
+
};*/
|
|
463
559
|
}
|
|
464
|
-
async function runBrowser(testExecutionData, browserOptions, browsersId, testInvocationTimeout = 3600000) {
|
|
560
|
+
async function runBrowser(testExecutionData, browserOptions, browsersId, testInvocationTimeout = 3600000, existingBrowserSession) {
|
|
465
561
|
const testsToRun = {
|
|
466
562
|
func: runTests.toString(),
|
|
467
|
-
|
|
563
|
+
testParams: testExecutionData
|
|
468
564
|
};
|
|
469
565
|
return await timeout(async () => {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
566
|
+
let browser;
|
|
567
|
+
let page;
|
|
568
|
+
let webUrl;
|
|
569
|
+
if (existingBrowserSession) {
|
|
570
|
+
// Reuse existing browser - just refresh the page
|
|
571
|
+
browser = existingBrowserSession.browser;
|
|
572
|
+
page = existingBrowserSession.page;
|
|
573
|
+
webUrl = existingBrowserSession.webUrl;
|
|
574
|
+
|
|
575
|
+
// Remove old console listeners before refresh to avoid stale state references
|
|
576
|
+
page.removeAllListeners('console');
|
|
577
|
+
await page.goto(webUrl);
|
|
578
|
+
try {
|
|
579
|
+
await page.waitForFunction(() => document.querySelector('.grok-preloader') == null, {
|
|
580
|
+
timeout: 3600000
|
|
581
|
+
});
|
|
582
|
+
} catch (error) {
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
// Create new browser
|
|
587
|
+
const params = Object.assign({
|
|
588
|
+
devtools: browserOptions.debug
|
|
589
|
+
}, defaultLaunchParameters);
|
|
590
|
+
if (browserOptions.gui) params['headless'] = false;
|
|
591
|
+
const out = await getBrowserPage(_puppeteer.default, params);
|
|
592
|
+
browser = out.browser;
|
|
593
|
+
page = out.page;
|
|
594
|
+
webUrl = await getWebUrlFromPage(page);
|
|
595
|
+
}
|
|
477
596
|
const recorder = new _puppeteerScreenRecorder.PuppeteerScreenRecorder(page, recorderConfig);
|
|
478
597
|
const currentBrowserNum = browsersId;
|
|
479
598
|
const logsDir = `./test-console-output-${currentBrowserNum}.log`;
|
|
480
599
|
const recordDir = `./test-record-${currentBrowserNum}.mp4`;
|
|
481
|
-
if (browserOptions.record) {
|
|
600
|
+
if (browserOptions.record && !existingBrowserSession) {
|
|
601
|
+
// Only set up recording on initial browser creation, not on retry
|
|
482
602
|
await recorder.start(recordDir);
|
|
483
603
|
await page.exposeFunction('addLogsToFile', addLogsToFile);
|
|
484
604
|
_fs.default.writeFileSync(logsDir, ``);
|
|
@@ -492,6 +612,130 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
|
|
|
492
612
|
addLogsToFile(logsDir, `CONSOLE LOG REQUEST: ${response.status()}, ${response.url()}\n`);
|
|
493
613
|
});
|
|
494
614
|
}
|
|
615
|
+
|
|
616
|
+
// State tracking for test output formatting
|
|
617
|
+
const categoryResults = new Map();
|
|
618
|
+
let currentCategory = null;
|
|
619
|
+
let currentTestName = null;
|
|
620
|
+
// Store failed tests with their errors to print after category header
|
|
621
|
+
let pendingFailures = [];
|
|
622
|
+
let pendingBeforeFailure = null;
|
|
623
|
+
let pendingAfterFailure = null;
|
|
624
|
+
let modernOutput = false;
|
|
625
|
+
const printCategorySummary = category => {
|
|
626
|
+
const results = categoryResults.get(category);
|
|
627
|
+
if (!results) return;
|
|
628
|
+
const formattedCategory = category.replace(/: /g, ', ');
|
|
629
|
+
const passedCount = results.passed;
|
|
630
|
+
if (results.failed > 0 || pendingBeforeFailure || pendingAfterFailure) {
|
|
631
|
+
console.log(`\x1b[31m❌ ${formattedCategory} (${passedCount} passed)\x1b[0m`);
|
|
632
|
+
// Print before() failure first
|
|
633
|
+
if (pendingBeforeFailure) {
|
|
634
|
+
console.log(` \x1b[31m❌ before\x1b[0m`);
|
|
635
|
+
if (pendingBeforeFailure.error) console.log(` \x1b[31m${pendingBeforeFailure.error}\x1b[0m`);
|
|
636
|
+
}
|
|
637
|
+
// Print after() failure
|
|
638
|
+
if (pendingAfterFailure) {
|
|
639
|
+
console.log(` \x1b[31m❌ after\x1b[0m`);
|
|
640
|
+
if (pendingAfterFailure.error) console.log(` \x1b[31m${pendingAfterFailure.error}\x1b[0m`);
|
|
641
|
+
}
|
|
642
|
+
// Print test failures
|
|
643
|
+
for (const failure of pendingFailures) {
|
|
644
|
+
console.log(` \x1b[31m❌ ${failure.testName}\x1b[0m`);
|
|
645
|
+
if (failure.error) console.log(` \x1b[31m${failure.error}\x1b[0m`);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
console.log(`\x1b[32m✅ ${formattedCategory} (${passedCount} passed)\x1b[0m`);
|
|
649
|
+
}
|
|
650
|
+
pendingFailures = [];
|
|
651
|
+
pendingBeforeFailure = null;
|
|
652
|
+
pendingAfterFailure = null;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Subscribe to page console events for modern output formatting
|
|
656
|
+
// On retry, old listeners were removed so we need to re-attach
|
|
657
|
+
page.on('console', msg => {
|
|
658
|
+
const text = msg.text();
|
|
659
|
+
if (!text.startsWith('Package testing: ')) return;
|
|
660
|
+
modernOutput = true;
|
|
661
|
+
// Extract tokens in {{...}} format
|
|
662
|
+
const tokens = [];
|
|
663
|
+
const tokenRegex = /\{\{([^}]+)\}\}/g;
|
|
664
|
+
let match;
|
|
665
|
+
while ((match = tokenRegex.exec(text)) !== null) tokens.push(match[1]);
|
|
666
|
+
|
|
667
|
+
// Category start: "Package testing: Started {{Category}}"
|
|
668
|
+
if (text.includes('Started') && tokens.length === 1) {
|
|
669
|
+
// Print summary of previous category if exists
|
|
670
|
+
if (currentCategory && categoryResults.has(currentCategory)) printCategorySummary(currentCategory);
|
|
671
|
+
currentCategory = tokens[0];
|
|
672
|
+
categoryResults.set(currentCategory, {
|
|
673
|
+
passed: 0,
|
|
674
|
+
failed: 0
|
|
675
|
+
});
|
|
676
|
+
pendingFailures = [];
|
|
677
|
+
pendingBeforeFailure = null;
|
|
678
|
+
pendingAfterFailure = null;
|
|
679
|
+
} else if (text.includes('Started') && tokens.length === 2) {
|
|
680
|
+
// Test start: "Package testing: Started {{Category}} {{TestName}}"
|
|
681
|
+
const category = tokens[0].replace(/: /g, ', ');
|
|
682
|
+
currentTestName = tokens[1];
|
|
683
|
+
process.stdout.write(`${category}: ${currentTestName}...`);
|
|
684
|
+
} else if (text.includes('Finished') && tokens.length === 3) {
|
|
685
|
+
// Test finish: "Package testing: Finished {{Category}} {{TestName}} with {{success/error}} for X ms"
|
|
686
|
+
const category = tokens[0];
|
|
687
|
+
const testName = tokens[1];
|
|
688
|
+
const status = tokens[2];
|
|
689
|
+
const results = categoryResults.get(category);
|
|
690
|
+
|
|
691
|
+
// Clear the current test line
|
|
692
|
+
process.stdout.write('\r\x1b[K');
|
|
693
|
+
if (status === 'success') {
|
|
694
|
+
if (results) results.passed++;
|
|
695
|
+
} else {
|
|
696
|
+
if (results) results.failed++;
|
|
697
|
+
pendingFailures.push({
|
|
698
|
+
testName,
|
|
699
|
+
error: null
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
currentTestName = null;
|
|
703
|
+
} else if (text.includes('Result for') && tokens.length === 2) {
|
|
704
|
+
// Error result: "Package testing: Result for {{Category}} {{TestName}}: error message"
|
|
705
|
+
const errorMsg = text.split(': ').slice(-1)[0];
|
|
706
|
+
// Attach error to the last pending failure
|
|
707
|
+
if (pendingFailures.length > 0) pendingFailures[pendingFailures.length - 1].error = errorMsg;
|
|
708
|
+
} else if (text.includes('Category before()') && text.includes('failed') && tokens.length >= 1) {
|
|
709
|
+
// Category before() failed: "Package testing: Category before() {{Category}} failed"
|
|
710
|
+
process.stdout.write('\r\x1b[K');
|
|
711
|
+
pendingBeforeFailure = {
|
|
712
|
+
error: null
|
|
713
|
+
};
|
|
714
|
+
} else if (text.includes('Result for') && text.includes('before:') && tokens.length >= 1) {
|
|
715
|
+
// Before error result: "Package testing: Result for {{Category}} before: error message"
|
|
716
|
+
const errorMsg = text.split('before: ').slice(-1)[0];
|
|
717
|
+
if (pendingBeforeFailure) pendingBeforeFailure.error = errorMsg;
|
|
718
|
+
} else if (text.includes('Category after()') && text.includes('failed') && tokens.length >= 1) {
|
|
719
|
+
// Category after() failed: "Package testing: Category after() {{Category}} failed"
|
|
720
|
+
process.stdout.write('\r\x1b[K');
|
|
721
|
+
pendingAfterFailure = {
|
|
722
|
+
error: null
|
|
723
|
+
};
|
|
724
|
+
} else if (text.includes('Result for') && text.includes('after:') && tokens.length >= 1) {
|
|
725
|
+
// After error result: "Package testing: Result for {{Category}} after: error message"
|
|
726
|
+
const errorMsg = text.split('after: ').slice(-1)[0];
|
|
727
|
+
if (pendingAfterFailure) pendingAfterFailure.error = errorMsg;
|
|
728
|
+
} else if (text.includes('Unhandled Exception')) {
|
|
729
|
+
// Unhandled exception: "Package testing: Unhandled Exception: ..."
|
|
730
|
+
// Clear any pending test line and move to new line
|
|
731
|
+
process.stdout.write('\r\x1b[K\n');
|
|
732
|
+
const errorMsg = text.replace('Package testing: Unhandled Exception: ', '');
|
|
733
|
+
if (errorMsg && errorMsg !== 'null') console.log(`\x1b[31m❌ Unhandled Exception: ${errorMsg}\x1b[0m`);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
const printFinalCategorySummary = () => {
|
|
737
|
+
if (currentCategory && categoryResults.has(currentCategory)) printCategorySummary(currentCategory);
|
|
738
|
+
};
|
|
495
739
|
const testingResults = await page.evaluate((testData, options) => {
|
|
496
740
|
if (options.benchmark) window.DG.Test.isInBenchmark = true;
|
|
497
741
|
if (options.reproduce) window.DG.Test.isReproducing = true;
|
|
@@ -499,7 +743,7 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
|
|
|
499
743
|
if (options.debug) window.DG.Test.isInDebug = true;
|
|
500
744
|
return new Promise((resolve, reject) => {
|
|
501
745
|
window.runTests = eval('(' + testData.func + ')');
|
|
502
|
-
window.runTests(testData.
|
|
746
|
+
window.runTests(testData.testParams, options.stopOnTimeout).then(results => {
|
|
503
747
|
resolve(results);
|
|
504
748
|
}).catch(e => {
|
|
505
749
|
resolve({
|
|
@@ -535,9 +779,29 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
|
|
|
535
779
|
// });
|
|
536
780
|
});
|
|
537
781
|
}, testsToRun, browserOptions);
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
782
|
+
|
|
783
|
+
// Print the final category summary
|
|
784
|
+
printFinalCategorySummary();
|
|
785
|
+
if (browserOptions.record && !existingBrowserSession) await recorder.stop();
|
|
786
|
+
if (modernOutput) {
|
|
787
|
+
testingResults.verbosePassed = '';
|
|
788
|
+
testingResults.verboseSkipped = '';
|
|
789
|
+
testingResults.verboseFailed = '';
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Only close browser if not keeping it open for retry
|
|
793
|
+
if (!browserOptions.keepBrowserOpen && browser != null) await browser.close();
|
|
794
|
+
|
|
795
|
+
// Return browser session for potential reuse
|
|
796
|
+
return {
|
|
797
|
+
...testingResults,
|
|
798
|
+
browserSession: browserOptions.keepBrowserOpen ? {
|
|
799
|
+
browser,
|
|
800
|
+
page,
|
|
801
|
+
webUrl
|
|
802
|
+
} : undefined,
|
|
803
|
+
modernOutput
|
|
804
|
+
};
|
|
541
805
|
}, testInvocationTimeout);
|
|
542
806
|
}
|
|
543
807
|
async function mergeBrowsersResults(browsersResults) {
|
|
@@ -550,7 +814,8 @@ async function mergeBrowsersResults(browsersResults) {
|
|
|
550
814
|
skippedAmount: browsersResults[0].skippedAmount,
|
|
551
815
|
failedAmount: browsersResults[0].failedAmount,
|
|
552
816
|
csv: browsersResults[0].csv,
|
|
553
|
-
error: ''
|
|
817
|
+
error: '',
|
|
818
|
+
modernOutput: browsersResults.some(r => r.modernOutput)
|
|
554
819
|
};
|
|
555
820
|
for (const browsersResult of browsersResults) {
|
|
556
821
|
if (mergedResult.csv === browsersResult.csv) continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datagrok-tools",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Utility to upload and publish packages to Datagrok",
|
|
5
5
|
"homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"archiver-promise": "^1.0.0",
|
|
14
14
|
"datagrok-api": "^1.26.0",
|
|
15
15
|
"estraverse": "^5.3.0",
|
|
16
|
-
"fs": "^0.0.1-security",
|
|
17
16
|
"glob": "^11.0.2",
|
|
18
17
|
"ignore-walk": "^3.0.4",
|
|
19
18
|
"inquirer": "^7.3.3",
|
|
@@ -25,7 +24,8 @@
|
|
|
25
24
|
"papaparse": "^5.4.1",
|
|
26
25
|
"path": "^0.12.7",
|
|
27
26
|
"puppeteer": "22.10.0",
|
|
28
|
-
"puppeteer-screen-recorder": "3.0.3"
|
|
27
|
+
"puppeteer-screen-recorder": "3.0.3",
|
|
28
|
+
"ts-morph": "^27.0.2"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"link": "npm link",
|
|
@@ -75,7 +75,6 @@
|
|
|
75
75
|
"@typescript-eslint/parser": "^5.62.0",
|
|
76
76
|
"eslint": "^8.56.0",
|
|
77
77
|
"eslint-config-google": "^0.14.0",
|
|
78
|
-
"ts-morph": "^27.0.2",
|
|
79
78
|
"typescript": "^5.3.3",
|
|
80
79
|
"webpack": "^5.89.0",
|
|
81
80
|
"webpack-cli": "^5.1.4"
|