@testim/testim-cli 3.196.0 → 3.200.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/README.md +1 -1
- package/cli/onExit.js +12 -1
- package/cli.js +5 -1
- package/commons/constants.js +0 -25
- package/commons/featureFlags.js +2 -0
- package/commons/npmWrapper.js +46 -14
- package/commons/npmWrapper.test.js +182 -6
- package/commons/socket/testResultService.js +4 -14
- package/commons/testimAnalytics.js +0 -1
- package/commons/testimDesiredCapabilitiesBuilder.js +0 -94
- package/commons/testimServicesApi.js +9 -79
- package/executionQueue.js +7 -4
- package/npm-shrinkwrap.json +960 -524
- package/package.json +3 -1
- package/player/stepActions/baseJsStepAction.js +5 -1
- package/player/stepActions/pixelValidationStepAction.js +28 -0
- package/player/stepActions/salesforceAutoLoginStepAction.js +5 -3
- package/player/stepActions/stepActionRegistrar.js +4 -48
- package/player/utils/eyeSdkService.js +230 -0
- package/reports/consoleReporter.js +29 -44
- package/reports/reporter.js +0 -21
- package/runOptions.js +13 -89
- package/runner.js +3 -44
- package/runners/{strategies/LocalStrategy.js → ParallelWorkerManager.js} +59 -68
- package/runners/TestPlanRunner.js +288 -67
- package/runners/runnerUtils.js +73 -0
- package/services/analyticsService.js +94 -0
- package/services/gridService.js +24 -20
- package/services/gridService.test.js +21 -21
- package/stepPlayers/hybridStepPlayback.js +4 -1
- package/stepPlayers/tdkHybridStepPlayback.js +1 -0
- package/testRunHandler.js +23 -5
- package/testRunStatus.js +18 -27
- package/utils.js +5 -5
- package/workers/BaseWorker.js +39 -39
- package/workers/BaseWorker.test.js +1 -1
- package/workers/WorkerExtensionSingleBrowser.js +6 -3
- package/commons/apkUploader/apkUploader.js +0 -46
- package/commons/apkUploader/apkUploaderFactory.js +0 -68
- package/commons/apkUploader/deviceFarmApkUploader.js +0 -41
- package/commons/apkUploader/saucelabsApkUploader.js +0 -36
- package/commons/apkUploader/testObjectApkUploader.js +0 -34
- package/player/mobile/mobileTestPlayer.js +0 -80
- package/player/mobile/mobileWebDriver.js +0 -155
- package/player/mobile/services/frameLocatorMock.js +0 -18
- package/player/mobile/services/mobilePortSelector.js +0 -22
- package/player/mobile/services/mobileTabService.js +0 -241
- package/player/mobile/utils/mobileScreenshotUtils.js +0 -46
- package/player/mobile/utils/mobileWindowUtils.js +0 -84
- package/player/stepActions/mobile/android/androidLocateStepAction.js +0 -122
- package/player/stepActions/mobile/android/androidLongClickStepAction.js +0 -12
- package/player/stepActions/mobile/android/androidScrollStepAction.js +0 -134
- package/player/stepActions/mobile/android/androidSpecialKeyStepAction.js +0 -22
- package/player/stepActions/mobile/android/androidSwipeStepAction.js +0 -32
- package/player/stepActions/mobile/androidGlobalActionStepAction.js +0 -12
- package/player/stepActions/mobile/androidTapStepAction.js +0 -19
- package/player/stepActions/mobile/androidTextChangeStepAction.js +0 -23
- package/player/stepActions/mobile/ios/iosLocateStepAction.js +0 -124
- package/player/stepActions/mobile/ios/iosScrollStepAction.js +0 -76
- package/runners/AnonymousTestPlanRunner.js +0 -106
- package/runners/BaseRunner.js +0 -42
- package/runners/BaseTestPlanRunner.js +0 -194
- package/runners/DeviceFarmRemoteRunner.js +0 -50
- package/runners/SchedulerRemoteRunner.js +0 -47
- package/runners/strategies/BaseStrategy.js +0 -86
- package/runners/strategies/DeviceFarmStrategy.js +0 -195
- package/runners/strategies/LocalDeviceFarmStrategy.js +0 -12
- package/runners/strategies/LocalTestStrategy.js +0 -14
- package/runners/strategies/Strategy.js +0 -17
- package/workers/WorkerAppium.js +0 -70
|
@@ -175,19 +175,19 @@ describe('gridService', () => {
|
|
|
175
175
|
|
|
176
176
|
describe('getGridSlot', () => {
|
|
177
177
|
let getGridByIdStub;
|
|
178
|
-
let
|
|
178
|
+
let getGridByNameStub;
|
|
179
179
|
let onGridSlot;
|
|
180
180
|
let addItemToGridCacheStub;
|
|
181
181
|
|
|
182
182
|
beforeEach(() => {
|
|
183
183
|
getGridByIdStub = sinon.stub(servicesApi, 'getGridById').resolves({ grid: { gridId: 'gridId', type: 'gridId' }, status: 'success' });
|
|
184
|
-
|
|
184
|
+
getGridByNameStub = sinon.stub(servicesApi, 'getGridByName').resolves({ grid: { gridId: 'gridId', type: 'gridName' }, status: 'success' });
|
|
185
185
|
addItemToGridCacheStub = sinon.stub(gridService, 'addItemToGridCache').callThrough();
|
|
186
186
|
onGridSlot = sinon.stub().resolves();
|
|
187
187
|
});
|
|
188
188
|
afterEach(() => {
|
|
189
189
|
getGridByIdStub.restore();
|
|
190
|
-
|
|
190
|
+
getGridByNameStub.restore();
|
|
191
191
|
addItemToGridCacheStub.restore();
|
|
192
192
|
});
|
|
193
193
|
|
|
@@ -195,7 +195,7 @@ describe('gridService', () => {
|
|
|
195
195
|
const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useLocalChromeDriver: true }, 'workerId');
|
|
196
196
|
expect(slot).to.eql({ mode: 'local' });
|
|
197
197
|
sinon.assert.notCalled(getGridByIdStub);
|
|
198
|
-
sinon.assert.notCalled(
|
|
198
|
+
sinon.assert.notCalled(getGridByNameStub);
|
|
199
199
|
sinon.assert.notCalled(addItemToGridCacheStub);
|
|
200
200
|
sinon.assert.calledOnce(onGridSlot);
|
|
201
201
|
});
|
|
@@ -204,7 +204,7 @@ describe('gridService', () => {
|
|
|
204
204
|
const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { useChromeLauncher: true }, 'workerId');
|
|
205
205
|
expect(slot).to.eql({ mode: 'local' });
|
|
206
206
|
sinon.assert.notCalled(getGridByIdStub);
|
|
207
|
-
sinon.assert.notCalled(
|
|
207
|
+
sinon.assert.notCalled(getGridByNameStub);
|
|
208
208
|
sinon.assert.notCalled(addItemToGridCacheStub);
|
|
209
209
|
sinon.assert.calledOnce(onGridSlot);
|
|
210
210
|
});
|
|
@@ -213,7 +213,7 @@ describe('gridService', () => {
|
|
|
213
213
|
const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { host: 'localhost', port: 4444 }, 'workerId');
|
|
214
214
|
expect(slot).to.shallowDeepEqual({ type: 'hostAndPort', host: 'localhost', port: 4444 });
|
|
215
215
|
sinon.assert.notCalled(getGridByIdStub);
|
|
216
|
-
sinon.assert.notCalled(
|
|
216
|
+
sinon.assert.notCalled(getGridByNameStub);
|
|
217
217
|
sinon.assert.notCalled(addItemToGridCacheStub);
|
|
218
218
|
sinon.assert.calledOnce(onGridSlot);
|
|
219
219
|
});
|
|
@@ -222,7 +222,7 @@ describe('gridService', () => {
|
|
|
222
222
|
const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { gridId: 'gridId' }, 'workerId');
|
|
223
223
|
expect(slot).to.shallowDeepEqual({ type: 'gridId', gridId: 'gridId' });
|
|
224
224
|
sinon.assert.calledOnce(getGridByIdStub);
|
|
225
|
-
sinon.assert.notCalled(
|
|
225
|
+
sinon.assert.notCalled(getGridByNameStub);
|
|
226
226
|
sinon.assert.calledOnce(addItemToGridCacheStub);
|
|
227
227
|
sinon.assert.calledOnce(onGridSlot);
|
|
228
228
|
});
|
|
@@ -230,7 +230,7 @@ describe('gridService', () => {
|
|
|
230
230
|
it('should get grid from server when passing grid name', async () => {
|
|
231
231
|
const slot = await gridService.getGridSlot('browser', 'executionId', 'testResultId', onGridSlot, { grid: 'gridName' }, 'workerId');
|
|
232
232
|
expect(slot).to.shallowDeepEqual({ type: 'gridName', gridId: 'gridId' });
|
|
233
|
-
sinon.assert.calledOnce(
|
|
233
|
+
sinon.assert.calledOnce(getGridByNameStub);
|
|
234
234
|
sinon.assert.notCalled(getGridByIdStub);
|
|
235
235
|
sinon.assert.calledOnce(addItemToGridCacheStub);
|
|
236
236
|
sinon.assert.calledOnce(onGridSlot);
|
|
@@ -282,9 +282,9 @@ describe('gridService', () => {
|
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it('should call releaseGridSlot with the correct parameters', async () => {
|
|
285
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
285
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
286
286
|
await gridService.releaseGridSlot('workerId', 'projectId');
|
|
287
|
-
sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
|
|
287
|
+
sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
|
|
288
288
|
});
|
|
289
289
|
|
|
290
290
|
it('should not call releaseGridSlot if the workerId is not in the cache', async () => {
|
|
@@ -293,16 +293,16 @@ describe('gridService', () => {
|
|
|
293
293
|
});
|
|
294
294
|
|
|
295
295
|
it('should not call releaseGridSlot if no slotId', async () => {
|
|
296
|
-
gridService.addItemToGridCache('workerId', 'gridId');
|
|
296
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId');
|
|
297
297
|
await gridService.releaseGridSlot('workerId', 'projectId');
|
|
298
298
|
sinon.assert.notCalled(releaseGridSlotStub);
|
|
299
299
|
});
|
|
300
300
|
|
|
301
301
|
it('should handle releaseGridSlot request error', async () => {
|
|
302
302
|
releaseGridSlotStub.rejects({ });
|
|
303
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
303
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
304
304
|
await gridService.releaseGridSlot('workerId', 'projectId');
|
|
305
|
-
sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
|
|
305
|
+
sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
|
|
306
306
|
});
|
|
307
307
|
});
|
|
308
308
|
|
|
@@ -322,15 +322,15 @@ describe('gridService', () => {
|
|
|
322
322
|
});
|
|
323
323
|
|
|
324
324
|
it('should send keepAlive to server on an interval', async () => {
|
|
325
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
325
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
326
326
|
await gridService.keepAlive.start('projectId');
|
|
327
327
|
clock.tick(10010);
|
|
328
|
-
sinon.assert.calledOnceWithExactly(keepAliveStub, 'projectId', [{ gridId: 'gridId', slotId: 'slotId', browser: 'chrome' }]);
|
|
328
|
+
sinon.assert.calledOnceWithExactly(keepAliveStub, 'projectId', [{ gridId: 'gridId', companyId: 'companyId', slotId: 'slotId', browser: 'chrome' }]);
|
|
329
329
|
await gridService.releaseGridSlot('workerId', 'projectId');
|
|
330
330
|
});
|
|
331
331
|
|
|
332
332
|
it('should send keepAlive to server every 10 seconds', async () => {
|
|
333
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
333
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
334
334
|
await gridService.keepAlive.start('projectId');
|
|
335
335
|
clock.tick(30010);
|
|
336
336
|
sinon.assert.calledThrice(keepAliveStub);
|
|
@@ -345,7 +345,7 @@ describe('gridService', () => {
|
|
|
345
345
|
|
|
346
346
|
it('should handle keepAlive request error', async () => {
|
|
347
347
|
keepAliveStub.rejects({ });
|
|
348
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
348
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
349
349
|
await gridService.keepAlive.start('projectId');
|
|
350
350
|
clock.tick(10010);
|
|
351
351
|
sinon.assert.calledOnce(keepAliveStub);
|
|
@@ -353,13 +353,13 @@ describe('gridService', () => {
|
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
it('should release all slots when ending', async () => {
|
|
356
|
-
gridService.addItemToGridCache('workerId', 'gridId', 'slotId', 'chrome');
|
|
357
|
-
gridService.addItemToGridCache('workerId1', 'gridId', 'slotId1', 'firefox');
|
|
356
|
+
gridService.addItemToGridCache('workerId', 'companyId', 'gridId', 'slotId', 'chrome');
|
|
357
|
+
gridService.addItemToGridCache('workerId1', 'companyId', 'gridId', 'slotId1', 'firefox');
|
|
358
358
|
await gridService.keepAlive.start('projectId');
|
|
359
359
|
await gridService.keepAlive.end('projectId');
|
|
360
360
|
sinon.assert.calledTwice(releaseGridSlotStub);
|
|
361
|
-
sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId', 'gridId', 'chrome');
|
|
362
|
-
sinon.assert.calledWith(releaseGridSlotStub, 'projectId', 'slotId1', 'gridId', 'firefox');
|
|
361
|
+
sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId', 'gridId', 'chrome');
|
|
362
|
+
sinon.assert.calledWith(releaseGridSlotStub, 'companyId', 'projectId', 'slotId1', 'gridId', 'firefox');
|
|
363
363
|
});
|
|
364
364
|
|
|
365
365
|
it('should not release slots if there is no slots in use', async () => {
|
|
@@ -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');
|
|
@@ -241,7 +242,7 @@ TestRun.prototype.clearTestResult = function () {
|
|
|
241
242
|
// make sure the execution is created by now
|
|
242
243
|
await this._testRunStatus.waitForExecutionStartedFinished();
|
|
243
244
|
// we probably can save this backend call by initializing the execution
|
|
244
|
-
return
|
|
245
|
+
return testimServicesApi.clearTestResult(this._options.project, this._testResultId, this._testId, {
|
|
245
246
|
name: this._testName,
|
|
246
247
|
resultId: this._testResultId,
|
|
247
248
|
status: 'pending',
|
|
@@ -251,9 +252,6 @@ TestRun.prototype.clearTestResult = function () {
|
|
|
251
252
|
testRetryKey: this.getRetryKey(),
|
|
252
253
|
});
|
|
253
254
|
});
|
|
254
|
-
if (this._testRunStatus.asyncReporting) {
|
|
255
|
-
return Promise.resolve();
|
|
256
|
-
}
|
|
257
255
|
return this.clearTestResultFinished;
|
|
258
256
|
};
|
|
259
257
|
|
|
@@ -316,6 +314,26 @@ TestRun.prototype.isRetryKeyMismatch = function (testResult) {
|
|
|
316
314
|
return testResult.testRetryKey && (testResult.testRetryKey !== this.getRetryKey());
|
|
317
315
|
};
|
|
318
316
|
|
|
317
|
+
TestRun.prototype.validateRunConfig = function () {
|
|
318
|
+
const baseUrl = this.getBaseUrl();
|
|
319
|
+
const { browserValue } = this.getRunConfig();
|
|
320
|
+
|
|
321
|
+
if (baseUrl && browserValue === 'safari') {
|
|
322
|
+
let parsedUrl;
|
|
323
|
+
try {
|
|
324
|
+
parsedUrl = new URL(baseUrl);
|
|
325
|
+
} catch (err) {
|
|
326
|
+
// ignore invalid URLs (missing http:// or https:// prefix)
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const { username, password } = parsedUrl;
|
|
330
|
+
|
|
331
|
+
if (username || password) {
|
|
332
|
+
throw new Error('Basic authentication in URL is not supported in Safari');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
319
337
|
TestRun.prototype.onStarted = function (startTimeout) {
|
|
320
338
|
return new Promise(resolve => {
|
|
321
339
|
// We can't leave the test result as it may remove other listeners as well
|
|
@@ -467,7 +485,7 @@ TestRun.prototype.onCompleted = function () {
|
|
|
467
485
|
waitForTestEnd();
|
|
468
486
|
}
|
|
469
487
|
} catch (err) {
|
|
470
|
-
logger.error('failed to check is complete', {err});
|
|
488
|
+
logger.error('failed to check is complete', { err });
|
|
471
489
|
waitForTestEnd();
|
|
472
490
|
}
|
|
473
491
|
}, 3000);
|
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 '';
|
|
@@ -167,21 +163,15 @@ RunStatus.prototype.updateTestStatusRunning = function (test, executionId, testR
|
|
|
167
163
|
delete testConfig.testData;
|
|
168
164
|
testConfig.testDataUrl = testDataUrl;
|
|
169
165
|
await this.executionStartedPromise;
|
|
170
|
-
return servicesApi.updateTestStatus(
|
|
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 this.executionStartedPromise.then(() => servicesApi.updateTestStatus('RUNNING', executionId, test.testId, test.resultId, test.startTime, null, null, null, test.config, projectId, 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();
|
|
@@ -201,9 +191,6 @@ RunStatus.prototype.onGridSlot = function (executionId, resultId, gridInfo) {
|
|
|
201
191
|
const test = this.getTestResult(resultId);
|
|
202
192
|
test.config.gridInfo = Object.assign({}, gridInfo, { key: undefined, user: undefined });
|
|
203
193
|
logger.info('on get grid info', { gridInfo: test.config.gridInfo });
|
|
204
|
-
// we don't wait for this call since it's only for analytics/debugging sake.
|
|
205
|
-
const p = this.updateTestStatusRunning(test, executionId);
|
|
206
|
-
p.catch(() => {}); // suppress unhandled rejection
|
|
207
194
|
};
|
|
208
195
|
|
|
209
196
|
RunStatus.prototype.reportTestStatus = function (workerId, result, test, isRerun) {
|
|
@@ -277,11 +264,11 @@ RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isR
|
|
|
277
264
|
return test;
|
|
278
265
|
};
|
|
279
266
|
|
|
280
|
-
RunStatus.prototype.testEndReport = async function (test, executionId, result) {
|
|
267
|
+
RunStatus.prototype.testEndReport = async function (test, executionId, result, testResultUpdates) {
|
|
281
268
|
const globalParameters = result.exportsGlobal;
|
|
282
269
|
try {
|
|
283
270
|
try {
|
|
284
|
-
await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }));
|
|
271
|
+
await runHook(this.options.afterTest, Object.assign({}, test, { globalParameters }), this.options.userData.loginData.token);
|
|
285
272
|
} catch (err) {
|
|
286
273
|
logger.error('HOOK threw an error', { test: test.testId, err });
|
|
287
274
|
// eslint-disable-next-line no-console
|
|
@@ -291,16 +278,23 @@ RunStatus.prototype.testEndReport = async function (test, executionId, result) {
|
|
|
291
278
|
return undefined;
|
|
292
279
|
}
|
|
293
280
|
|
|
294
|
-
return await servicesApi.updateTestStatus(
|
|
281
|
+
return await servicesApi.updateTestStatus(this.options.project, executionId, test.testId, test.resultId, 'FINISHED', {
|
|
282
|
+
startTime: test.startTime,
|
|
283
|
+
endTime: result.endTime,
|
|
284
|
+
success: test.success,
|
|
285
|
+
failureReason: test.failureReason,
|
|
286
|
+
remoteRunId: this.options.remoteRunId,
|
|
287
|
+
...testResultUpdates,
|
|
288
|
+
}, 5);
|
|
295
289
|
} catch (err) {
|
|
296
290
|
logger.error('Failed to update test finished', { err });
|
|
297
291
|
throw err;
|
|
298
292
|
}
|
|
299
293
|
};
|
|
300
294
|
|
|
301
|
-
RunStatus.prototype.testEndAndReport = function (wid, result, executionId, sessionId, isRerun) {
|
|
295
|
+
RunStatus.prototype.testEndAndReport = function (wid, result, executionId, sessionId, isRerun, testResultUpdates) {
|
|
302
296
|
const test = this.testEnd(wid, result, executionId, sessionId, isRerun);
|
|
303
|
-
return this.testEndReport(test, executionId, result);
|
|
297
|
+
return this.testEndReport(test, executionId, result, testResultUpdates);
|
|
304
298
|
};
|
|
305
299
|
|
|
306
300
|
RunStatus.prototype.calcTestRunStatus = function () {
|
|
@@ -402,10 +396,7 @@ RunStatus.prototype.executionStart = function (executionId, projectId, startTime
|
|
|
402
396
|
const ret = servicesApi.reportExecutionStarted(data);
|
|
403
397
|
this.executionStartedPromise = ret;
|
|
404
398
|
ret.catch(e => logger.error(e));
|
|
405
|
-
|
|
406
|
-
return ret;
|
|
407
|
-
}
|
|
408
|
-
return undefined;
|
|
399
|
+
return ret;
|
|
409
400
|
});
|
|
410
401
|
};
|
|
411
402
|
|
package/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ const OSS = [
|
|
|
19
19
|
{ osName: 'Windows 7', bs: { os: 'WINDOWS', os_version: '7' }, sl: { platform: 'Windows 7' } },
|
|
20
20
|
{ osName: 'Windows XP', bs: { os: 'WINDOWS', os_version: 'XP' }, sl: { platform: 'Windows XP' } },
|
|
21
21
|
{ osName: 'macOS Big Sur', bs: { os: 'OS X', os_version: 'Big Sur', safari_version: '14' }, sl: { platform: 'macOS 11', safari_version: '14' } },
|
|
22
|
-
{ osName: 'macOS Catalina',bs: { os: 'OS X', os_version: 'Catalina', safari_version: '13' }, sl: { platform: 'macOS 10.15', safari_version: '13' } },
|
|
22
|
+
{ osName: 'macOS Catalina', bs: { os: 'OS X', os_version: 'Catalina', safari_version: '13' }, sl: { platform: 'macOS 10.15', safari_version: '13' } },
|
|
23
23
|
{ osName: 'macOS Mojave', bs: { os: 'OS X', os_version: 'Mojave', safari_version: '12' }, sl: { platform: 'macOS 10.14', safari_version: '12' } },
|
|
24
24
|
{ osName: 'macOS High Sierra', bs: { os: 'OS X', os_version: 'High Sierra', safari_version: '11' }, sl: { platform: 'macOS 10.13', safari_version: '11' } },
|
|
25
25
|
{ osName: 'macOS Sierra', bs: { os: 'OS X', os_version: 'Sierra', safari_version: '10' }, sl: { platform: 'macOS 10.12', safari_version: '10.0' } },
|
|
@@ -35,11 +35,11 @@ const OSS = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
const BROWSERS = [
|
|
38
|
-
{ browserName: 'Chrome', bs: { browser: 'Chrome', browser_version: '
|
|
38
|
+
{ browserName: 'Chrome', bs: { browser: 'Chrome', browser_version: '94' }, sl: { browserName: 'chrome', version: '94.0' }, browserValue: 'chrome' },
|
|
39
39
|
{ browserName: 'Firefox', bs: { browser: 'Firefox', browser_version: '89' }, sl: { browserName: 'firefox', version: '89.0' }, browserValue: 'firefox' },
|
|
40
40
|
{ browserName: 'Safari', bs: { browser: 'Safari' }, sl: { browserName: 'safari' }, browserValue: 'safari' },
|
|
41
|
-
{ browserName: 'Edge', bs: { browser: 'Edge', browser_version: '
|
|
42
|
-
{ browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '
|
|
41
|
+
{ browserName: 'Edge', bs: { browser: 'Edge', browser_version: '18' }, sl: { browserName: 'MicrosoftEdge', version: '18.17763' }, browserValue: 'edge' },
|
|
42
|
+
{ browserName: 'Edge Chromium', bs: { browser: 'Edge', browser_version: '94' }, sl: { browserName: 'MicrosoftEdge', version: '94' }, synonyms: ['edge-chromium'], browserValue: 'edge-chromium', seleniumName: 'MicrosoftEdge' },
|
|
43
43
|
{ browserName: 'Internet Explorer 11', bs: { browser: 'IE', browser_version: '11' }, sl: { browserName: 'internet explorer', version: '11.0' }, synonyms: ['ie11'], browserValue: 'ie11' },
|
|
44
44
|
{ browserName: 'Browser', bs: {}, sl: { browserName: 'Browser' }, browserValue: 'browser' },
|
|
45
45
|
{ browserName: 'Android', bs: { browserName: 'android' }, sl: {}, browserValue: 'android' },
|
|
@@ -143,7 +143,7 @@ function getEnvironmentGitBranch() {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function getUniqBrowsers(options, testList) {
|
|
146
|
-
if ((options.testConfigNames.length || options.testConfigIds.length || options.testPlan.length || options.testPlanIds.length
|
|
146
|
+
if ((options.testConfigNames.length || options.testConfigIds.length || options.testPlan.length || options.testPlanIds.length) && !options.browser) {
|
|
147
147
|
return _.uniq(testList.map(t => t.runConfig.browserValue));
|
|
148
148
|
}
|
|
149
149
|
return [options.browser.toLowerCase()];
|
package/workers/BaseWorker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const Bluebird = require('bluebird');
|
|
4
4
|
const moment = require('moment');
|
|
5
5
|
const ms = require('ms');
|
|
6
6
|
|
|
@@ -38,12 +38,26 @@ function buildFailureResult(testId, testName, resultId, reason) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
class BaseWorker {
|
|
41
|
-
constructor(executionQueue, customExtensionLocalLocation) {
|
|
41
|
+
constructor(executionQueue, options, customExtensionLocalLocation, executionId, onTestStarted, onTestCompleted, onGridSlot, onTestIgnored, releaseSlotOnTestFinished = true) {
|
|
42
42
|
this.lambdatestService = new LambdatestService();
|
|
43
|
+
|
|
43
44
|
this.id = BaseWorker.getWorkerId();
|
|
44
45
|
this.executionQueue = executionQueue;
|
|
45
46
|
this.customExtensionLocalLocation = customExtensionLocalLocation;
|
|
46
|
-
|
|
47
|
+
|
|
48
|
+
this.isCodeMode = options.files && options.files.length > 0;
|
|
49
|
+
this.baseUrl = options.baseUrl;
|
|
50
|
+
this.isRegressionBaselineRun = options.isRegressionBaselineRun;
|
|
51
|
+
this.testRunTimeout = options.timeout;
|
|
52
|
+
this.onTestStarted = onTestStarted;
|
|
53
|
+
this.onTestCompleted = onTestCompleted;
|
|
54
|
+
this.onGridSlot = onGridSlot;
|
|
55
|
+
this.onTestIgnored = onTestIgnored;
|
|
56
|
+
this.releaseSlotOnTestFinished = releaseSlotOnTestFinished;
|
|
57
|
+
|
|
58
|
+
this.userData = options.userData;
|
|
59
|
+
this.executionId = executionId;
|
|
60
|
+
this.options = options;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
static getWorkerId() {
|
|
@@ -69,7 +83,7 @@ class BaseWorker {
|
|
|
69
83
|
throw new NotImplementedError(true);
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
runTestOnce(testRunHandler, player) {
|
|
86
|
+
async runTestOnce(testRunHandler, player) {
|
|
73
87
|
testRunHandler.setSessionId(player.getSessionId());
|
|
74
88
|
logger.info('Test run started', {
|
|
75
89
|
testId: testRunHandler.getTestId(),
|
|
@@ -77,26 +91,9 @@ class BaseWorker {
|
|
|
77
91
|
seleniumSession: player.getSessionId(),
|
|
78
92
|
});
|
|
79
93
|
if (this.options.lightweightMode && this.options.lightweightMode.disableResults) {
|
|
80
|
-
return
|
|
94
|
+
return undefined;
|
|
81
95
|
}
|
|
82
|
-
return testRunHandler.clearTestResult();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
start(options, onStart, onResult, executionId, onGridSlot, onTestIgnored, releaseSlotOnTestFinished) {
|
|
86
|
-
this.isCodeMode = options.files.length > 0;
|
|
87
|
-
this.baseUrl = options.baseUrl;
|
|
88
|
-
this.isRegressionBaselineRun = options.isRegressionBaselineRun;
|
|
89
|
-
this.testRunTimeout = options.timeout;
|
|
90
|
-
this.onStart = onStart;
|
|
91
|
-
this.onResult = onResult;
|
|
92
|
-
this.onGridSlot = onGridSlot;
|
|
93
|
-
this.onTestIgnored = onTestIgnored;
|
|
94
|
-
this.releaseSlotOnTestFinished = releaseSlotOnTestFinished;
|
|
95
|
-
|
|
96
|
-
this.userData = options.userData;
|
|
97
|
-
this.executionId = executionId;
|
|
98
|
-
this.options = options;
|
|
99
|
-
return this.run();
|
|
96
|
+
return await testRunHandler.clearTestResult();
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
handleQuarantine(testRunHandler) {
|
|
@@ -124,13 +121,12 @@ class BaseWorker {
|
|
|
124
121
|
let gridInfo = await utils.runWithRetries(async () => {
|
|
125
122
|
const startTime = Date.now();
|
|
126
123
|
try {
|
|
127
|
-
return await
|
|
128
|
-
.then(() => this.getSlotOnce(testRunHandler))
|
|
124
|
+
return await Bluebird.resolve(this.getSlotOnce(testRunHandler))
|
|
129
125
|
.timeout(this.options.getBrowserTimeout, timeoutMessages.GET_BROWSER_TIMEOUT_MSG);
|
|
130
126
|
} catch (error) {
|
|
131
127
|
logger.error('error getting grid slot', { error, testId: this.testId, testResultId: this.testResultId, executionId: this.executionId });
|
|
132
128
|
failedGetSlotAttempts++;
|
|
133
|
-
await
|
|
129
|
+
await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
|
|
134
130
|
throw error;
|
|
135
131
|
}
|
|
136
132
|
}, this.options.getBrowserRetries);
|
|
@@ -154,7 +150,7 @@ class BaseWorker {
|
|
|
154
150
|
this.options.gridData.host = gridInfo.host;
|
|
155
151
|
this.options.gridData.failedGetBrowserAttempts = failedGetBrowserAttempts;
|
|
156
152
|
const getSessionTimeout = Math.max(this.lambdatestService.getSessionTimeout, this.options.getSessionTimeout);
|
|
157
|
-
const getBrowserRes = await
|
|
153
|
+
const getBrowserRes = await Bluebird.resolve()
|
|
158
154
|
.log('before getBrowserOnce')
|
|
159
155
|
.then(() => this.getBrowserOnce(testRunHandler, customExtensionLocalLocation, player, gridInfo))
|
|
160
156
|
.log('after getBrowserOnce')
|
|
@@ -169,7 +165,7 @@ class BaseWorker {
|
|
|
169
165
|
player.onDone();
|
|
170
166
|
|
|
171
167
|
if (!(error instanceof PageNotAvailableError)) {
|
|
172
|
-
await
|
|
168
|
+
await Bluebird.delay(this.options.getBrowserTimeout - (Date.now() - startTime));
|
|
173
169
|
}
|
|
174
170
|
throw error;
|
|
175
171
|
}
|
|
@@ -190,14 +186,15 @@ class BaseWorker {
|
|
|
190
186
|
}
|
|
191
187
|
|
|
192
188
|
async runTest(testRunHandler, customExtensionLocalLocation, shouldRerun) {
|
|
189
|
+
perf.log('inside runTest');
|
|
193
190
|
const projectId = this.userData && this.userData.projectId;
|
|
194
191
|
const quarantineResult = this.handleQuarantine(testRunHandler);
|
|
195
192
|
if (quarantineResult) {
|
|
196
193
|
return quarantineResult;
|
|
197
194
|
}
|
|
198
195
|
|
|
199
|
-
perf.log('before runTest
|
|
200
|
-
await this.
|
|
196
|
+
perf.log('before runTest onTestStarted');
|
|
197
|
+
await this.onTestStarted(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
|
|
201
198
|
const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
|
|
202
199
|
|
|
203
200
|
try {
|
|
@@ -207,12 +204,12 @@ class BaseWorker {
|
|
|
207
204
|
}
|
|
208
205
|
}
|
|
209
206
|
|
|
210
|
-
runTestCleanup() {
|
|
211
|
-
return
|
|
207
|
+
async runTestCleanup() {
|
|
208
|
+
return undefined;
|
|
212
209
|
}
|
|
213
210
|
|
|
214
211
|
onQueueCompleted() {
|
|
215
|
-
return
|
|
212
|
+
return undefined;
|
|
216
213
|
}
|
|
217
214
|
|
|
218
215
|
run() {
|
|
@@ -238,9 +235,9 @@ class BaseWorker {
|
|
|
238
235
|
try {
|
|
239
236
|
const testRetryKey = testRunHandler.getRetryKey();
|
|
240
237
|
testResult.testRetryKey = testRetryKey;
|
|
241
|
-
await this.
|
|
242
|
-
if (!(this.options.lightweightMode && this.options.lightweightMode.general)) {
|
|
243
|
-
await
|
|
238
|
+
await this.onTestCompleted(this.id, this.testId, testResult, sessionId, shouldRerun);
|
|
239
|
+
if (this.executionQueue.hasMoreTests() && !(this.options.lightweightMode && this.options.lightweightMode.general)) {
|
|
240
|
+
await Bluebird.delay(DELAY_BETWEEN_TESTS);
|
|
244
241
|
}
|
|
245
242
|
await this.runTestCleanup();
|
|
246
243
|
if (shouldRerun) {
|
|
@@ -340,7 +337,7 @@ class BaseWorker {
|
|
|
340
337
|
|
|
341
338
|
const projectId = this.userData && this.userData.projectId;
|
|
342
339
|
const { errorType, reason } = buildError(err);
|
|
343
|
-
|
|
340
|
+
testimServicesApi.updateTestResult(projectId, this.testResultId, this.testId, {
|
|
344
341
|
status: testRunStatus.COMPLETED,
|
|
345
342
|
success: false,
|
|
346
343
|
reason,
|
|
@@ -390,10 +387,13 @@ class BaseWorker {
|
|
|
390
387
|
!disableRemoteStep && remoteStepService.joinToRemoteStep(this.testResultId),
|
|
391
388
|
!disableResults && testResultService.joinToTestResult(this.testResultId, this.testId),
|
|
392
389
|
])
|
|
393
|
-
.
|
|
390
|
+
.then(() => testRunHandler.validateRunConfig())
|
|
394
391
|
.then(() => this.runTest(testRunHandler, this.customExtensionLocalLocation, shouldRerun))
|
|
395
392
|
.then(testResult => onRunComplete(testResult, testRunHandler))
|
|
396
|
-
.
|
|
393
|
+
.then(result => {
|
|
394
|
+
perf.log('After onRunComplete');
|
|
395
|
+
return result;
|
|
396
|
+
})
|
|
397
397
|
.catch(runError => recoverTestResults(runError, testRunHandler))
|
|
398
398
|
.finally(() => {
|
|
399
399
|
if (!disableRemoteStep) {
|
|
@@ -15,7 +15,7 @@ describe('BaseWorker', () => {
|
|
|
15
15
|
let testPlayerMock;
|
|
16
16
|
|
|
17
17
|
beforeEach(() => {
|
|
18
|
-
worker = new BaseWorker();
|
|
18
|
+
worker = new BaseWorker(null, {});
|
|
19
19
|
worker.userData = {};
|
|
20
20
|
worker.options = { gridData: {}, browser: 'chrome', company: { companyId: 'companyId' }, getBrowserTimeout: 1000, getSessionTimeout: 100, getBrowserRetries: 10 };
|
|
21
21
|
worker.testRunConfig = {};
|
|
@@ -44,9 +44,8 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
44
44
|
return quarantineResult;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
perf.log('before runTest
|
|
48
|
-
|
|
49
|
-
await this.onStart(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
|
|
47
|
+
perf.log('before runTest onTestStarted single browser');
|
|
48
|
+
await this.onTestStarted(this.id, testRunHandler.getTestId(), testRunHandler.getTestResultId(), shouldRerun, testRunHandler.getRetryKey());
|
|
50
49
|
const testPlayer = await this.getTestPlayer(testRunHandler, customExtensionLocalLocation);
|
|
51
50
|
|
|
52
51
|
testRunHandler.markClearBrowser();
|
|
@@ -54,6 +53,10 @@ class WorkerExtensionSingleBrowser extends WorkerExtension {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
async runTestCleanup() {
|
|
56
|
+
if (!this.executionQueue.hasMoreTests()) {
|
|
57
|
+
await this.onQueueCompleted();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
57
60
|
if (this.options.lightweightMode && this.options.lightweightMode.general) {
|
|
58
61
|
await Bluebird.delay(DELAY_BETWEEN_TESTS);
|
|
59
62
|
}
|