@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.
- package/agent/routers/index.js +2 -1
- package/agent/routers/playground/router.js +1 -1
- package/commons/chrome-launcher.js +6 -6
- package/commons/constants.js +2 -0
- package/commons/getSessionPlayerRequire.js +2 -20
- package/commons/initializeUserWithAuth.js +2 -2
- package/commons/mockNetworkRuleFileSchema.json +40 -38
- package/commons/testimDesiredCapabilitiesBuilder.js +43 -0
- package/commons/testimServicesApi.js +11 -2
- package/credentialsManager.js +17 -20
- package/npm-shrinkwrap.json +2104 -444
- package/package.json +4 -2
- package/player/WebdriverioWebDriverApi.js +7 -2
- package/player/appiumTestPlayer.js +102 -0
- package/player/seleniumTestPlayer.js +3 -2
- package/player/services/frameLocator.js +2 -1
- package/player/services/mobileFrameLocatorMock.js +32 -0
- package/player/services/playbackTimeoutCalculator.js +1 -0
- package/player/services/portSelector.js +10 -8
- package/player/services/tabService.js +29 -0
- package/player/stepActions/sfdcRecordedStepAction.js +2 -2
- package/player/stepActions/sfdcStepAction.js +2 -2
- package/player/stepActions/stepAction.js +15 -1
- package/player/utils/stepActionUtils.js +4 -2
- package/player/utils/windowUtils.js +138 -125
- package/player/webdriver.js +39 -25
- package/reports/debugReporter.js +41 -39
- package/reports/jsonReporter.js +53 -50
- package/reports/reporter.js +135 -136
- package/runOptions.js +2 -1
- package/runners/ParallelWorkerManager.js +2 -0
- package/testRunStatus.js +457 -459
- package/workers/BaseWorker.js +13 -6
- package/workers/WorkerAppium.js +123 -0
package/agent/routers/index.js
CHANGED
|
@@ -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',
|
|
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
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
const { exec } = require(
|
|
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 ===
|
|
8
|
+
if (platform === 'win32') {
|
|
9
9
|
exec(`start chrome ${url}`);
|
|
10
|
-
} else if (platform ===
|
|
10
|
+
} else if (platform === 'darwin') {
|
|
11
11
|
exec(`open -a "Google Chrome" ${url}`);
|
|
12
|
-
} else if (platform ===
|
|
12
|
+
} else if (platform === 'linux') {
|
|
13
13
|
exec(`google-chrome ${url}`);
|
|
14
14
|
}
|
|
15
15
|
};
|
package/commons/constants.js
CHANGED
|
@@ -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')
|
|
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 ===
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
"
|
|
129
|
-
"required": ["entries"],
|
|
129
|
+
},
|
|
130
|
+
"type": "object",
|
|
131
|
+
"required": ["entries"],
|
|
130
132
|
"properties": {
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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'), {
|
|
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';
|
package/credentialsManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
const credentials = {};
|
|
67
66
|
|
|
68
67
|
const prompts = require('prompts');
|
|
69
68
|
const ora = require('ora');
|
|
70
69
|
|
|
71
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
+
};
|