@testim/testim-cli 3.251.0 → 3.253.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.
Files changed (34) hide show
  1. package/agent/routers/index.js +2 -1
  2. package/agent/routers/playground/router.js +1 -1
  3. package/commons/chrome-launcher.js +6 -6
  4. package/commons/constants.js +2 -0
  5. package/commons/getSessionPlayerRequire.js +2 -20
  6. package/commons/initializeUserWithAuth.js +2 -2
  7. package/commons/mockNetworkRuleFileSchema.json +40 -38
  8. package/commons/testimDesiredCapabilitiesBuilder.js +43 -0
  9. package/commons/testimServicesApi.js +11 -2
  10. package/credentialsManager.js +17 -20
  11. package/npm-shrinkwrap.json +2104 -444
  12. package/package.json +4 -2
  13. package/player/WebdriverioWebDriverApi.js +7 -2
  14. package/player/appiumTestPlayer.js +102 -0
  15. package/player/seleniumTestPlayer.js +3 -2
  16. package/player/services/frameLocator.js +2 -1
  17. package/player/services/mobileFrameLocatorMock.js +32 -0
  18. package/player/services/playbackTimeoutCalculator.js +1 -0
  19. package/player/services/portSelector.js +10 -8
  20. package/player/services/tabService.js +29 -0
  21. package/player/stepActions/sfdcRecordedStepAction.js +2 -2
  22. package/player/stepActions/sfdcStepAction.js +2 -2
  23. package/player/stepActions/stepAction.js +15 -1
  24. package/player/utils/stepActionUtils.js +4 -2
  25. package/player/utils/windowUtils.js +138 -125
  26. package/player/webdriver.js +39 -25
  27. package/reports/debugReporter.js +41 -39
  28. package/reports/jsonReporter.js +53 -50
  29. package/reports/reporter.js +135 -136
  30. package/runOptions.js +2 -1
  31. package/runners/ParallelWorkerManager.js +2 -0
  32. package/testRunStatus.js +457 -459
  33. package/workers/BaseWorker.js +13 -6
  34. package/workers/WorkerAppium.js +123 -0
