@testim/testim-cli 3.197.0 → 3.201.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/OverrideTestDataBuilder.js +1 -1
- package/commons/featureFlags.js +0 -3
- package/commons/npmWrapper.js +46 -14
- package/commons/npmWrapper.test.js +182 -6
- package/executionQueue.js +2 -2
- package/npm-shrinkwrap.json +476 -148
- package/package.json +6 -6
- package/player/stepActions/pixelValidationStepAction.js +2 -2
- package/player/stepActions/salesforceAutoLoginStepAction.js +3 -3
- package/player/stepActions/stepActionRegistrar.js +2 -1
- package/player/utils/eyeSdkService.js +8 -2
- package/reports/consoleReporter.js +29 -24
- package/reports/junitReporter.js +0 -3
- package/runner.js +1 -10
- package/runners/ParallelWorkerManager.js +2 -2
- package/runners/TestPlanRunner.js +18 -16
- package/services/gridService.js +0 -4
- package/stepPlayers/hybridStepPlayback.js +4 -1
- package/stepPlayers/tdkHybridStepPlayback.js +1 -0
- package/testRunHandler.js +30 -7
- package/testRunStatus.js +6 -19
- package/workers/BaseWorker.js +1 -0
- package/workers/WorkerSelenium.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testim/testim-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.201.0",
|
|
4
4
|
"description": "Command line interface for running Testing on your CI",
|
|
5
5
|
"author": "Oren Rubin",
|
|
6
6
|
"contributors": [{
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"typescript": "3.9.3"
|
|
37
37
|
},
|
|
38
38
|
"lazyDependencies": {
|
|
39
|
-
"ngrok": "3.
|
|
39
|
+
"ngrok": "3.4.0",
|
|
40
40
|
"webpack": "4.43.0",
|
|
41
41
|
"playwright": "0.12.1",
|
|
42
42
|
"puppeteer": "2.1.1",
|
|
@@ -79,10 +79,10 @@
|
|
|
79
79
|
"glob": "7.1.6",
|
|
80
80
|
"istanbul-lib-report": "3.0.0",
|
|
81
81
|
"istanbul-reports": "3.0.2",
|
|
82
|
-
"jimp": "0.
|
|
82
|
+
"jimp": "0.16.1",
|
|
83
83
|
"jsdom": "16.7.0",
|
|
84
84
|
"jsonwebtoken": "8.5.1",
|
|
85
|
-
"lodash": "4.17.
|
|
85
|
+
"lodash": "4.17.21",
|
|
86
86
|
"memory-fs": "0.5.0",
|
|
87
87
|
"memorystream": "0.3.1",
|
|
88
88
|
"mkdirp": "1.0.4",
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"superagent-proxy": "3.0.0",
|
|
106
106
|
"test-exclude": "6.0.0",
|
|
107
107
|
"threads": "0.12.0",
|
|
108
|
-
"ua-parser-js": "0.7.
|
|
108
|
+
"ua-parser-js": "0.7.28",
|
|
109
109
|
"validate-npm-package-name": "3.0.0",
|
|
110
|
-
"ws": "
|
|
110
|
+
"ws": "8.2.3",
|
|
111
111
|
"xml2js": "0.4.23",
|
|
112
112
|
"yaml": "1.10.0"
|
|
113
113
|
},
|
|
@@ -7,9 +7,9 @@ const logger = require('../../commons/logger').getLogger('pixel-validation-step-
|
|
|
7
7
|
|
|
8
8
|
class PixelValidationStepAction extends StepAction {
|
|
9
9
|
async performAction() {
|
|
10
|
-
const { shouldUseVisualGrid, applitoolsSdkConfig: config } = this.context;
|
|
10
|
+
const { shouldUseVisualGrid, applitoolsSdkConfig: config, testResultId } = this.context;
|
|
11
11
|
this.runContext = this.context.getRunContext(undefined);
|
|
12
|
-
const eyeManager = await eyeSdkService.getManager(shouldUseVisualGrid, this.context.config.applitoolsConcurrency || 5);
|
|
12
|
+
const eyeManager = await eyeSdkService.getManager(shouldUseVisualGrid, this.context.config.applitoolsConcurrency || 5, testResultId);
|
|
13
13
|
const targetElementData = this.getTarget() || {};
|
|
14
14
|
try {
|
|
15
15
|
const openedEye = await eyeManager.openEyes({ driver: this.driver.client, config });
|
|
@@ -7,7 +7,7 @@ class SalesforceAutoLoginStepAction extends NavigationStepAction {
|
|
|
7
7
|
try {
|
|
8
8
|
salesforceUrl = await this.updateBaseUrl(salesforceUrl);
|
|
9
9
|
await this.driver.url(salesforceUrl);
|
|
10
|
-
await Promise.delay(
|
|
10
|
+
await Promise.delay(5000); // wait a little for the page to load (fixes screenshots and clicking on elements in username verification screen)
|
|
11
11
|
let newUrl = await this.driver.getUrl();
|
|
12
12
|
// Verify username screen
|
|
13
13
|
const isUsernameVerificationNeeded = newUrl.includes(this.step.USERNAME_VERIFICATION_PATH_ID);
|
|
@@ -19,10 +19,10 @@ class SalesforceAutoLoginStepAction extends NavigationStepAction {
|
|
|
19
19
|
var done = arguments[1];
|
|
20
20
|
done();
|
|
21
21
|
`, timeout);
|
|
22
|
-
await Promise.delay(
|
|
22
|
+
await Promise.delay(5000);
|
|
23
23
|
newUrl = await this.driver.getUrl(); // If we managed to continue correctly we want to get the new redirected url
|
|
24
24
|
}
|
|
25
|
-
await Promise.delay(
|
|
25
|
+
await Promise.delay(1500);
|
|
26
26
|
return {
|
|
27
27
|
success: true,
|
|
28
28
|
newUrl,
|
|
@@ -21,9 +21,10 @@ const RefreshStepAction = require('./RefreshStepAction');
|
|
|
21
21
|
const ApiStepAction = require('./apiStepAction');
|
|
22
22
|
const ExtractTextStepAction = require('./extractTextStepAction');
|
|
23
23
|
const TdkHybridStepAction = require('./tdkHybridStepAction');
|
|
24
|
-
const SalesforceAutoLoginStepAction = require('./salesforceAutoLoginStepAction');
|
|
25
24
|
const PixelValidationStepAction = require('./pixelValidationStepAction');
|
|
26
25
|
|
|
26
|
+
const SalesforceAutoLoginStepAction = require('./salesforceAutoLoginStepAction');
|
|
27
|
+
|
|
27
28
|
const CliJsStepAction = require('./cliJsStepAction');
|
|
28
29
|
const CliConditionStepAction = require('./cliConditionStepAction');
|
|
29
30
|
const NodePackageStepAction = require('./nodePackageStepAction');
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// https://github.com/applitools/eyes.sdk.javascript1/blob/master/packages/eyes-webdriverio-4/src/spec-driver.ts
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @type {{ EyeSdkBuilder: typeof import('../../../../clickim/src/background/eyeSdkBuilder').EyeSdkBuilder }}
|
|
5
|
+
*/
|
|
6
|
+
const sessionPlayer = require('../../commons/getSessionPlayerRequire');
|
|
3
7
|
const { makeSDK } = require('@applitools/eyes-sdk-core');
|
|
4
8
|
const { W3C_ELEMENT_ID } = require('../constants');
|
|
5
9
|
const _ = require('lodash');
|
|
@@ -222,8 +226,10 @@ class EyeSdkService {
|
|
|
222
226
|
VisualGridClient: require('@applitools/visual-grid-client'),
|
|
223
227
|
});
|
|
224
228
|
}
|
|
225
|
-
getManager(useVisualGrid, concurrency) {
|
|
226
|
-
|
|
229
|
+
async getManager(useVisualGrid, concurrency, batchId) {
|
|
230
|
+
const manager = await this.sdk.makeManager({ type: useVisualGrid ? 'vg' : 'classic', concurrency });
|
|
231
|
+
sessionPlayer.EyeSdkBuilder.rememberCreatedBatch(batchId);
|
|
232
|
+
return manager;
|
|
227
233
|
}
|
|
228
234
|
}
|
|
229
235
|
|
|
@@ -7,6 +7,8 @@ const constants = require('../commons/constants');
|
|
|
7
7
|
const featureAvailabilityService = require('../commons/featureAvailabilityService');
|
|
8
8
|
const { getAbortedTests, getFailedTests, getPassedTests, getFailureEvaluatingCount, getSkippedCount } = require('./reporterUtils');
|
|
9
9
|
|
|
10
|
+
const colorize = { success: chalk.green, warn: chalk.yellow, error: chalk.red };
|
|
11
|
+
|
|
10
12
|
class ConsoleReporter {
|
|
11
13
|
constructor(options, branchToUse) {
|
|
12
14
|
this.options = options;
|
|
@@ -16,12 +18,8 @@ class ConsoleReporter {
|
|
|
16
18
|
this.branchToUse = branchToUse;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.log(`W:${workerId} ${message}`);
|
|
22
|
-
} else {
|
|
23
|
-
console.log(message);
|
|
24
|
-
}
|
|
21
|
+
toWorkerIdPrefix(workerId) {
|
|
22
|
+
return this.config.showWorkerNames ? `W:${workerId}` : '';
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
printWorkerDivider() {
|
|
@@ -32,7 +30,7 @@ class ConsoleReporter {
|
|
|
32
30
|
const type = isCodeMode ? 'File' : 'Test';
|
|
33
31
|
const testIdLabel = test.isTestsContainer ? '' : `(${test.testId})`;
|
|
34
32
|
const testUrlLabel = test.isTestsContainer ? '' : `url: ${chalk.underline(utils.getTestUrl(this.options.editorUrl, this.options.project, test.testId, test.resultId, this.branchToUse))}`;
|
|
35
|
-
this.
|
|
33
|
+
console.log(this.toWorkerIdPrefix(workerId), `${type} "${test.name}" started ${testIdLabel} ${testUrlLabel}`.trim());
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
onTestFinished(test, workerId, isRerun, isCodeMode) {
|
|
@@ -42,7 +40,9 @@ class ConsoleReporter {
|
|
|
42
40
|
}
|
|
43
41
|
const testStatus = test.success ? constants.runnerTestStatus.PASSED : constants.runnerTestStatus.FAILED;
|
|
44
42
|
const testIdLabel = test.isTestsContainer ? ' ' : `(${test.testId})`;
|
|
45
|
-
|
|
43
|
+
const color = colorize[test.success ? 'success' : 'error'];
|
|
44
|
+
|
|
45
|
+
console.log(color(this.toWorkerIdPrefix(workerId), `Test "${test.name}" finished status: ${testStatus} ${testIdLabel} duration: ${utils.getDuration(test.duration)}`));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
printAllFailedTests(failedTests) {
|
|
@@ -54,8 +54,8 @@ class ConsoleReporter {
|
|
|
54
54
|
}
|
|
55
55
|
return `${failedTest.name} : ${testUrl}`;
|
|
56
56
|
});
|
|
57
|
-
console.log('Failed runs are:');
|
|
58
|
-
console.log(failedTestStrings.join('\n\r'));
|
|
57
|
+
console.log(colorize.error('Failed runs are:'));
|
|
58
|
+
console.log(colorize.error(failedTestStrings.join('\n\r')));
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -79,15 +79,20 @@ class ConsoleReporter {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const planName = this.buildTestPlanName(isAnonymous, testPlanName, isCodeMode);
|
|
82
|
+
|
|
83
|
+
let message;
|
|
84
|
+
const color = colorize[failed ? 'error' : 'success'];
|
|
85
|
+
|
|
82
86
|
if (isCodeMode || planName.trim() === '' || planName.trim() === 'Anonymous') {
|
|
83
|
-
|
|
84
|
-
console.log(`Tests completed. PASSED: ${passed} FAILED: ${failed}${failedEvaluatingString} ABORTED: ${aborted}${skippedString} Duration: ${utils.getDuration(duration)} (Execution ID: ${executionId})`);
|
|
85
|
-
this.printWorkerDivider();
|
|
87
|
+
message = `Tests completed. PASSED: ${passed} FAILED: ${failed}${failedEvaluatingString} ABORTED: ${aborted}${skippedString} Duration: ${utils.getDuration(duration)} (Execution ID: ${executionId})`;
|
|
86
88
|
} else {
|
|
87
|
-
|
|
88
|
-
console.log(`Test plan${planName} completed PASSED: ${passed} FAILED: ${failed}${failedEvaluatingString} ABORTED: ${aborted}${skippedString} Duration: ${utils.getDuration(duration)} (${executionId})`);
|
|
89
|
-
this.printWorkerDivider();
|
|
89
|
+
message = `Test plan${planName} completed PASSED: ${passed} FAILED: ${failed}${failedEvaluatingString} ABORTED: ${aborted}${skippedString} Duration: ${utils.getDuration(duration)} (${executionId})`;
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
this.printWorkerDivider();
|
|
93
|
+
console.log(color(message));
|
|
94
|
+
this.printWorkerDivider();
|
|
95
|
+
|
|
91
96
|
this.printAllFailedTests(failedTests);
|
|
92
97
|
}
|
|
93
98
|
|
|
@@ -138,24 +143,24 @@ class ConsoleReporter {
|
|
|
138
143
|
onGetSlot(workerId, browser) {
|
|
139
144
|
const gridNameOrId = this.options.grid || this.options.gridId;
|
|
140
145
|
if (gridNameOrId) {
|
|
141
|
-
this.
|
|
146
|
+
console.log(this.toWorkerIdPrefix(workerId), `Get ${chalk.underline(browser)} slot from ${chalk.underline(gridNameOrId)}`);
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
onGetSession(workerId, testName, mode) {
|
|
146
|
-
this.
|
|
151
|
+
console.log(this.toWorkerIdPrefix(workerId), `Get browser to run ${chalk.underline(testName)}`);
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
onWaitToTestStart(workerId) {
|
|
150
|
-
this.
|
|
155
|
+
console.log(this.toWorkerIdPrefix(workerId), 'Wait for test start');
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
onWaitToTestComplete(workerId, isCodeMode, debuggerAddress) {
|
|
154
159
|
const type = isCodeMode ? 'file' : 'test';
|
|
155
|
-
this.
|
|
160
|
+
console.log(this.toWorkerIdPrefix(workerId), `Wait for ${type} complete`);
|
|
156
161
|
if (debuggerAddress && isCodeMode) {
|
|
157
162
|
// TODO(Benji) decide with Amitai what we want to do with this
|
|
158
|
-
this.
|
|
163
|
+
console.log(this.toWorkerIdPrefix(workerId), `Chrome Debugger available at ${debuggerAddress}`);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
166
|
|
|
@@ -166,11 +171,11 @@ class ConsoleReporter {
|
|
|
166
171
|
// heuristic, show the message on the same attempt
|
|
167
172
|
const gridNameOrId = this.options.grid || this.options.gridId;
|
|
168
173
|
if (gridNameOrId) { // if the user passes a grid or a gridId - show those
|
|
169
|
-
this.
|
|
174
|
+
console.log(colorize.warn(this.toWorkerIdPrefix(workerId), `It is taking us some time to get a browser from the grid ${gridNameOrId}`));
|
|
170
175
|
} else if (this.options.usingLocalChromeDriver) {
|
|
171
|
-
this.
|
|
176
|
+
console.log(colorize.warn(this.toWorkerIdPrefix(workerId), 'We are having issues starting ChromeDriver for you locally'));
|
|
172
177
|
} else if (this.options.host) {
|
|
173
|
-
this.
|
|
178
|
+
console.log(colorize.warn(this.toWorkerIdPrefix(workerId), `We are having issues reaching your Selenium grid at ${this.options.host}:${this.options.port || 4444}`));
|
|
174
179
|
} else {
|
|
175
180
|
// in other cases - print nothing
|
|
176
181
|
}
|
package/reports/junitReporter.js
CHANGED
|
@@ -50,9 +50,6 @@ class JunitReporter {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function getPrintName(testResult) {
|
|
53
|
-
if (!featureFlags.flags.testNameTestDataInJunitReport.isEnabled()) {
|
|
54
|
-
return testResult.name;
|
|
55
|
-
}
|
|
56
53
|
const testData = testResult.testData || {};
|
|
57
54
|
const testDataNumber = typeof testData.total === 'number' ? ` - ${testData.index} / ${testData.total} Data set` : '';
|
|
58
55
|
return `${testResult.name}${testDataNumber}`;
|
package/runner.js
CHANGED
|
@@ -286,16 +286,7 @@ async function init(options) {
|
|
|
286
286
|
reporter.setOptions(options, branchToUse);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
function run(options, customExtensionLocalLocation) {
|
|
290
|
-
perf.log('in runner.js run');
|
|
291
|
-
|
|
292
|
-
if (options.files.length > 0 && !featureFlags.flags.enableTDKRun.isEnabled()) {
|
|
293
|
-
throw new ArgError('run command is not supported, please sign up to the Testim Development Kit beta - https://go.testim.io/testims-new-beta-testim-tdk-testim-development-kit-testautomation');
|
|
294
|
-
}
|
|
295
|
-
return runRunner(options, customExtensionLocalLocation);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
289
|
module.exports = {
|
|
299
|
-
run,
|
|
290
|
+
run: runRunner,
|
|
300
291
|
init: Promise.method(init),
|
|
301
292
|
};
|
|
@@ -46,14 +46,14 @@ class ParallelWorkerManager {
|
|
|
46
46
|
return Array.from(new Array(count), createWorker);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
async runTests(testList, testStatus, executionId, options, branchToUse, authData, workerCount, stopOnError) {
|
|
49
|
+
async runTests(testList, testStatus, executionId, executionName, options, branchToUse, authData, workerCount, stopOnError) {
|
|
50
50
|
if (testList && testList.length === 0) {
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const runAndWaitToComplete = token => new Promise((resolve, reject) => {
|
|
55
55
|
const projectId = options.project;
|
|
56
|
-
const executionQueue = new ExecutionQueue(executionId, testList, options, branchToUse, testStatus);
|
|
56
|
+
const executionQueue = new ExecutionQueue(executionId, executionName, testList, options, branchToUse, testStatus);
|
|
57
57
|
|
|
58
58
|
const combinedTestResults = {};
|
|
59
59
|
const testCount = testList.length;
|
|
@@ -29,30 +29,30 @@ class TestPlanRunner {
|
|
|
29
29
|
this.workerManager = new ParallelWorkerManager(customExtensionLocalLocation);
|
|
30
30
|
this.startTime = Date.now();
|
|
31
31
|
}
|
|
32
|
-
runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, testStatus) {
|
|
32
|
+
runTestAllPhases(beforeTests, tests, afterTests, branchToUse, tpOptions, executionId, executionName, testStatus) {
|
|
33
33
|
const executionResults = {};
|
|
34
34
|
const authData = testimCustomToken.getTokenV3UserData();
|
|
35
35
|
|
|
36
|
-
const runBeforeTests = (beforeTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
36
|
+
const runBeforeTests = (beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
|
|
37
37
|
const workerCount = 1;
|
|
38
38
|
const stopOnError = true;
|
|
39
|
-
return this.workerManager.runTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
39
|
+
return this.workerManager.runTests(beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
40
40
|
.then(beforeTestsResults => Object.assign(executionResults, beforeTestsResults));
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const runTestPlanTests = (tests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
43
|
+
const runTestPlanTests = (tests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
|
|
44
44
|
const workerCount = config.TESTIM_CONCURRENT_WORKER_COUNT || tpOptions.parallel;
|
|
45
45
|
const stopOnError = false;
|
|
46
46
|
perf.log('right before this.workerManager.runTests');
|
|
47
|
-
return this.workerManager.runTests(tests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
47
|
+
return this.workerManager.runTests(tests, testStatus, executionId, executionName, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
48
48
|
.log('right after this.workerManager.runTests')
|
|
49
49
|
.then(testsResults => Object.assign(executionResults, testsResults));
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
const runAfterTests = (afterTests, testStatus, executionId, tpOptions, branchToUse, authData) => {
|
|
52
|
+
const runAfterTests = (afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData) => {
|
|
53
53
|
const workerCount = 1;
|
|
54
54
|
const stopOnError = false;
|
|
55
|
-
return this.workerManager.runTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
55
|
+
return this.workerManager.runTests(afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData, workerCount, stopOnError)
|
|
56
56
|
.then(afterTestsResults => Object.assign(executionResults, afterTestsResults));
|
|
57
57
|
};
|
|
58
58
|
|
|
@@ -63,13 +63,19 @@ class TestPlanRunner {
|
|
|
63
63
|
const sessionType = utils.getSessionType(tpOptions);
|
|
64
64
|
analyticsService.analyticsExecsStart({ authData, executionId, projectId: tpOptions.project, sessionType });
|
|
65
65
|
perf.log('right before runBeforeTests');
|
|
66
|
-
return runBeforeTests(beforeTests, testStatus, executionId, tpOptions, branchToUse, authData)
|
|
66
|
+
return runBeforeTests(beforeTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData)
|
|
67
67
|
.log('right before runTestPlanTests')
|
|
68
|
-
.then(() => runTestPlanTests(tests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
68
|
+
.then(() => runTestPlanTests(tests, testStatus, executionId, executionName, tpOptions, branchToUse, authData))
|
|
69
69
|
.log('right after runTestPlanTests')
|
|
70
|
-
.then(() => runAfterTests(afterTests, testStatus, executionId, tpOptions, branchToUse, authData))
|
|
70
|
+
.then(() => runAfterTests(afterTests, testStatus, executionId, executionName, tpOptions, branchToUse, authData))
|
|
71
71
|
.then(() => executionResults)
|
|
72
|
-
.catch(
|
|
72
|
+
.catch(err => {
|
|
73
|
+
logger.error('error running test plan', { err });
|
|
74
|
+
if (err instanceof StopRunOnError) {
|
|
75
|
+
return catchBeforeTestsFailed(executionId);
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
});
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
async initRealDataService(projectId) {
|
|
@@ -144,10 +150,6 @@ class TestPlanRunner {
|
|
|
144
150
|
|
|
145
151
|
const isCodeMode = tpOptions.files.length > 0;
|
|
146
152
|
|
|
147
|
-
if (isCodeMode && tpOptions.mode === constants.CLI_MODE.SELENIUM) {
|
|
148
|
-
// in selenium mode we don't need to wait for the runner and clickim to sync, so we don't need to wait for reports.
|
|
149
|
-
testStatus.setAsyncReporting(true);
|
|
150
|
-
}
|
|
151
153
|
const testListInfoPromise = tpOptions.lightweightMode && tpOptions.lightweightMode.onlyTestIdsNoSuite ?
|
|
152
154
|
{ beforeTests, tests, afterTests } :
|
|
153
155
|
testStatus.executionStart(executionId, projectId, this.startTime, testPlanName);
|
|
@@ -165,7 +167,7 @@ class TestPlanRunner {
|
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
perf.log('before runTestAllPhases');
|
|
168
|
-
const results = await this.runTestAllPhases(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, branch, tpOptions, executionId, testStatus);
|
|
170
|
+
const results = await this.runTestAllPhases(testListInfo.beforeTests, testListInfo.tests, testListInfo.afterTests, branch, tpOptions, executionId, testPlanName || 'All Tests', testStatus);
|
|
169
171
|
const childResults = await Bluebird.resolve(childTestResults)
|
|
170
172
|
.timeout(TDK_CHILD_RESULTS_TIMEOUT)
|
|
171
173
|
.catch(async () => {
|
package/services/gridService.js
CHANGED
|
@@ -245,10 +245,6 @@ async function getTestPlanGridData(options) {
|
|
|
245
245
|
if (testPlanGrids.includes(undefined)) {
|
|
246
246
|
throw new ArgError('failed to find one of the test plan defined grid');
|
|
247
247
|
}
|
|
248
|
-
const gridTypes = _(testPlanGrids).map(grid => grid.type).uniq().value();
|
|
249
|
-
if (gridTypes.includes('testimMobile') && gridTypes.length > 1) {
|
|
250
|
-
throw new ArgError('Test plans cannot include two different grid types');
|
|
251
|
-
}
|
|
252
248
|
return getSerializableObject(_.first(testPlanGrids));
|
|
253
249
|
}
|
|
254
250
|
|
|
@@ -6,6 +6,7 @@ const perfLogger = require('../commons/performance-logger');
|
|
|
6
6
|
const MemoryFS = require('memory-fs');
|
|
7
7
|
const mfs = new MemoryFS();
|
|
8
8
|
const AbortController = require("abort-controller");
|
|
9
|
+
const logger = require('../commons/logger').getLogger('hybrid-step-playback');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @type {Map<string, import("abort-controller")>}
|
|
@@ -119,10 +120,12 @@ module.exports.execute = async function execute(step, context, driver, loginData
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
return { success: false, shouldRetry: false, reason: 'unknown hybrid format ' + hybridFunction.type };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
logger.log('error running hybrid step', { err });
|
|
122
125
|
} finally {
|
|
123
126
|
runningStepsAbortControllersRegistry.delete(context.stepResultId);
|
|
124
127
|
}
|
|
125
|
-
}
|
|
128
|
+
};
|
|
126
129
|
|
|
127
130
|
module.exports.abort = function abort(stepResultId) {
|
|
128
131
|
const abortController = runningStepsAbortControllersRegistry.get(stepResultId);
|
package/testRunHandler.js
CHANGED
|
@@ -7,6 +7,7 @@ const testimServicesApi = require('./commons/testimServicesApi');
|
|
|
7
7
|
const { timeoutMessages, CLI_MODE } = require('./commons/constants');
|
|
8
8
|
const logger = require('./commons/logger').getLogger('test-run-handler');
|
|
9
9
|
const perf = require('./commons/performance-logger');
|
|
10
|
+
const { URL } = require('url');
|
|
10
11
|
const Promise = require('bluebird');
|
|
11
12
|
const _ = require('lodash');
|
|
12
13
|
const remoteStepPlayback = require('./stepPlayers/remoteStepPlayback');
|
|
@@ -14,13 +15,13 @@ const utils = require('./utils');
|
|
|
14
15
|
const config = require('./commons/config');
|
|
15
16
|
const analytics = require('./commons/testimAnalytics');
|
|
16
17
|
const { SeleniumPerfStats } = require('./commons/SeleniumPerfStats');
|
|
17
|
-
const featureFlags = require('./commons/featureFlags');
|
|
18
18
|
const { preloadTests } = require('./commons/preloadTests');
|
|
19
19
|
|
|
20
20
|
const RETRIES_ON_TIMEOUT = 3;
|
|
21
21
|
|
|
22
|
-
const TestRun = function (executionId, test, options, branchToUse, testRunStatus) {
|
|
22
|
+
const TestRun = function (executionId, executionName, test, options, branchToUse, testRunStatus) {
|
|
23
23
|
this._executionId = executionId;
|
|
24
|
+
this._executionName = executionName;
|
|
24
25
|
this._testStatus = test.testStatus;
|
|
25
26
|
this._testId = test.testId;
|
|
26
27
|
this._testName = test.name;
|
|
@@ -80,6 +81,10 @@ TestRun.prototype.getExecutionId = function () {
|
|
|
80
81
|
return this._executionId;
|
|
81
82
|
};
|
|
82
83
|
|
|
84
|
+
TestRun.prototype.getExecutionName = function () {
|
|
85
|
+
return this._executionName;
|
|
86
|
+
};
|
|
87
|
+
|
|
83
88
|
TestRun.prototype.getNativeAppData = function () {
|
|
84
89
|
if (this._nativeApp && !this._options.baseUrl) {
|
|
85
90
|
return this._nativeApp;
|
|
@@ -121,6 +126,7 @@ TestRun.prototype.getRunRequestParams = async function () {
|
|
|
121
126
|
refreshToken: testimCustomToken.getRefreshToken(),
|
|
122
127
|
projectId: this._options.project,
|
|
123
128
|
executionId: this._executionId,
|
|
129
|
+
executionName: this._executionName,
|
|
124
130
|
testId: this._testId,
|
|
125
131
|
resultId: this._testResultId,
|
|
126
132
|
baseUrl: this._baseUrl,
|
|
@@ -251,9 +257,6 @@ TestRun.prototype.clearTestResult = function () {
|
|
|
251
257
|
testRetryKey: this.getRetryKey(),
|
|
252
258
|
});
|
|
253
259
|
});
|
|
254
|
-
if (this._testRunStatus.asyncReporting) {
|
|
255
|
-
return Promise.resolve();
|
|
256
|
-
}
|
|
257
260
|
return this.clearTestResultFinished;
|
|
258
261
|
};
|
|
259
262
|
|
|
@@ -316,6 +319,26 @@ TestRun.prototype.isRetryKeyMismatch = function (testResult) {
|
|
|
316
319
|
return testResult.testRetryKey && (testResult.testRetryKey !== this.getRetryKey());
|
|
317
320
|
};
|
|
318
321
|
|
|
322
|
+
TestRun.prototype.validateRunConfig = function () {
|
|
323
|
+
const baseUrl = this.getBaseUrl();
|
|
324
|
+
const { browserValue } = this.getRunConfig();
|
|
325
|
+
|
|
326
|
+
if (baseUrl && browserValue === 'safari') {
|
|
327
|
+
let parsedUrl;
|
|
328
|
+
try {
|
|
329
|
+
parsedUrl = new URL(baseUrl);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
// ignore invalid URLs (missing http:// or https:// prefix)
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const { username, password } = parsedUrl;
|
|
335
|
+
|
|
336
|
+
if (username || password) {
|
|
337
|
+
throw new Error('Basic authentication in URL is not supported in Safari');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
319
342
|
TestRun.prototype.onStarted = function (startTimeout) {
|
|
320
343
|
return new Promise(resolve => {
|
|
321
344
|
// We can't leave the test result as it may remove other listeners as well
|
|
@@ -467,7 +490,7 @@ TestRun.prototype.onCompleted = function () {
|
|
|
467
490
|
waitForTestEnd();
|
|
468
491
|
}
|
|
469
492
|
} catch (err) {
|
|
470
|
-
logger.error('failed to check is complete', {err});
|
|
493
|
+
logger.error('failed to check is complete', { err });
|
|
471
494
|
waitForTestEnd();
|
|
472
495
|
}
|
|
473
496
|
}, 3000);
|
|
@@ -510,7 +533,7 @@ TestRun.prototype.isAllowReportTestResultRetries = function () {
|
|
|
510
533
|
TestRun.prototype.onRetry = async function () {
|
|
511
534
|
this._previousTestResultId = this._testResultId;
|
|
512
535
|
|
|
513
|
-
if (!
|
|
536
|
+
if (!this.isAllowReportTestResultRetries()) {
|
|
514
537
|
return;
|
|
515
538
|
}
|
|
516
539
|
|
package/testRunStatus.js
CHANGED
|
@@ -23,11 +23,11 @@ const gitRepoUrl = process.env.GIT_URL || process.env.CIRCLE_REPOSITORY_URL;
|
|
|
23
23
|
const runnerVersion = utils.getRunnerVersion();
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
function runHook(fn,
|
|
26
|
+
function runHook(fn, ...args) {
|
|
27
27
|
if (!fn || typeof fn !== 'function') {
|
|
28
28
|
return Promise.resolve();
|
|
29
29
|
}
|
|
30
|
-
return Promise.try(() => fn(
|
|
30
|
+
return Promise.try(() => fn(...args) || {}).catch(err => {
|
|
31
31
|
logger.warn('failed to run hook', { err });
|
|
32
32
|
throw new ArgError(`failed to run hook promise ${err.message}`);
|
|
33
33
|
});
|
|
@@ -43,7 +43,6 @@ const RunStatus = function (testInfoList, options, testPlanId, branchToUse) {
|
|
|
43
43
|
this.exportsGlobal = {};
|
|
44
44
|
this.testInfoList = testInfoList;
|
|
45
45
|
|
|
46
|
-
this.asyncReporting = false; // whether or not we wait for result reporting
|
|
47
46
|
this.executionStartedPromise = Promise.resolve();
|
|
48
47
|
|
|
49
48
|
const browserNames = utils.getUniqBrowsers(options, testInfoList);
|
|
@@ -92,9 +91,6 @@ const RunStatus = function (testInfoList, options, testPlanId, branchToUse) {
|
|
|
92
91
|
RunStatus.prototype.waitForExecutionStartedFinished = function () {
|
|
93
92
|
return this.executionStartedPromise;
|
|
94
93
|
};
|
|
95
|
-
RunStatus.prototype.setAsyncReporting = function (isAsyncReportingEnabled = false) {
|
|
96
|
-
this.asyncReporting = isAsyncReportingEnabled;
|
|
97
|
-
};
|
|
98
94
|
RunStatus.prototype.getTestResult = function (resultId) {
|
|
99
95
|
return this.testRunStatus[resultId];
|
|
100
96
|
};
|
|
@@ -157,7 +153,7 @@ RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testR
|
|
|
157
153
|
return this.executionStartedPromise;
|
|
158
154
|
}
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
return servicesApi.updateTestDataArtifact(projectId, test.testId, test.resultId, test.config.testData, projectData.defaults)
|
|
161
157
|
.catch(err => {
|
|
162
158
|
logger.error('failed to upload test data artifact (runner)', { err });
|
|
163
159
|
return '';
|
|
@@ -169,19 +165,13 @@ RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testR
|
|
|
169
165
|
await this.executionStartedPromise;
|
|
170
166
|
return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: testConfig, remoteRunId, testRetryKey });
|
|
171
167
|
});
|
|
172
|
-
if (this.asyncReporting) {
|
|
173
|
-
// silence is golden
|
|
174
|
-
} else {
|
|
175
|
-
return res;
|
|
176
|
-
}
|
|
177
|
-
return servicesApi.updateTestStatus(projectId, executionId, test.testId, test.resultId, 'RUNNING', { startTime: test.startTime, config: test.config, remoteRunId, testRetryKey });
|
|
178
168
|
};
|
|
179
169
|
|
|
180
170
|
RunStatus.prototype.testStartReport = function (test, executionId, testRetryKey) {
|
|
181
171
|
if (utils.isQuarantineAndNotRemoteRun(test, this.options)) {
|
|
182
172
|
return Promise.resolve();
|
|
183
173
|
}
|
|
184
|
-
return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }))
|
|
174
|
+
return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
|
|
185
175
|
.then(params => {
|
|
186
176
|
this.options.runParams[test.resultId] = test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
|
|
187
177
|
test.startTime = Date.now();
|
|
@@ -278,7 +268,7 @@ RunStatus.prototype.testEndReport = async function (test, executionId, result, t
|
|
|
278
268
|
const globalParameters = result.exportsGlobal;
|
|
279
269
|
try {
|
|
280
270
|
try {
|
|
281
|
-
await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }));
|
|
271
|
+
await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
|
|
282
272
|
} catch (err) {
|
|
283
273
|
logger.error('HOOK threw an error', { test: test.testId, err });
|
|
284
274
|
// eslint-disable-next-line no-console
|
|
@@ -406,10 +396,7 @@ RunStatus.prototype.executionStart = function (executionId, projectId, startTime
|
|
|
406
396
|
const ret = servicesApi.reportExecutionStarted(data);
|
|
407
397
|
this.executionStartedPromise = ret;
|
|
408
398
|
ret.catch(e => logger.error(e));
|
|
409
|
-
|
|
410
|
-
return ret;
|
|
411
|
-
}
|
|
412
|
-
return undefined;
|
|
399
|
+
return ret;
|
|
413
400
|
});
|
|
414
401
|
};
|
|
415
402
|
|
package/workers/BaseWorker.js
CHANGED
|
@@ -387,6 +387,7 @@ class BaseWorker {
|
|
|
387
387
|
!disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
|
|
388
388
|
!disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
|
|
389
389
|
])
|
|
390
|
+
.then(() => testRunHandler.validateRunConfig())
|
|
390
391
|
.then(() => this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun))
|
|
391
392
|
.then(testResult => onRunComplete(testResult, testRunHandler))
|
|
392
393
|
.then(result => {
|
|
@@ -88,6 +88,9 @@ class WorkerSelenium extends BaseWorker {
|
|
|
88
88
|
|
|
89
89
|
setupCliPerformanceMonitoring(sessionPlayer);
|
|
90
90
|
|
|
91
|
+
sessionPlayer.playbackManager.executionId = testRunHandler.getExecutionId();
|
|
92
|
+
sessionPlayer.playbackManager.executionName = testRunHandler.getExecutionName();
|
|
93
|
+
|
|
91
94
|
sessionPlayer.setLightweightMode(this.options.lightweightMode);
|
|
92
95
|
if (sessionPlayerInit.localAssetService) {
|
|
93
96
|
sessionPlayerInit.localAssetService.initialize({ serverUrl: this.options.localRCASaver });
|