@testim/testim-cli 3.266.0 → 3.268.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/commons/socket/baseSocketServiceSocketIO.js +25 -2
- package/commons/socket/testResultService.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +14 -7
- package/commons/testimServicesApi.js +31 -5
- package/npm-shrinkwrap.json +38 -38
- package/package.json +1 -1
- package/reports/consoleReporter.js +6 -2
- package/runOptions.d.ts +4 -3
- package/runOptions.js +3 -2
- package/runner.js +1 -1
- package/runners/ParallelWorkerManager.js +66 -12
- package/runners/TestPlanRunner.js +2 -4
- package/services/gridService.js +48 -36
- package/services/gridService.test.js +11 -18
- package/testRunHandler.js +216 -235
- package/testRunStatus.js +9 -1
- package/testimNpmDriver.js +15 -18
- package/workers/BaseWorker.js +98 -44
- package/workers/BaseWorker.test.js +2 -7
- package/workers/WorkerAppium.js +64 -24
- package/workers/WorkerExtension.js +11 -5
- package/workers/WorkerExtensionSingleBrowser.js +12 -2
- package/workers/WorkerSelenium.js +29 -20
package/testRunStatus.js
CHANGED
|
@@ -233,7 +233,11 @@ class RunStatus {
|
|
|
233
233
|
return this.testStartReport(test, executionId, testRetryKey);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
/**
|
|
237
|
+
* @param {string} resultId
|
|
238
|
+
* @param {Awaited<ReturnType<typeof import('./services/gridService')['getGridSlot']>>} gridInfo
|
|
239
|
+
*/
|
|
240
|
+
onGridSlot(resultId, gridInfo) {
|
|
237
241
|
const test = this.getTestResult(resultId);
|
|
238
242
|
test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
|
|
239
243
|
logger.info('on get grid info', { gridInfo: test.config.gridInfo });
|
|
@@ -264,6 +268,10 @@ class RunStatus {
|
|
|
264
268
|
return result.success ? constants.runnerTestStatus.PASSED : constants.runnerTestStatus.FAILED;
|
|
265
269
|
}
|
|
266
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @param {number} wid
|
|
273
|
+
* @param {Parameters<ConstructorParameters<typeof import('./workers/BaseWorker')>[7]>[1]} resultId
|
|
274
|
+
*/
|
|
267
275
|
onTestIgnored(wid, resultId) {
|
|
268
276
|
const test = this.getTestResult(resultId);
|
|
269
277
|
reporter.onTestIgnored(wid, test, `test in ${constants.testStatus.QUARANTINE}`);
|
package/testimNpmDriver.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
-
const Promise = require('bluebird');
|
|
3
2
|
const semver = require('semver');
|
|
4
3
|
const config = require('./commons/config');
|
|
5
4
|
const fs = require('fs');
|
|
@@ -7,10 +6,9 @@ const logger = require('./commons/logger').getLogger('npm-driver');
|
|
|
7
6
|
const localRunnerCache = require('./commons/runnerFileCache');
|
|
8
7
|
const npmWrapper = require('./commons/npmWrapper');
|
|
9
8
|
const chalk = require('chalk');
|
|
9
|
+
const utils = require('./utils');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
return Promise.resolve(npmWrapper.getLatestPackageVersion(packName));
|
|
13
|
-
}
|
|
11
|
+
const getNpmVersion = localRunnerCache.memoize(() => npmWrapper.getLatestPackageVersion('@testim/testim-cli'), 'getNpmVersion');
|
|
14
12
|
|
|
15
13
|
function getPackageVersion() {
|
|
16
14
|
try {
|
|
@@ -29,22 +27,21 @@ function getPackageVersion() {
|
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
function checkNpmVersion() {
|
|
30
|
+
async function checkNpmVersion() {
|
|
33
31
|
if (config.IS_ON_PREM) {
|
|
34
|
-
return
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const latestVersion = await utils.promiseTimeout(getNpmVersion('@testim/testim-cli'), 5000, 'The API call to NPM timed out');
|
|
36
|
+
const packVersion = getPackageVersion();
|
|
37
|
+
if (packVersion && semver.lt(packVersion, latestVersion)) {
|
|
38
|
+
console.log(chalk.yellow(
|
|
39
|
+
`Warning: You are using version ${packVersion}, a newer version is available. To update please run npm install (npm install -g @testim/testim-cli)`)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
logger.warn('Failed to get NPM version', { err });
|
|
35
44
|
}
|
|
36
|
-
return localRunnerCache.memoize(() => getNpmVersion('@testim/testim-cli')
|
|
37
|
-
.timeout(5000, 'The API call to NPM timed out')
|
|
38
|
-
.then(latestVersion => {
|
|
39
|
-
const packVersion = getPackageVersion();
|
|
40
|
-
if (packVersion && semver.lt(packVersion, latestVersion)) {
|
|
41
|
-
console.log(chalk.yellow(
|
|
42
|
-
`Warning: You are using version ${packVersion}, a newer version is available. To update please run npm install (npm install -g @testim/testim-cli)`)
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
.catch(err => logger.warn('Failed to get NPM version', { err }))
|
|
47
|
-
.then(() => true), 'checkNpmVersion');
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
module.exports = {
|
package/workers/BaseWorker.js
CHANGED
|
@@ -27,6 +27,12 @@ const { SETUP_TIMEOUT, NETWORK_ERROR, GRID_ERROR, BROWSER_CLOSED, SELENIUM_ERROR
|
|
|
27
27
|
const DELAY_BETWEEN_TESTS = ms('1s');
|
|
28
28
|
let ordinal = 1;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} testId
|
|
32
|
+
* @param {string} testName
|
|
33
|
+
* @param {string} resultId
|
|
34
|
+
* @param {string} reason
|
|
35
|
+
*/
|
|
30
36
|
function buildFailureResult(testId, testName, resultId, reason) {
|
|
31
37
|
return {
|
|
32
38
|
testId,
|
|
@@ -43,10 +49,10 @@ class BaseWorker {
|
|
|
43
49
|
* @param {import('../runOptions').RunnerOptions} options
|
|
44
50
|
* @param {string=} customExtensionLocalLocation
|
|
45
51
|
* @param {string} executionId
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {
|
|
52
|
+
* @param {(workerId: number, testId: string, resultId: string, isRerun: boolean | undefined, testRetryKey: `${number}:${number}` ) => Promise<any>} onTestStarted
|
|
53
|
+
* @param {(workerId: number, testId: string, testResult: any, sessionId: string, shouldRerun: boolean | undefined) => Promise<void>} onTestCompleted
|
|
54
|
+
* @param {import('../testRunStatus')['onGridSlot']} onGridSlot
|
|
55
|
+
* @param {(workerId: number, testResult: { name: string; testId: string; resultId: string; runnerStatus: 'SKIPPED'; testStatus: 'quarantine' }) => void} onTestIgnored
|
|
50
56
|
* @param {boolean=} releaseSlotOnTestFinished
|
|
51
57
|
*/
|
|
52
58
|
constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
|
|
@@ -75,10 +81,17 @@ class BaseWorker {
|
|
|
75
81
|
return ordinal++;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} browser
|
|
86
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
87
|
+
*/
|
|
88
|
+
async getGridSlot(browser, testRunHandler) {
|
|
89
|
+
const slot = await gridService.getGridSlot(browser, testRunHandler.executionId, this.options, this.id);
|
|
90
|
+
this.onGridSlot(testRunHandler.testResultId, slot);
|
|
91
|
+
return slot;
|
|
80
92
|
}
|
|
81
93
|
|
|
94
|
+
/** @param {import('../testRunHandler')} testRunHandler */
|
|
82
95
|
async getSlotOnce(testRunHandler) {
|
|
83
96
|
const { browserValue } = this.testRunConfig;
|
|
84
97
|
reporter.onGetSlot(this.id, browserValue || 'chrome');
|
|
@@ -86,6 +99,10 @@ class BaseWorker {
|
|
|
86
99
|
return gridInfo;
|
|
87
100
|
}
|
|
88
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @abstract
|
|
104
|
+
* @returns {import('../player/appiumTestPlayer') | import('../player/seleniumTestPlayer') | import('../player/extensionTestPlayer') | import('../player/chromeLauncherTestPlayer')}
|
|
105
|
+
*/
|
|
89
106
|
initPlayer() {
|
|
90
107
|
throw new NotImplementedError(true);
|
|
91
108
|
}
|
|
@@ -94,30 +111,35 @@ class BaseWorker {
|
|
|
94
111
|
throw new NotImplementedError(true);
|
|
95
112
|
}
|
|
96
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
116
|
+
* @param {ReturnType<typeof this['initPlayer']>} player
|
|
117
|
+
*/
|
|
97
118
|
async runTestOnce(testRunHandler, player) {
|
|
98
|
-
testRunHandler.
|
|
119
|
+
testRunHandler.sessionId = player.getSessionId();
|
|
99
120
|
logger.info('Test run started', {
|
|
100
|
-
testId: testRunHandler.
|
|
101
|
-
resultId: testRunHandler.
|
|
121
|
+
testId: testRunHandler.testId,
|
|
122
|
+
resultId: testRunHandler.testResultId,
|
|
102
123
|
seleniumSession: player.getSessionId(),
|
|
103
124
|
});
|
|
104
125
|
|
|
105
126
|
return await testRunHandler.clearTestResult();
|
|
106
127
|
}
|
|
107
128
|
|
|
129
|
+
/** @param {import('../testRunHandler')} testRunHandler */
|
|
108
130
|
handleQuarantine(testRunHandler) {
|
|
109
|
-
if (utils.isQuarantineAndNotRemoteRun({ testStatus: testRunHandler.
|
|
110
|
-
|
|
111
|
-
name: testRunHandler.getTestName(),
|
|
112
|
-
testId: testRunHandler.getTestId(),
|
|
113
|
-
resultId: testRunHandler.getTestResultId(),
|
|
114
|
-
runnerStatus: runnerTestStatus.SKIPPED,
|
|
115
|
-
testStatus: testRunHandler.getTestStatus(),
|
|
116
|
-
};
|
|
117
|
-
this.onTestIgnored(this.id, testResult);
|
|
118
|
-
return testResult;
|
|
131
|
+
if (!utils.isQuarantineAndNotRemoteRun({ testStatus: testRunHandler.testStatus }, this.options)) {
|
|
132
|
+
return undefined;
|
|
119
133
|
}
|
|
120
|
-
|
|
134
|
+
const testResult = {
|
|
135
|
+
name: testRunHandler.testName,
|
|
136
|
+
testId: testRunHandler.testId,
|
|
137
|
+
resultId: testRunHandler.testResultId,
|
|
138
|
+
runnerStatus: runnerTestStatus.SKIPPED,
|
|
139
|
+
testStatus: testRunHandler.testStatus,
|
|
140
|
+
};
|
|
141
|
+
this.onTestIgnored(this.id, testResult);
|
|
142
|
+
return testResult;
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
setSessionTimeout() {
|
|
@@ -127,6 +149,10 @@ class BaseWorker {
|
|
|
127
149
|
return Math.max(this.lambdatestService.getSessionTimeout, this.options.getSessionTimeout);
|
|
128
150
|
}
|
|
129
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
154
|
+
* @param {string=} customExtensionLocalLocation
|
|
155
|
+
*/
|
|
130
156
|
async getTestPlayer(testRunHandler, customExtensionLocalLocation) {
|
|
131
157
|
const projectId = this.userData?.projectId;
|
|
132
158
|
let testPlayer;
|
|
@@ -202,6 +228,11 @@ class BaseWorker {
|
|
|
202
228
|
return testPlayer;
|
|
203
229
|
}
|
|
204
230
|
|
|
231
|
+
/**
|
|
232
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
233
|
+
* @param {string=} customExtensionLocalLocation
|
|
234
|
+
* @param {boolean=} shouldRerun
|
|
235
|
+
*/
|
|
205
236
|
async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
|
|
206
237
|
perf.log('inside runTest');
|
|
207
238
|
const projectId = this.userData?.projectId;
|
|
@@ -211,7 +242,7 @@ class BaseWorker {
|
|
|
211
242
|
}
|
|
212
243
|
|
|
213
244
|
perf.log('before runTest onTestStarted');
|
|
214
|
-
const test = await this.onTestStarted(this.id, testRunHandler.
|
|
245
|
+
const test = await this.onTestStarted(this.id, testRunHandler.testId, testRunHandler.testResultId, shouldRerun, testRunHandler.retryKey);
|
|
215
246
|
testRunHandler._baseUrl = test.config.baseUrl;
|
|
216
247
|
|
|
217
248
|
const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
|
|
@@ -233,11 +264,16 @@ class BaseWorker {
|
|
|
233
264
|
run() {
|
|
234
265
|
const runNextTest = () => process.nextTick(() => this.run());
|
|
235
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @param {*} testResult
|
|
269
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
270
|
+
* @param {Error=} err
|
|
271
|
+
*/
|
|
236
272
|
const onRunComplete = async (testResult, testRunHandler, err) => {
|
|
237
273
|
if (utils.isQuarantineAndNotRemoteRun(testResult, this.options)) {
|
|
238
274
|
return runNextTest();
|
|
239
275
|
}
|
|
240
|
-
const sessionId = testRunHandler.
|
|
276
|
+
const sessionId = testRunHandler.sessionId;
|
|
241
277
|
|
|
242
278
|
const isTimeoutError = (timeoutMsg) => err.message.includes(timeoutMsg);
|
|
243
279
|
const isIgnoreErrors = err && (err instanceof GetBrowserError);
|
|
@@ -251,7 +287,7 @@ class BaseWorker {
|
|
|
251
287
|
);
|
|
252
288
|
|
|
253
289
|
try {
|
|
254
|
-
const testRetryKey = testRunHandler.
|
|
290
|
+
const testRetryKey = testRunHandler.retryKey;
|
|
255
291
|
testResult.testRetryKey = testRetryKey;
|
|
256
292
|
await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
|
|
257
293
|
if (this.executionQueue.hasMoreTests() && !this.options.lightweightMode?.general) {
|
|
@@ -271,7 +307,7 @@ class BaseWorker {
|
|
|
271
307
|
testRetryKey,
|
|
272
308
|
totalRetries: testRunHandler._totalRetryCount,
|
|
273
309
|
});
|
|
274
|
-
this.testResultId = testRunHandler.
|
|
310
|
+
this.testResultId = testRunHandler.testResultId;
|
|
275
311
|
return await runTestAndCalcResult(testRunHandler, shouldRerun);
|
|
276
312
|
}
|
|
277
313
|
return await runNextTest();
|
|
@@ -287,6 +323,10 @@ class BaseWorker {
|
|
|
287
323
|
const getNetworkErrorMessage = () => 'Due to network connectivity issues, Testim CLI has been unable to connect to the grid.\n' +
|
|
288
324
|
`Please make sure the CLI has stable access to the internet. ${didNetworkConnectivityTestFail() ? '(Internal: network connectivity test failed)' : ''}`;
|
|
289
325
|
|
|
326
|
+
/**
|
|
327
|
+
* @param {Error} err
|
|
328
|
+
* @param {boolean} wasNetworkHealthy
|
|
329
|
+
*/
|
|
290
330
|
const buildError = (err, wasNetworkHealthy) => {
|
|
291
331
|
if (!wasNetworkHealthy && featureFlags.flags.errorMessageOnBadNetwork.isEnabled()) {
|
|
292
332
|
return {
|
|
@@ -344,6 +384,10 @@ class BaseWorker {
|
|
|
344
384
|
return { errorType: UNKNOWN_ERROR, reason: msg };
|
|
345
385
|
};
|
|
346
386
|
|
|
387
|
+
/**
|
|
388
|
+
* @param {Error} err
|
|
389
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
390
|
+
*/
|
|
347
391
|
const onRunError = async (err, testRunHandler) => {
|
|
348
392
|
const wasNetworkHealthy = await isNetworkHealthy();
|
|
349
393
|
if (!wasNetworkHealthy && featureFlags.flags.warnOnBadNetwork.isEnabled()) {
|
|
@@ -360,12 +404,16 @@ class BaseWorker {
|
|
|
360
404
|
success: false,
|
|
361
405
|
reason,
|
|
362
406
|
errorType,
|
|
363
|
-
testRetryKey: testRunHandler.
|
|
407
|
+
testRetryKey: testRunHandler.retryKey,
|
|
364
408
|
setupStepResult: { status: testRunStatus.COMPLETED, success: false, reason, errorType },
|
|
365
|
-
}, testRunHandler.
|
|
409
|
+
}, testRunHandler.remoteRunId);
|
|
366
410
|
await onRunComplete(buildFailureResult(this.testId, this.testName, this.testResultId, reason), testRunHandler, err);
|
|
367
411
|
};
|
|
368
412
|
|
|
413
|
+
/**
|
|
414
|
+
* @param {Error} runError
|
|
415
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
416
|
+
*/
|
|
369
417
|
const recoverTestResults = async (runError, testRunHandler) => {
|
|
370
418
|
const testId = this.testId;
|
|
371
419
|
const resultId = this.testResultId;
|
|
@@ -401,35 +449,41 @@ class BaseWorker {
|
|
|
401
449
|
const disableResults = this.options.disableSockets || (this.options.lightweightMode?.disableResults && (this.options.useChromeLauncher || this.options.mode !== 'extension'));
|
|
402
450
|
const disableRemoteStep = this.options.disableSockets || (this.options.lightweightMode?.disableRemoteStep);
|
|
403
451
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
452
|
+
/**
|
|
453
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
454
|
+
* @param {boolean=} shouldRerun
|
|
455
|
+
*/
|
|
456
|
+
const runTestAndCalcResult = async (testRunHandler, shouldRerun) => {
|
|
457
|
+
try {
|
|
458
|
+
await Promise.all([
|
|
459
|
+
!disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
|
|
460
|
+
!disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
|
|
461
|
+
]);
|
|
462
|
+
testRunHandler.validateRunConfig();
|
|
463
|
+
const testResult = await this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun);
|
|
464
|
+
const result = await onRunComplete(testResult, testRunHandler);
|
|
412
465
|
perf.log('After onRunComplete');
|
|
413
466
|
return result;
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
|
|
467
|
+
} catch (runError) {
|
|
468
|
+
return recoverTestResults(runError, testRunHandler);
|
|
469
|
+
} finally {
|
|
417
470
|
if (!disableRemoteStep) {
|
|
418
471
|
remoteStepService.unlistenToRemoteStep(this.testResultId);
|
|
419
472
|
}
|
|
420
|
-
}
|
|
473
|
+
}
|
|
474
|
+
};
|
|
421
475
|
|
|
422
476
|
const testRunHandler = this.executionQueue.getNext();
|
|
423
477
|
|
|
424
478
|
if (!testRunHandler) { // no more tests to run
|
|
425
479
|
return this.onQueueCompleted();
|
|
426
480
|
}
|
|
427
|
-
this.testId = testRunHandler.
|
|
428
|
-
this.testName = testRunHandler.
|
|
429
|
-
this.testResultId = testRunHandler.
|
|
430
|
-
this.overrideTestConfigId = testRunHandler.
|
|
431
|
-
this.testRunConfig = testRunHandler.
|
|
432
|
-
this.branch = testRunHandler.
|
|
481
|
+
this.testId = testRunHandler.testId;
|
|
482
|
+
this.testName = testRunHandler.testName;
|
|
483
|
+
this.testResultId = testRunHandler.testResultId;
|
|
484
|
+
this.overrideTestConfigId = testRunHandler.overrideTestConfigId;
|
|
485
|
+
this.testRunConfig = testRunHandler.runConfig;
|
|
486
|
+
this.branch = testRunHandler.branch;
|
|
433
487
|
|
|
434
488
|
return runTestAndCalcResult(testRunHandler);
|
|
435
489
|
}
|
|
@@ -25,12 +25,12 @@ describe('BaseWorker', () => {
|
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
worker = new BaseWorker(null, {}, null, null, onTestStartedStub);
|
|
28
|
+
worker = new BaseWorker(null, {}, null, null, onTestStartedStub, null, () => { /* noop */ });
|
|
29
29
|
worker.userData = {};
|
|
30
30
|
worker.options = { gridData: {}, browser: 'chrome', company: { companyId: 'companyId' }, getBrowserTimeout: 1000, getSessionTimeout: 100, getBrowserRetries: 10 };
|
|
31
31
|
worker.testRunConfig = {};
|
|
32
32
|
|
|
33
|
-
testRunHandlerMock = {
|
|
33
|
+
testRunHandlerMock = { executionId: 'executionId', testResultId: 'testResultId' };
|
|
34
34
|
testPlayerMock = { onDone: sinon.spy() };
|
|
35
35
|
|
|
36
36
|
sinon.stub(worker, 'initPlayer').returns(testPlayerMock);
|
|
@@ -170,11 +170,6 @@ describe('BaseWorker', () => {
|
|
|
170
170
|
it('should call the runTestOnc with the base url of the test object we acquired from onTestStarted', async () => {
|
|
171
171
|
const testRunHandler = {
|
|
172
172
|
_baseUrl: 'https://testim.io',
|
|
173
|
-
getTestStatus: () => sinon.stub().returns(42),
|
|
174
|
-
getTestId: () => sinon.stub().returns(42),
|
|
175
|
-
getTestResultId: () => sinon.stub().returns(42),
|
|
176
|
-
getRetryKey: () => sinon.stub().returns(42),
|
|
177
|
-
getExecutionId: () => sinon.stub().returns(42),
|
|
178
173
|
testRunHandler: () => sinon.stub().returns(42),
|
|
179
174
|
clearTestResult: () => sinon.stub().returns(42),
|
|
180
175
|
};
|
package/workers/WorkerAppium.js
CHANGED
|
@@ -8,33 +8,46 @@ const AppiumTestPlayer = require('../player/appiumTestPlayer');
|
|
|
8
8
|
const sessionPlayerInit = require('../commons/getSessionPlayerRequire');
|
|
9
9
|
const AppiumApi = require('../commons/getSessionPlayerRequire').AppiumApi;
|
|
10
10
|
const desiredCapabilitiesBuilder = require('../commons/testimDesiredCapabilitiesBuilder');
|
|
11
|
+
const testimServicesApi = require('../commons/testimServicesApi');
|
|
12
|
+
const config = require('../commons/config');
|
|
11
13
|
|
|
12
14
|
class WorkerAppium extends BaseWorker {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @override
|
|
17
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
18
|
+
*/
|
|
18
19
|
initPlayer(testRunHandler) {
|
|
19
|
-
return new AppiumTestPlayer(
|
|
20
|
-
|
|
20
|
+
return new AppiumTestPlayer(
|
|
21
|
+
this.id,
|
|
22
|
+
testRunHandler.runParams,
|
|
21
23
|
this.options.shouldMonitorPerformance,
|
|
22
|
-
testRunHandler.
|
|
24
|
+
testRunHandler.automationMode,
|
|
23
25
|
undefined,
|
|
24
|
-
testRunHandler.
|
|
25
|
-
testRunHandler.
|
|
26
|
+
testRunHandler.retryCount,
|
|
27
|
+
testRunHandler.previousTestResultId,
|
|
28
|
+
);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
async getBrowserOnce(testRunHandler, customExtensionLocalLocation, appiumTestPlayer, gridInfo) {
|
|
29
|
-
reporter.onGetSession(this.id, this.testName, testRunHandler.
|
|
32
|
+
reporter.onGetSession(this.id, this.testName, testRunHandler.runMode);
|
|
30
33
|
const { driver } = appiumTestPlayer;
|
|
31
|
-
|
|
34
|
+
let appPath = null;
|
|
35
|
+
if (this.options.appId) {
|
|
36
|
+
const { project: projectId, appId } = this.options;
|
|
37
|
+
const mobileApp = await testimServicesApi.getAppDetails({ appId, projectId });
|
|
38
|
+
if (!mobileApp) {
|
|
39
|
+
logger.error('mobile app not found', { appId, projectId });
|
|
40
|
+
throw new Error('mobile app not found');
|
|
41
|
+
}
|
|
42
|
+
appPath = `${config.SERVICES_HOST}/storage${mobileApp.filePath}?access_token=${this.options.authData.token}`;
|
|
43
|
+
}
|
|
44
|
+
const nativeApp = await testRunHandler.getNativeAppData();
|
|
32
45
|
const projectType = this.options.projectData.type;
|
|
33
|
-
const capabilities = desiredCapabilitiesBuilder.buildAppiumOptions({ projectType, gridInfo, testRunConfig: this.testRunConfig, nativeApp, options: this.options });
|
|
34
46
|
try {
|
|
35
|
-
|
|
47
|
+
const capabilities = desiredCapabilitiesBuilder.buildAppiumOptions({ projectType, gridInfo, testRunConfig: this.testRunConfig, nativeApp, options: this.options, appPath });
|
|
36
48
|
const activeSession = await driver.remote(capabilities);
|
|
37
49
|
driver.activeSession = activeSession;
|
|
50
|
+
await this.updateDeviceInfo(testRunHandler, activeSession);
|
|
38
51
|
logger.info(`init new appium session testName: ${this.testName}`, { sessionId: activeSession.sessionId, testResultId: this.testResultId, nativeApp });
|
|
39
52
|
} catch (err) {
|
|
40
53
|
//TODO: catch app status validation
|
|
@@ -44,10 +57,34 @@ class WorkerAppium extends BaseWorker {
|
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
getServerAddressFromGrid(testRunHandler) {
|
|
47
|
-
const {
|
|
60
|
+
const { host, port, accessToken } = testRunHandler._options.gridData;
|
|
48
61
|
return `https://${host}:${port}/v0/${accessToken}/wd/hub`;
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
async updateDeviceInfo(testRunHandler, activeSession) {
|
|
65
|
+
const {
|
|
66
|
+
_executionId, _testId, _testResultId,
|
|
67
|
+
_options: { project: projectId },
|
|
68
|
+
} = testRunHandler;
|
|
69
|
+
const device = {
|
|
70
|
+
name: activeSession.capabilities.deviceName,
|
|
71
|
+
model: activeSession.capabilities.deviceModel,
|
|
72
|
+
osVersion: activeSession.capabilities.platformVersion,
|
|
73
|
+
udid: activeSession.capabilities.udid,
|
|
74
|
+
osType: activeSession.capabilities.platformName,
|
|
75
|
+
scaleFactor: activeSession.capabilities.pixelRatio,
|
|
76
|
+
virtual: false,
|
|
77
|
+
};
|
|
78
|
+
await testimServicesApi.updateTestStatus(
|
|
79
|
+
projectId,
|
|
80
|
+
_executionId,
|
|
81
|
+
_testId,
|
|
82
|
+
_testResultId,
|
|
83
|
+
'RUNNING',
|
|
84
|
+
{ device },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
getDirectAddressConnection(activeSessionCapabilities) {
|
|
52
89
|
const { directConnectProtocol: protocol, directConnectHost: host, directConnectPort: port, directConnectPath: path } = activeSessionCapabilities;
|
|
53
90
|
if (protocol && host && port && path) {
|
|
@@ -56,27 +93,29 @@ class WorkerAppium extends BaseWorker {
|
|
|
56
93
|
return undefined;
|
|
57
94
|
}
|
|
58
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
98
|
+
* @param {import('../player/appiumTestPlayer')} appiumTestPlayer
|
|
99
|
+
*/
|
|
59
100
|
async runTestOnce(testRunHandler, appiumTestPlayer) {
|
|
60
101
|
const { driver, sessionPlayer } = appiumTestPlayer;
|
|
61
102
|
const version = sessionPlayerInit.manifestVersion || 'runner';
|
|
62
103
|
|
|
63
104
|
reporter.onWaitToTestComplete(this.id, this.isCodeMode);
|
|
64
105
|
|
|
65
|
-
sessionPlayer.playbackManager.executionId = testRunHandler.
|
|
66
|
-
sessionPlayer.playbackManager.executionName = testRunHandler.
|
|
106
|
+
sessionPlayer.playbackManager.executionId = testRunHandler.executionId;
|
|
107
|
+
sessionPlayer.playbackManager.executionName = testRunHandler.executionName;
|
|
67
108
|
|
|
68
109
|
const serverAddress = this.getDirectAddressConnection(driver.activeSession.capabilities) || this.getServerAddressFromGrid(testRunHandler);
|
|
69
|
-
const DOMParser = new
|
|
110
|
+
const DOMParser = new jsdom.JSDOM('').window.DOMParser;
|
|
70
111
|
sessionPlayer.playbackManager.appiumApi = new AppiumApi(serverAddress, driver.activeSession.sessionId, DOMParser);
|
|
71
|
-
|
|
72
|
-
|
|
73
112
|
if (sessionPlayerInit.localAssetService) {
|
|
74
113
|
sessionPlayerInit.localAssetService.initialize({ serverUrl: this.options.localRCASaver });
|
|
75
114
|
}
|
|
76
115
|
async function runAppiumTest() {
|
|
77
116
|
try {
|
|
78
117
|
const INCOGNITO = false;
|
|
79
|
-
const testResult = await
|
|
118
|
+
const testResult = await new Promise((resolve, reject) =>
|
|
80
119
|
// eslint-disable-next-line no-promise-executor-return
|
|
81
120
|
sessionPlayer.playByTestId(
|
|
82
121
|
this.testId,
|
|
@@ -90,11 +129,12 @@ class WorkerAppium extends BaseWorker {
|
|
|
90
129
|
this.overrideTestConfigId,
|
|
91
130
|
this.branch,
|
|
92
131
|
INCOGNITO,
|
|
93
|
-
testRunHandler.
|
|
94
|
-
undefined,
|
|
132
|
+
testRunHandler.remoteRunId,
|
|
95
133
|
undefined,
|
|
96
134
|
undefined,
|
|
97
|
-
|
|
135
|
+
undefined
|
|
136
|
+
)
|
|
137
|
+
);
|
|
98
138
|
if (sessionPlayerInit.localAssetService) {
|
|
99
139
|
await sessionPlayerInit.localAssetService.drain();
|
|
100
140
|
}
|
|
@@ -21,6 +21,12 @@ class WorkerExtension extends BaseWorker {
|
|
|
21
21
|
return new ExtensionTestPlayer(this.id);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
26
|
+
* @param {string=} customExtensionLocalLocation
|
|
27
|
+
* @param {import('../player/extensionTestPlayer')} player
|
|
28
|
+
* @param {Awaited<ReturnType<import('../services/gridService')['getGridSlot']>>} gridInfo
|
|
29
|
+
*/
|
|
24
30
|
async _getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo) {
|
|
25
31
|
const { driver } = player;
|
|
26
32
|
try {
|
|
@@ -40,8 +46,8 @@ class WorkerExtension extends BaseWorker {
|
|
|
40
46
|
logger.error('failed to get browser', {
|
|
41
47
|
err,
|
|
42
48
|
gridInfo,
|
|
43
|
-
testId: testRunHandler.
|
|
44
|
-
resultId: testRunHandler.
|
|
49
|
+
testId: testRunHandler.testId,
|
|
50
|
+
resultId: testRunHandler.testResultId,
|
|
45
51
|
});
|
|
46
52
|
throw err;
|
|
47
53
|
}
|
|
@@ -49,7 +55,7 @@ class WorkerExtension extends BaseWorker {
|
|
|
49
55
|
|
|
50
56
|
/** @override */
|
|
51
57
|
async getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo) {
|
|
52
|
-
reporter.onGetSession(this.id, this.testName, testRunHandler.
|
|
58
|
+
reporter.onGetSession(this.id, this.testName, testRunHandler.runMode);
|
|
53
59
|
return this._getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo);
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -175,8 +181,8 @@ class WorkerExtension extends BaseWorker {
|
|
|
175
181
|
} catch (err) {
|
|
176
182
|
logger.error('failed to run test', {
|
|
177
183
|
err,
|
|
178
|
-
testId: testRunHandler.
|
|
179
|
-
resultId: testRunHandler.
|
|
184
|
+
testId: testRunHandler.testId,
|
|
185
|
+
resultId: testRunHandler.testResultId,
|
|
180
186
|
});
|
|
181
187
|
throw err;
|
|
182
188
|
}
|
|
@@ -23,10 +23,15 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo) {
|
|
26
|
-
reporter.onGetSession(this.id, `worker ${this.id}`, testRunHandler.
|
|
26
|
+
reporter.onGetSession(this.id, `worker ${this.id}`, testRunHandler.runMode);
|
|
27
27
|
return this._getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @override
|
|
32
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
33
|
+
* @param {string=} customExtensionLocalLocation
|
|
34
|
+
*/
|
|
30
35
|
async getTestPlayer(testRunHandler, customExtensionLocalLocation) {
|
|
31
36
|
if (this.testPlayer && !this.testPlayer.driver.isAlive()) {
|
|
32
37
|
logger.warn('WorkerExtensionSingleBrowser is releasing a dead player', { workerId: this.id });
|
|
@@ -38,6 +43,11 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
38
43
|
return this.testPlayer;
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
* @param {import('../testRunHandler')} testRunHandler
|
|
48
|
+
* @param {string=} customExtensionLocalLocation
|
|
49
|
+
* @param {boolean=} shouldRerun
|
|
50
|
+
*/
|
|
41
51
|
async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
|
|
42
52
|
const quarantineResult = this.handleQuarantine(testRunHandler);
|
|
43
53
|
if (quarantineResult) {
|
|
@@ -45,7 +55,7 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
perf.log('before runTest onTestStarted single browser');
|
|
48
|
-
const test = await this.onTestStarted(this.id, testRunHandler.
|
|
58
|
+
const test = await this.onTestStarted(this.id, testRunHandler.testId, testRunHandler.testResultId, shouldRerun, testRunHandler.retryKey);
|
|
49
59
|
testRunHandler._baseUrl = test.config.baseUrl;
|
|
50
60
|
const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
|
|
51
61
|
|