@testim/testim-cli 3.235.0 → 3.238.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.
@@ -1,11 +1,38 @@
1
1
  const { expect, sinon } = require('../../test/utils/testUtils');
2
2
  const servicesApi = require('../commons/testimServicesApi');
3
+ const httpRequest = require('../commons/httpRequest');
3
4
  const LambdatestService = require('./lambdatestService');
5
+ const utils = require('../utils');
6
+ const fse = require('fs-extra');
7
+ const os = require('os');
8
+ const childProcess = require('child_process');
9
+ const EventEmitter = require('events');
10
+ const portfinder = require('portfinder');
11
+ const { getExtensionsUrl } = require('../runOptionsUtils');
12
+
13
+ class Process extends EventEmitter {
14
+ constructor() {
15
+ super();
16
+ this.stdout = new EventEmitter();
17
+ this.stderr = new EventEmitter();
18
+ }
19
+ setCode(code) {
20
+ this.code = code;
21
+ }
22
+ kill() {
23
+ this.emit('close', this.code);
24
+ }
25
+ }
4
26
 
5
27
  describe('LambdatestService', () => {
28
+ let sandbox;
6
29
  let lambdatestService;
7
30
  beforeEach(() => {
8
31
  lambdatestService = new LambdatestService();
32
+ sandbox = sinon.createSandbox();
33
+ });
34
+ afterEach(() => {
35
+ sandbox.restore();
9
36
  });
10
37
 
11
38
  describe('isLambdatestGrid', () => {
@@ -86,8 +113,240 @@ describe('LambdatestService', () => {
86
113
  lambdatestService.isActive = false;
87
114
  expect(lambdatestService.getSessionRetries).to.be.equal(null);
88
115
  });
116
+
117
+ it('should not return lt special selenium capabilities when inactove', () => {
118
+ lambdatestService.isActive = false;
119
+ expect(lambdatestService.getCapabilities({})).to.eql({});
120
+ });
121
+
122
+ it('should return lt selenium capabilities', () => {
123
+ const browser = utils.guid();
124
+ const executionId = utils.guid();
125
+ const testResultId = utils.guid();
126
+ const testName = utils.guid();
127
+
128
+ lambdatestService.isActive = true;
129
+ LambdatestService.lambdatestConfig = {
130
+ CAPABILITIES: { [browser]: { specificBrowserCaps: 123 } },
131
+ };
132
+
133
+ expect(lambdatestService.getCapabilities({}, browser, executionId, testResultId, testName)).to.shallowDeepEqual({
134
+ build: executionId,
135
+ name: `${testResultId} - ${testName}`,
136
+ platform: LambdatestService.lambdatestConfig.PLATFORM,
137
+ // eslint-disable-next-line camelcase
138
+ selenium_version: LambdatestService.lambdatestConfig.SELENIUM_VERSION,
139
+ resolution: LambdatestService.lambdatestConfig.RESOLUTION,
140
+ timezone: LambdatestService.lambdatestConfig.TIMEZONE,
141
+ specificBrowserCaps: 123,
142
+ console: true,
143
+ queueTimeout: 300,
144
+ });
145
+ });
146
+
147
+ it('should return lt tunnel name as part of selenium capabilities', () => {
148
+ lambdatestService.isActive = true;
149
+ LambdatestService.lambdatestConfig = { CAPABILITIES: {} };
150
+ LambdatestService.tunnelName = utils.guid();
151
+
152
+ expect(lambdatestService.getCapabilities({})).to.shallowDeepEqual({
153
+ tunnel: true,
154
+ tunnelName: LambdatestService.tunnelName,
155
+ });
156
+ });
157
+
158
+ it('should load testim extension when it is not set', () => {
159
+ lambdatestService.isActive = true;
160
+ LambdatestService.lambdatestConfig = { CAPABILITIES: {} };
161
+ LambdatestService.tunnelName = utils.guid();
162
+
163
+ expect(lambdatestService.getCapabilities({ mode: 'extension' }, 'chrome')).to.shallowDeepEqual({ loadExtension: [getExtensionsUrl({}, true).chrome] });
164
+ expect(lambdatestService.getCapabilities({ mode: 'extension' }, 'somOtherBrowser')).to.shallowDeepEqual({ loadExtension: [] });
165
+ });
166
+
167
+ it('should load testim extension when passing extensionPath flag', () => {
168
+ lambdatestService.isActive = true;
169
+ LambdatestService.lambdatestConfig = { CAPABILITIES: {} };
170
+ LambdatestService.tunnelName = utils.guid();
171
+
172
+ const extensionPath = 'http://localhost:1234/extension.zip';
173
+ expect(lambdatestService.getCapabilities({ mode: 'extension', extensionPath })).to.shallowDeepEqual({ loadExtension: [extensionPath] });
174
+ });
175
+
176
+ it('should load testim extension when passing installCustomExtension flag', () => {
177
+ lambdatestService.isActive = true;
178
+ LambdatestService.lambdatestConfig = { CAPABILITIES: {} };
179
+ LambdatestService.tunnelName = utils.guid();
180
+
181
+ const installCustomExtension = 'http://localhost:1234/extension.zip';
182
+ expect(lambdatestService.getCapabilities({ mode: 'extension', installCustomExtension })).to.shallowDeepEqual({ loadExtension: [installCustomExtension] });
183
+ });
184
+ });
185
+
186
+ describe('prepareTunnel', () => {
187
+ it('should do nothing when tunnel binary already exists', async () => {
188
+ sandbox.stub(fse, 'pathExists').resolves(true);
189
+ const chmod = sandbox.stub(fse, 'chmodSync');
190
+ await LambdatestService.prepareTunnel();
191
+ expect(chmod).not.to.have.been.called;
192
+ });
193
+
194
+ it('should throw when unsupported os', async () => {
195
+ sandbox.stub(fse, 'pathExists').resolves(false);
196
+ sandbox.stub(os, 'platform').returns('wtf');
197
+ sandbox.stub(os, 'arch').returns('wtf');
198
+
199
+ await expect(LambdatestService.prepareTunnel()).to.be.rejectedWith(Error, 'tunnel on wtfwtf platform is not supported.');
200
+ });
201
+
202
+ it('should download and extract tunnel binary', async () => {
203
+ sandbox.stub(fse, 'pathExists').resolves(false);
204
+ sandbox.stub(os, 'platform').returns('win32');
205
+ sandbox.stub(os, 'arch').returns('x64');
206
+
207
+ const download = sandbox.stub(utils, 'downloadAndSave');
208
+ const unzip = sandbox.stub(utils, 'unzipFile').resolves();
209
+
210
+ await LambdatestService.prepareTunnel();
211
+ sinon.assert.calledOnce(unzip);
212
+ sinon.assert.calledOnce(download);
213
+ expect(download.args[0][0]).to.startWith('https://downloads.lambdatest.com/tunnel/');
214
+ });
89
215
  });
90
216
 
91
217
  describe('connectTunnel', () => {
218
+ let prepareTunnelStub;
219
+ let spawnStub;
220
+ let credentials;
221
+ let httpGetStub;
222
+ let processMock;
223
+
224
+ beforeEach(() => {
225
+ processMock = new Process();
226
+ credentials = { gridUsername: utils.guid(), gridPassword: utils.guid() };
227
+ sandbox.stub(portfinder, 'getPortPromise').resolves(1234);
228
+ prepareTunnelStub = sandbox.stub(LambdatestService, 'prepareTunnel');
229
+ spawnStub = sandbox.stub(childProcess, 'spawn').returns(processMock);
230
+ httpGetStub = sandbox.stub(httpRequest, 'get').resolves({});
231
+ });
232
+
233
+ it('should do nothing when using externalLambdatestTunnelId', async () => {
234
+ await LambdatestService.connectTunnel({ externalLambdatestTunnelId: 123 });
235
+ sinon.assert.neverCalledWith(prepareTunnelStub);
236
+ sinon.assert.neverCalledWith(spawnStub);
237
+ });
238
+
239
+ it('should prepare the tunnel', async () => {
240
+ await LambdatestService.connectTunnel({ ...credentials });
241
+ sinon.assert.calledOnce(prepareTunnelStub);
242
+ });
243
+
244
+ it('should reject when no credentials', async () => {
245
+ await expect(LambdatestService.connectTunnel({ })).to.be.rejectedWith(Error, 'tunnel requires username and password');
246
+ });
247
+
248
+ it('should spawn the tunnel process', async () => {
249
+ await LambdatestService.connectTunnel({ ...credentials });
250
+ sinon.assert.calledOnce(spawnStub);
251
+ expect(spawnStub.args[0][1]).to.eql([
252
+ '--tunnelName', LambdatestService.tunnelName, '--infoAPIPort', 1234,
253
+ '--user', credentials.gridUsername, '--key', credentials.gridPassword,
254
+ ]);
255
+ });
256
+
257
+ it('should accept tunnelUser and tunnelKey on gridData', async () => {
258
+ credentials = { gridData: { tunnelUser: utils.guid(), tunnelKey: utils.guid() } };
259
+ await LambdatestService.connectTunnel({ ...credentials });
260
+ sinon.assert.calledOnce(spawnStub);
261
+ expect(spawnStub.args[0][1]).to.eql([
262
+ '--tunnelName', LambdatestService.tunnelName, '--infoAPIPort', 1234,
263
+ '--user', credentials.gridData.tunnelUser, '--key', credentials.gridData.tunnelKey,
264
+ ]);
265
+ });
266
+
267
+ it('should allow using externalLambdatestUseWss', async () => {
268
+ await LambdatestService.connectTunnel({ ...credentials, externalLambdatestUseWss: true });
269
+ sinon.assert.calledOnce(spawnStub);
270
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--mode ws');
271
+ });
272
+
273
+ it('should allow using proxyUri', async () => {
274
+ global.proxyUri = 'http://localhost';
275
+ await LambdatestService.connectTunnel({ ...credentials });
276
+ sinon.assert.calledOnce(spawnStub);
277
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--proxy-host localhost');
278
+ global.proxyUri = undefined;
279
+ });
280
+
281
+ it('should allow using proxyUri port and credentials', async () => {
282
+ global.proxyUri = 'http://user:pass@localhost:1234';
283
+ await LambdatestService.connectTunnel({ ...credentials });
284
+ sinon.assert.calledOnce(spawnStub);
285
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--proxy-host localhost');
286
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--proxy-port 1234');
287
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--proxy-user user');
288
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--proxy-pass pass');
289
+ global.proxyUri = undefined;
290
+ });
291
+
292
+ it('should throw when proxyUri is invalid', async () => {
293
+ global.proxyUri = 'i am invalid';
294
+ await expect(LambdatestService.connectTunnel({ ...credentials })).to.be.rejectedWith(Error, 'proxy url is invalid');
295
+ global.proxyUri = undefined;
296
+ });
297
+
298
+ it('should allow using externalLambdatestDisableAutomationTunneling', async () => {
299
+ await LambdatestService.connectTunnel({ ...credentials, externalLambdatestDisableAutomationTunneling: true });
300
+ sinon.assert.calledOnce(spawnStub);
301
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--bypassHosts run.testim.io,services.testim.io,api.coralogix.com,conf.rollout.io,statestore.rollout.io,push.rollout.io,analytic.rollout.io,res.cloudinary.com');
302
+ });
303
+
304
+ it('should allow using externalLambdatestMitm', async () => {
305
+ await LambdatestService.connectTunnel({ ...credentials, externalLambdatestMitm: true });
306
+ sinon.assert.calledOnce(spawnStub);
307
+ expect(spawnStub.args[0][1].join(' ')).to.contain('--mitm');
308
+ });
309
+
310
+ it('should verify tunnel started', async () => {
311
+ await LambdatestService.connectTunnel({ ...credentials });
312
+ sinon.assert.calledOnce(httpGetStub);
313
+ });
314
+ it('should throw when tunnel did not start', async () => {
315
+ sandbox.stub(utils, 'runWithRetries').rejects(new Error('tunnel did not start'));
316
+ await expect(LambdatestService.connectTunnel({ ...credentials })).to.be.rejectedWith(Error, 'tunnel did not start');
317
+ processMock.stdout.emit('data', '');
318
+ processMock.stderr.emit('data', '');
319
+ });
320
+ });
321
+
322
+ describe('disconnectTunnel', () => {
323
+ let processMock;
324
+ let killStub;
325
+
326
+ beforeEach(() => {
327
+ processMock = new Process();
328
+ killStub = sandbox.stub(processMock, 'kill').callThrough();
329
+ sandbox.stub(childProcess, 'spawn').returns(processMock);
330
+ sandbox.stub(LambdatestService, 'prepareTunnel');
331
+ sandbox.stub(httpRequest, 'get').resolves({});
332
+ });
333
+
334
+ it('should kill the tunnel', async () => {
335
+ await LambdatestService.connectTunnel({ tunnel: true, company: {}, gridUsername: utils.guid(), gridPassword: utils.guid() });
336
+ await LambdatestService.disconnectTunnel({ company: {} });
337
+ sinon.assert.calledOnce(killStub);
338
+ });
339
+
340
+ it('should reject when killing the tunnel fails', async () => {
341
+ processMock.setCode(1);
342
+ await LambdatestService.connectTunnel({ tunnel: true, company: {}, gridUsername: utils.guid(), gridPassword: utils.guid() });
343
+ await expect(LambdatestService.disconnectTunnel({ company: {} })).to.be.rejectedWith(Error);
344
+ sinon.assert.calledOnce(killStub);
345
+ });
346
+
347
+ it('should do nothing when using externalLambdatestTunnelId', async () => {
348
+ await LambdatestService.disconnectTunnel({ externalLambdatestTunnelId: 123 });
349
+ sinon.assert.neverCalledWith(killStub);
350
+ });
92
351
  });
93
352
  });