@@ -20,7 +20,8 @@ module.exports = function(beforeMiddleware, standaloneBrowserInfo) {
20
20
  * set cors options
21
21
  */
22
22
 
23
- const whitelist = ['http://localhost:3000', 'https://app.testim.io', 'https://staging.testim.io', 'https://playground.testim.io', 'https://app.staging.testim.cc', 'chrome-extension://pebeiooilphfmbohdbhbomomkkoghoia'];
23
+ const whitelist = ['http://localhost:3000', 'https://app.testim.io', 'https://staging.testim.io', 'https://playground.testim.io',
24
+ 'https://app.staging.testim.cc', 'chrome-extension://pebeiooilphfmbohdbhbomomkkoghoia', 'https://tta-crm.tricentis.com'];
24
25
  const corsOptions = {
25
26
  methods: ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS'],
26
27
  allowedHeaders: ['Content-Type', 'Authorization'],
@@ -7,7 +7,7 @@ const {ClientError, PlaygroundCodeError} = require('../../../errors');
7
7
  const {runPlaygroundTest, stopPlaygroundTest, CODE_TYPES} = require('./service');
8
8
  const {DISABLE_AGENT_ORIGIN_CHECK} = require('../../../commons/config');
9
9
 
10
- const VALID_HOSTS = ['localhost', 'app.testim.io', 'playground.testim.io', 'staging.testim.io', 'app.staging.testim.cc'];
10
+ const VALID_HOSTS = ['localhost', 'app.testim.io', 'playground.testim.io', 'staging.testim.io', 'app.staging.testim.cc', 'tta-crm.tricentis.com'];
11
11
 
12
12
  const parseUrl = (url) => {
13
13
  if(!url) {
@@ -1,15 +1,15 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- const { exec } = require("child_process");
3
+ const { exec } = require('child_process');
4
4
 
5
- module.exports.launchChrome = function(url) {
5
+ module.exports.launchChrome = function (url) {
6
6
  const { platform } = process;
7
7
 
8
- if (platform === "win32") {
8
+ if (platform === 'win32') {
9
9
  exec(`start chrome ${url}`);
10
- } else if (platform === "darwin") {
10
+ } else if (platform === 'darwin') {
11
11
  exec(`open -a "Google Chrome" ${url}`);
12
- } else if (platform === "linux") {
12
+ } else if (platform === 'linux') {
13
13
  exec(`google-chrome ${url}`);
14
14
  }
15
15
  };
@@ -41,6 +41,7 @@ module.exports = {
41
41
  CLI_MODE: {
42
42
  EXTENSION: 'extension',
43
43
  SELENIUM: 'selenium',
44
+ APPIUM: 'appium',
44
45
  },
45
46
  sessionType: {
46
47
  CODELESS: 'codeless',
@@ -54,6 +55,7 @@ module.exports = {
54
55
  HYBRID: 'testimHybrid',
55
56
  BROWSERSTACK: 'browserstack',
56
57
  SAUCELABS: 'saucelabs',
58
+ TESTIM_HEADSPIN: 'testimHeadspin',
57
59
  },
58
60
  stepResult: {
59
61
  SETUP_TIMEOUT: 'setup-timeout',
@@ -3,28 +3,10 @@
3
3
  const perf = require('./performance-logger');
4
4
 
5
5
  perf.log('getSessionPlayerRequire start');
6
- const getSessionPlayerFolder = require('./prepareRunnerAndTestimStartUtils').getSessionPlayerFolder;
6
+ const { getSessionPlayerFolder } = require('./prepareRunnerAndTestimStartUtils');
7
7
 
8
8
  const testimAppDataFolder = getSessionPlayerFolder();
9
- /**
10
- * @type {{
11
- sessionPlayer: typeof import('../../../clickim/src/background/session/sessionPlayer').SessionPlayer;
12
- utils: typeof import('../../../clickim/src/lib/utils').utils;
13
- commonConstants: typeof import('../../../clickim/src/common/commonConstantsStrong');
14
- locatorBuilderUtils: import('../../../clickim/src/locators/locatorBuilderUtils')['locatorBuilderUtils'];
15
- assetService: import('../../../clickim/src/background/assetService')['assetService'];
16
- localAssetService: import('../../../clickim/src/background/localAssetService');
17
- urlUtils: import('../../../clickim/src/background/portMatch/urlUtils');
18
- positionUtils: import('../../../clickim/src/lib/positionUtils');
19
- visibilityUtils: import('../../../clickim/src/background/visibilityUtils');
20
- apiCall: import('../../../clickim/src/common/playback/apiCall')['apiCall'];
21
- stepParamBuilder: typeof import('../../../clickim/src/common/stepParamsBuilder').StepParamsBuilder;
22
- stepParamExpressionEvaluator: import('../../../clickim/src/common/stepParamExpressionEvaluator');
23
- manifestVersion: string | undefined;
24
- EyeSdkBuilder: typeof import('../../../clickim/src/background/eyeSdkBuilder').EyeSdkBuilder;
25
- sfdc: typeof import('sfdc-engine');
26
- }}
27
- */
9
+ /** @type {import('clickim/src/background/sessionPlayerInit').SessionPlayerInit} */
28
10
  const sessionPlayer = require(require('path').join(testimAppDataFolder, 'sessionPlayer.js')); // eslint-disable-line import/no-dynamic-require
29
11
 
30
12
  module.exports = sessionPlayer;
@@ -3,7 +3,7 @@ const perf = require('./performance-logger');
3
3
  const localRunnerCache = require('./runnerFileCache');
4
4
  const servicesApi = require('./testimServicesApi.js');
5
5
  const testimCustomToken = require('./testimCustomToken');
6
-
6
+ const { CLI_MODE } = require('./constants');
7
7
 
8
8
  const FIVE_MINUTES_MS = 1000 * 60 * 5;
9
9
  const TEN_HOURS_MS = 1000 * 60 * 60 * 10;
@@ -11,7 +11,7 @@ const TEN_HOURS_MS = 1000 * 60 * 60 * 10;
11
11
  function preloadSlowRequires(mode) {
12
12
  process.nextTick(() => {
13
13
  // heuristic to pay the cost of loading the sessionPlayer here while we are waiting for the backend
14
- if (mode === 'selenium') {
14
+ if (mode === CLI_MODE.SELENIUM || mode === CLI_MODE.APPIUM) {
15
15
  try {
16
16
  require('./getSessionPlayerRequire');
17
17
  // jsdom for the same reason, we don't require workerSelenium here since it actually takes longer to load
@@ -13,7 +13,6 @@
13
13
  }
14
14
  }
15
15
  },
16
-
17
16
  "header": {
18
17
  "type": "object",
19
18
  "required": [
@@ -88,48 +87,51 @@
88
87
  "text": {
89
88
  "type": "string"
90
89
  }
90
+ }
91
91
  }
92
92
  }
93
- }
94
- },
95
- "redirectResponse": {
96
- "type": "object",
97
- "required": ["redirectUrl"],
98
- "additionalProperties": false,
99
- "properties": {
100
- "redirectUrl": { "type": "string" }
101
- }
102
- },
103
- "passthroughResponse": {
104
- "type": "object",
105
- "required": ["passthrough"],
106
- "additionalProperties": false,
107
- "properties": {
108
- "passthrough": { "type": "boolean", "enum": [ true ] }
109
- }
110
- },
111
-
112
- "entry": {
113
- "type": "object",
114
- "required": ["request", "response"],
115
- "additionalProperties": false,
116
- "properties": {
117
- "request": { "$ref": "#/definitions/request" },
118
- "response": {
119
- "oneOf": [
120
- { "$ref": "#/definitions/response" },
121
- { "$ref": "#/definitions/redirectResponse" },
122
- { "$ref": "#/definitions/passthroughResponse" }
123
- ]
93
+ },
94
+ "redirectResponse": {
95
+ "type": "object",
96
+ "required": ["redirectUrl"],
97
+ "additionalProperties": false,
98
+ "properties": {
99
+ "redirectUrl": { "type": "string" }
100
+ }
101
+ },
102
+ "passthroughResponse": {
103
+ "type": "object",
104
+ "required": ["passthrough"],
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "passthrough": { "type": "boolean", "enum": [ true ] }
108
+ }
109
+ },
110
+ "entry": {
111
+ "type": "object",
112
+ "required": ["request", "response"],
113
+ "additionalProperties": false,
114
+ "properties": {
115
+ "request": { "$ref": "#/definitions/request" },
116
+ "response": {
117
+ "oneOf": [
118
+ { "$ref": "#/definitions/response" },
119
+ { "$ref": "#/definitions/redirectResponse" },
120
+ { "$ref": "#/definitions/passthroughResponse" }
121
+ ]
122
+ },
123
+ "maxHits": {
124
+ "type": "integer",
125
+ "minimum": 1
126
+ }
124
127
  }
125
128
  }
126
- }
127
- },
128
- "type": "object",
129
- "required": ["entries"],
129
+ },
130
+ "type": "object",
131
+ "required": ["entries"],
130
132
  "properties": {
131
- "version": { "type": "string", "enum": ["1.2", "1.2.0"] },
132
- "creator": { "type": "string" },
133
+ "version": { "type": "string", "enum": ["1.2", "1.2.0"] },
134
+ "creator": { "type": "string" },
133
135
  "entries": {
134
136
  "type": "array",
135
137
  "items": { "$ref": "#/definitions/entry" }
@@ -602,6 +602,49 @@ function buildSeleniumOptions(browserOptions, testName, testRunConfig, gridInfo,
602
602
  return opts;
603
603
  }
604
604
 
605
+ //testRunConfig not in used for now
606
+ function buildAppiumOptions({ projectType, gridInfo, testRunConfig, nativeApp }) {
607
+ if (!nativeApp) {
608
+ throw Error('missing mobile app!');
609
+ }
610
+ if (gridInfo.type !== gridTypes.TESTIM_HEADSPIN) {
611
+ throw Error('unsupported grid was detected please make sure to select supported mobile grid');
612
+ }
613
+ const connection = {
614
+ protocol: gridInfo.protocol || 'https',
615
+ hostname: gridInfo.host,
616
+ port: gridInfo.port,
617
+ path: `/v0/${gridInfo.accessToken}/wd/hub`,
618
+ };
619
+
620
+ let appCaps = {};
621
+ switch (projectType) {
622
+ case 'ios':
623
+ appCaps = {
624
+ 'appium:bundleId': nativeApp.id,
625
+ platformName: 'iOS',
626
+ 'appium:automationName': 'XCUITest',
627
+ };
628
+ break;
629
+ case 'android':
630
+ appCaps = {
631
+ appPackage: nativeApp.packageName,
632
+ appActivity: nativeApp.activity,
633
+ 'appium:automationName': 'UiAutomator2',
634
+ };
635
+ break;
636
+ default:
637
+ throw Error(`unsupported mobile project ${projectType}`);
638
+ }
639
+ return {
640
+ ...connection,
641
+ desiredCapabilities: appCaps,
642
+ capabilities: appCaps,
643
+ };
644
+ }
645
+
605
646
  module.exports = {
606
647
  buildSeleniumOptions,
648
+ buildAppiumOptions,
649
+
607
650
  };
@@ -21,7 +21,7 @@ function getTokenHeader() {
21
21
  return testimCustomToken.getCustomTokenV3()
22
22
  .then(brearToken => {
23
23
  if (!brearToken) {
24
- return Promise.reject(new Error('Failed to get token from server'));
24
+ throw new Error('Failed to get token from server');
25
25
  }
26
26
  return { Authorization: `Bearer ${brearToken}` };
27
27
  });
@@ -43,6 +43,7 @@ function postAuth({
43
43
  });
44
44
  }
45
45
 
46
+ // eslint-disable-next-line default-param-last
46
47
  function postAuthFormData(url, fields, files, headers = {}, timeout) {
47
48
  return getTokenHeader()
48
49
  .then(tokenHeaders => {
@@ -160,6 +161,7 @@ function reportExecutionStarted({
160
161
  });
161
162
  }
162
163
 
164
+ // eslint-disable-next-line default-param-last
163
165
  function reportExecutionFinished(status, executionId, projectId, success, tmsOptions = {}, remoteRunId, resultExtraData) {
164
166
  const endTime = Date.now();
165
167
 
@@ -289,7 +291,14 @@ async function getEditorUrl() {
289
291
  return config.EDITOR_URL;
290
292
  }
291
293
  try {
292
- return await pRetry(() => getWithAuth('/system-info/editor-url'), { reties: DEFAULT_REQUEST_RETRY });
294
+ return await pRetry(() => getWithAuth('/system-info/editor-url'), {
295
+ reties: DEFAULT_REQUEST_RETRY,
296
+ onFailedAttempt: error => {
297
+ if (error.attemptNumber >= DEFAULT_REQUEST_RETRY) {
298
+ throw error;
299
+ }
300
+ },
301
+ });
293
302
  } catch (err) {
294
303
  logger.error('cannot retrieve editor-url from server');
295
304
  return 'https://app.testim.io';
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  const fse = require('fs-extra');
4
4
  const path = require('path');
@@ -26,10 +26,8 @@ async function getCredentialsFromChrome() {
26
26
  return timeout(new Promise(resolve => app.get('/loginInfo', (req, res) => {
27
27
  resolve(JSON.parse(Buffer.from(req.query.info, 'base64').toString()));
28
28
  res.status(200).end();
29
- })), 60000).catch(() => {
30
- return null;
31
- });
32
- })();
29
+ })), 60000).catch(() => null);
30
+ }());
33
31
  await new Promise((resolve, reject) => {
34
32
  const server = app.listen(42543, (err) => {
35
33
  if (err) {
@@ -45,6 +43,7 @@ async function getCredentialsFromChrome() {
45
43
  const url = await getEditorUrl();
46
44
  launchChrome(`${url}/#/new-test`);
47
45
  } catch (err) {
46
+ // eslint-disable-next-line no-console
48
47
  console.log('Unable to open Testim automatically - please manually go to https://app.testim.io');
49
48
  }
50
49
 
@@ -52,7 +51,7 @@ async function getCredentialsFromChrome() {
52
51
  return data;
53
52
  }
54
53
 
55
- async function doLogin({overwriteExisting = true, projects = null} = {}) {
54
+ async function doLogin({ overwriteExisting = true, projects = null } = {}) {
56
55
  const homedir = os.homedir();
57
56
 
58
57
  const testimCredentialsFile = path.join(homedir, '.testim');
@@ -63,17 +62,15 @@ async function doLogin({overwriteExisting = true, projects = null} = {}) {
63
62
  return;
64
63
  }
65
64
 
66
- let credentials = {};
65
+ const credentials = {};
67
66
 
68
67
  const prompts = require('prompts');
69
68
  const ora = require('ora');
70
69
 
71
- let spinner = ora(`Getting credentials from Testim extension ...`).start();
70
+ const spinner = ora('Getting credentials from Testim extension ...').start();
72
71
 
73
72
  if (!projects) {
74
- projects = await timeout(Promise.resolve(getCredentialsFromChrome()), 62000).catch(e => {
75
- return null;
76
- });
73
+ projects = await timeout(Promise.resolve(getCredentialsFromChrome()), 62000).catch(e => null);
77
74
  }
78
75
 
79
76
  if (projects && projects.token) { // V1(legacy) of the login extension API
@@ -83,19 +80,18 @@ async function doLogin({overwriteExisting = true, projects = null} = {}) {
83
80
 
84
81
  await writeCredentials(testimCredentialsFile, credentials);
85
82
  return;
86
- } else if (projects && projects.length) { // V2(current) of the login extension API
87
-
83
+ } if (projects && projects.length) { // V2(current) of the login extension API
88
84
  spinner.succeed();
89
85
 
90
86
  const response = projects.length === 1 ?
91
- { project: projects[0]} :
87
+ { project: projects[0] } :
92
88
  await prompts({
93
89
  type: 'select',
94
90
  name: 'project',
95
91
  message: 'There are multiple projects associated with your user account. Please select the project you would like to connect to:',
96
- choices: projects.map(p => ({title: p.name, value: p}))
92
+ choices: projects.map(p => ({ title: p.name, value: p })),
97
93
  }
98
- );
94
+ );
99
95
 
100
96
  credentials.token = response.project.ci.token;
101
97
  credentials.projectId = response.project.id;
@@ -104,13 +100,14 @@ async function doLogin({overwriteExisting = true, projects = null} = {}) {
104
100
  }
105
101
 
106
102
  spinner.fail();
103
+ // eslint-disable-next-line no-console
107
104
  console.log('Error getting credentials - please pass `--token` and `--project` to the CLI or try again');
108
-
109
105
  }
110
106
 
111
107
  async function writeCredentials(testimCredentialsFile, credentials) {
112
108
  await fse.writeFile(testimCredentialsFile, YAML.stringify(credentials));
113
- console.log(`Testim credentials saved in '${testimCredentialsFile}'`)
109
+ // eslint-disable-next-line no-console
110
+ console.log(`Testim credentials saved in '${testimCredentialsFile}'`);
114
111
  }
115
112
 
116
113
  async function getCredentialProperty(property) {
@@ -139,5 +136,5 @@ async function getCredentialProperty(property) {
139
136
  module.exports = {
140
137
  getProjectId,
141
138
  getToken,
142
- doLogin
143
- }
139
+ doLogin,
140
+ };