package/testRunStatus.js CHANGED
@@ -174,7 +174,21 @@ RunStatus.prototype.testStartReport = function (test, executionId, testRetryKey)
174
174
  }
175
175
  return runHook(this.options.beforeTest, Object.assign({}, test, { exportsGlobal: this.exportsGlobal }), this.options.userData.loginData.token)
176
176
  .then(async params => {
177
- this.options.runParams[test.resultId] = test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
177
+ // Temporary Sapiens log (SUP-3192)
178
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
179
+ logger.info('testRunStatus - testStartReport', {
180
+ 'test.config.testData': test.config.testData,
181
+ 'this.exportsGlobal': this.exportsGlobal,
182
+ 'this.fileUserParamsData': this.fileUserParamsData,
183
+ 'this.beforeSuiteParams': this.beforeSuiteParams,
184
+ params,
185
+ executionId,
186
+ 'test.testId': test.testId,
187
+ 'test.resultId': test.resultId,
188
+ });
189
+ }
190
+ test.config.testData = Object.assign({}, test.config.testData, this.exportsGlobal, this.fileUserParamsData, this.beforeSuiteParams, params);
191
+ this.options.runParams[test.resultId] = test.config.testData;
178
192
  test.startTime = Date.now();
179
193
  await this.updateTestStatusRunning(test, executionId, testRetryKey);
180
194
 
@@ -234,8 +248,10 @@ RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isR
234
248
  const duration = (result.endTime - result.startTime) || 0;
235
249
  test.sessionId = sessionId;
236
250
  test.startTime = result.startTime || test.startTime || Date.now();
237
- test.duration = result.duration = duration;
238
- result.failureReason = test.failureReason = result.failureReason || result.reason;
251
+ test.duration = duration;
252
+ result.duration = duration;
253
+ test.failureReason = result.failureReason || result.reason;
254
+ result.failureReason = test.failureReason;
239
255
  test.failurePath = result.failurePath;
240
256
  test.resultId = result.resultId;
241
257
  test.success = result.success;
@@ -245,8 +261,9 @@ RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isR
245
261
  }
246
262
 
247
263
  test.resultUrl = utils.getTestUrl(this.options.editorUrl, this.options.project, test.testId, test.resultId, this.branchToUse);
264
+ test.status = this.calcResultText(result);
248
265
 
249
- test.status = result.status = this.calcResultText(result);
266
+ result.status = test.status;
250
267
  result.name = test.name;
251
268
  result.testStatus = test.testStatus;
252
269
  result.testId = result.testId || test.testId;
@@ -263,7 +280,19 @@ RunStatus.prototype.testEnd = function (wid, result, executionId, sessionId, isR
263
280
  const isCodeMode = this.options.files.length > 0;
264
281
  reporter.onTestFinished(test, wid, isRerun, isCodeMode);
265
282
 
266
- this.exportsGlobal = Object.assign({}, this.exportsGlobal, result.exportsGlobal);
283
+ const afterMerge = Object.assign({}, this.exportsGlobal, result.exportsGlobal);
284
+ // Temporary Sapiens log (SUP-3192)
285
+ if (this.options.projectData && this.options.projectData.projectId === 'fZ63D61PRQQVvvtGY6Ue' && this.options.suites && this.options.suites.includes('Sanity')) {
286
+ logger.info('testRunStatus - testEnd', {
287
+ 'this.exportsGlobal': this.exportsGlobal,
288
+ 'result.exportsGlobal': result.exportsGlobal,
289
+ afterMerge,
290
+ executionId,
291
+ 'test.testId': test.testId,
292
+ 'test.resultId': test.resultId,
293
+ });
294
+ }
295
+ this.exportsGlobal = afterMerge;
267
296
  return test;
268
297
  };
269
298