@testim/testim-cli 3.244.0 → 3.247.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/chromiumInstaller.js +92 -0
- package/cli.js +89 -87
- package/cliAgentMode.js +39 -114
- package/commons/chromedriverWrapper.js +4 -3
- package/commons/featureFlags.js +1 -0
- package/commons/getSessionPlayerRequire.js +2 -1
- package/commons/httpRequestCounters.test.js +9 -9
- package/commons/npmWrapper.js +3 -3
- package/commons/npmWrapper.test.js +1 -1
- package/commons/prepareRunner.js +2 -0
- package/commons/socket/baseSocketServiceSocketIO.js +2 -2
- package/commons/testimDesiredCapabilitiesBuilder.js +1 -3
- package/commons/testimServicesApi.js +45 -39
- package/errors.js +2 -1
- package/npm-shrinkwrap.json +467 -366
- package/package.json +2 -2
- package/player/WebDriverHttpRequest.js +45 -37
- package/player/chromeLauncherTestPlayer.js +2 -2
- package/player/services/playbackTimeoutCalculator.js +5 -1
- package/player/stepActions/RefreshStepAction.js +2 -4
- package/player/stepActions/inputFileStepAction.js +6 -2
- package/player/stepActions/sfdcStepAction.js +27 -0
- package/player/stepActions/stepActionRegistrar.js +12 -0
- package/player/utils/screenshotUtils.js +2 -2
- package/player/utils/windowUtils.js +3 -3
- package/player/webdriver.js +51 -52
- package/runOptions.js +3 -1
- package/runner.js +44 -41
- package/services/lambdatestService.js +2 -2
- package/services/lambdatestService.test.js +2 -1
- package/testRunHandler.js +7 -3
- package/testRunStatus.js +2 -1
- package/utils.js +54 -15
- package/utils.test.js +26 -0
- package/workers/BaseWorker.js +9 -7
- package/workers/BaseWorker.test.js +11 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testim/testim-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.247.0",
|
|
4
4
|
"description": "Command line interface for running Testing on your CI",
|
|
5
5
|
"author": "Oren Rubin",
|
|
6
6
|
"contributors": [{
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"appium-dom-utils": "1.0.6",
|
|
58
58
|
"archiver": "5.3.0",
|
|
59
59
|
"bluebird": "3.7.2",
|
|
60
|
-
"bluebird-retry": "0.11.0",
|
|
61
60
|
"body-parser": "1.19.1",
|
|
62
61
|
"chalk": "4.1.2",
|
|
63
62
|
"chrome-launcher": "0.15.0",
|
|
@@ -87,6 +86,7 @@
|
|
|
87
86
|
"npm": "8.3.0",
|
|
88
87
|
"object-hash": "3.0.0",
|
|
89
88
|
"ora": "5.4.1",
|
|
89
|
+
"p-retry": "4.6.2",
|
|
90
90
|
"pako": "1.0.11",
|
|
91
91
|
"portfinder": "1.0.28",
|
|
92
92
|
"promise-queue": "2.2.5",
|
|
@@ -4,8 +4,8 @@ const _ = require('lodash');
|
|
|
4
4
|
const httpRequest = require('../commons/httpRequest');
|
|
5
5
|
const utils = require('../utils');
|
|
6
6
|
const SeleniumProtocolError = require('./SeleniumProtocolError');
|
|
7
|
-
const {SELENIUM_STATUS_CODES} = require('./constants');
|
|
8
|
-
const logger = require('
|
|
7
|
+
const { SELENIUM_STATUS_CODES } = require('./constants');
|
|
8
|
+
const logger = require('../commons/logger').getLogger('WebDriverHttpRequest');
|
|
9
9
|
|
|
10
10
|
function isSuccessfulResponse(body, statusCode) {
|
|
11
11
|
/**
|
|
@@ -56,37 +56,37 @@ class WebDriverHttpRequest {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
handleFailedStatus(requestId, response, err = {}) {
|
|
59
|
-
const {body, statusCode, headers, text} = response;
|
|
59
|
+
const { body, statusCode, headers, text } = response;
|
|
60
60
|
const resBody = _.isEmpty(body) && text ? text : body;
|
|
61
61
|
/**
|
|
62
62
|
* in Appium you find sometimes more exact error messages in origValue
|
|
63
63
|
*/
|
|
64
64
|
if (resBody && resBody.value && typeof resBody.value.origValue === 'string' && typeof resBody.value.message === 'string') {
|
|
65
|
-
resBody.value.message +=
|
|
65
|
+
resBody.value.message += ` ${resBody.value.origValue}`;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
if (resBody && typeof resBody === 'string') {
|
|
69
|
-
throw new SeleniumProtocolError(resBody, {requestId});
|
|
69
|
+
throw new SeleniumProtocolError(resBody, { requestId });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (resBody && resBody.value) {
|
|
73
73
|
const errorCode = SELENIUM_STATUS_CODES[resBody.status] || (resBody.value && SELENIUM_STATUS_CODES[resBody.value.error]) || SELENIUM_STATUS_CODES[-1];
|
|
74
|
-
|
|
74
|
+
const error = {
|
|
75
75
|
type: errorCode ? errorCode.id : 'unknown',
|
|
76
76
|
message: errorCode ? errorCode.message : 'unknown',
|
|
77
|
-
orgStatusMessage: resBody.value ? resBody.value.message : ''
|
|
77
|
+
orgStatusMessage: resBody.value ? resBody.value.message : '',
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
throw new SeleniumProtocolError(error, {requestId});
|
|
80
|
+
throw new SeleniumProtocolError(error, { requestId });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// IEServer webdriver bug where the error is put into the Allow header
|
|
84
84
|
// https://github.com/SeleniumHQ/selenium/issues/6828
|
|
85
85
|
if (statusCode === 405) {
|
|
86
86
|
const allowHeader = headers && headers.allow;
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
89
|
-
throw
|
|
87
|
+
const _err = new SeleniumProtocolError(allowHeader, { requestId });
|
|
88
|
+
if (_err.message.match(/Command not found/)) {
|
|
89
|
+
throw _err;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -94,12 +94,12 @@ class WebDriverHttpRequest {
|
|
|
94
94
|
status: -1,
|
|
95
95
|
type: err.code || 'ECONNREFUSED',
|
|
96
96
|
orgStatusMessage: err.rawResponse || err.message,
|
|
97
|
-
message: err.rawResponse || "Couldn't connect to selenium server"
|
|
98
|
-
}, {requestId});
|
|
97
|
+
message: err.rawResponse || "Couldn't connect to selenium server",
|
|
98
|
+
}, { requestId });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
processHttpOnSuccess(response, requestId) {
|
|
102
|
-
const {body, statusCode} = response;
|
|
102
|
+
const { body, statusCode } = response;
|
|
103
103
|
if (isSuccessfulResponse(body, statusCode)) {
|
|
104
104
|
return body;
|
|
105
105
|
}
|
|
@@ -108,44 +108,49 @@ class WebDriverHttpRequest {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
processHttpOnError(err, requestId) {
|
|
111
|
-
const response = err && err.response || {};
|
|
111
|
+
const response = (err && err.response) || {};
|
|
112
112
|
return this.handleFailedStatus(requestId, response, err);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
httpPostRequest(path, body = {}) {
|
|
116
116
|
const requestId = utils.guid();
|
|
117
|
-
if (path ===
|
|
118
|
-
logger.info(
|
|
117
|
+
if (path === '/session') {
|
|
118
|
+
logger.info('POST REQUEST', { requestId, path, testResultId: this.testResultId });
|
|
119
119
|
} else {
|
|
120
|
-
logger.info(
|
|
120
|
+
logger.info('POST REQUEST', { requestId, path, body, testResultId: this.testResultId });
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
return httpRequest.postFullRes(`${this.gridUrl}${path}`, body, this.headers, this.connectionRetryTimeout)
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
.then(response => {
|
|
125
|
+
logger.info('POST RESPONSE', {
|
|
126
|
+
requestId,
|
|
127
|
+
path,
|
|
128
|
+
res: JSON.stringify(response.body),
|
|
129
|
+
testResultId: this.testResultId,
|
|
130
|
+
});
|
|
131
|
+
return response;
|
|
132
|
+
})
|
|
130
133
|
.catch(err => this.processHttpOnError(err, requestId))
|
|
131
134
|
.then((response) => this.processHttpOnSuccess(response, requestId));
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
httpGetRequest(path) {
|
|
135
138
|
const requestId = utils.guid();
|
|
136
|
-
logger.info(
|
|
139
|
+
logger.info('GET REQUEST', { requestId, path, testResultId: this.testResultId });
|
|
137
140
|
const query = {};
|
|
138
141
|
return httpRequest.getFullRes(`${this.gridUrl}${path}`, query, this.headers, this.connectionRetryTimeout)
|
|
139
|
-
.
|
|
140
|
-
if (_.endsWith(path,
|
|
141
|
-
|
|
142
|
+
.then(response => {
|
|
143
|
+
if (_.endsWith(path, '/screenshot')) {
|
|
144
|
+
logger.info('GET RESPONSE', { requestId, path, testResultId: this.testResultId });
|
|
145
|
+
return response;
|
|
142
146
|
}
|
|
143
|
-
logger.info(
|
|
147
|
+
logger.info('GET RESPONSE', {
|
|
144
148
|
requestId,
|
|
145
149
|
path,
|
|
146
150
|
res: JSON.stringify(response.body),
|
|
147
|
-
testResultId: this.testResultId
|
|
151
|
+
testResultId: this.testResultId,
|
|
148
152
|
});
|
|
153
|
+
return response;
|
|
149
154
|
})
|
|
150
155
|
.catch(err => this.processHttpOnError(err, requestId))
|
|
151
156
|
.then((response) => this.processHttpOnSuccess(response, requestId));
|
|
@@ -153,14 +158,17 @@ class WebDriverHttpRequest {
|
|
|
153
158
|
|
|
154
159
|
httpDeleteRequest(path) {
|
|
155
160
|
const requestId = utils.guid();
|
|
156
|
-
logger.info(
|
|
161
|
+
logger.info('DELETE REQUEST', { requestId, path, testResultId: this.testResultId });
|
|
157
162
|
return httpRequest.deleteFullRes(`${this.gridUrl}${path}`, undefined, this.headers, this.connectionRetryTimeout)
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
.then(response => {
|
|
164
|
+
logger.info('DELETE RESPONSE', {
|
|
165
|
+
requestId,
|
|
166
|
+
path,
|
|
167
|
+
res: JSON.stringify(response.body),
|
|
168
|
+
testResultId: this.testResultId,
|
|
169
|
+
});
|
|
170
|
+
return response;
|
|
171
|
+
})
|
|
164
172
|
.catch(err => this.processHttpOnError(err, requestId))
|
|
165
173
|
.then((response) => this.processHttpOnSuccess(response, requestId));
|
|
166
174
|
}
|
|
@@ -23,8 +23,8 @@ class LauncherDriver {
|
|
|
23
23
|
this.chrome.process.once('exit', () => { this._isAlive = false; });
|
|
24
24
|
this.chrome.process.once('close', () => { this._isAlive = false; });
|
|
25
25
|
this._isAlive = true;
|
|
26
|
-
const
|
|
27
|
-
await this.cdpTestRunner.initSession(
|
|
26
|
+
const webSocketDebuggerUrl = await utils.getCdpAddressForHost(`localhost:${this.chrome.port}`);
|
|
27
|
+
await this.cdpTestRunner.initSession(webSocketDebuggerUrl);
|
|
28
28
|
|
|
29
29
|
registerExitHook(() => this.chrome.kill());
|
|
30
30
|
}
|
|
@@ -4,6 +4,7 @@ const _ = require('lodash');
|
|
|
4
4
|
|
|
5
5
|
const COMMUNICATION_BUFFER_TIME = 1000;
|
|
6
6
|
const UI_VERIFICATION_STEPS = ['simple-ui-verification', 'wait-for-simple-ui-verification'];
|
|
7
|
+
const FULL_TIMEOUT_STEP_TYPES = [...UI_VERIFICATION_STEPS, 'custom-validation', 'sfdc-step-login'];
|
|
7
8
|
|
|
8
9
|
class PlaybackTimeoutCalculator {
|
|
9
10
|
constructor(isDebuggerConnected) {
|
|
@@ -41,7 +42,7 @@ class PlaybackTimeoutCalculator {
|
|
|
41
42
|
};
|
|
42
43
|
stepPlayback.setStartTimestamp();
|
|
43
44
|
const totalStepTime = this.getTotalStepRunTime(stepPlayback);
|
|
44
|
-
const currentRetryTimes =
|
|
45
|
+
const currentRetryTimes = FULL_TIMEOUT_STEP_TYPES.includes(stepPlayback.stepType) ? [totalStepTime] : getRetryTimeoutSuggestions(totalStepTime);
|
|
45
46
|
this.resetStepVariables(totalStepTime, currentRetryTimes);
|
|
46
47
|
stepPlayback.context.data.maxTotalStepTime = totalStepTime;
|
|
47
48
|
}
|
|
@@ -61,6 +62,9 @@ class PlaybackTimeoutCalculator {
|
|
|
61
62
|
if (UI_VERIFICATION_STEPS.includes(stepPlayback.stepType)) {
|
|
62
63
|
fallbackTimeout = stepPlayback.context.config.applitoolsStepTimeout || HALF_HOUR_IN_MS;
|
|
63
64
|
}
|
|
65
|
+
if (stepPlayback.step.type.startsWith('sfdc-')) {
|
|
66
|
+
fallbackTimeout = stepPlayback.step.defaultTimeout;
|
|
67
|
+
}
|
|
64
68
|
return (stepPlayback.step.useStepTimeout && stepPlayback.step.stepTimeout) ? stepPlayback.step.stepTimeout : fallbackTimeout;
|
|
65
69
|
}
|
|
66
70
|
getTotalStepTimeLeftToPlay(stepPlayback, totalStepTime = this.totalStepTime) {
|
|
@@ -6,10 +6,8 @@ require('bluebird');
|
|
|
6
6
|
class RefreshStepAction extends StepAction {
|
|
7
7
|
execute() {
|
|
8
8
|
return this.driver.reloadTab()
|
|
9
|
-
.
|
|
10
|
-
.catch(error => {
|
|
11
|
-
return { success: false, reason: error.message };
|
|
12
|
-
});
|
|
9
|
+
.then(() => ({ success: true }))
|
|
10
|
+
.catch(error => ({ success: false, reason: error.message }));
|
|
13
11
|
}
|
|
14
12
|
}
|
|
15
13
|
|
|
@@ -86,18 +86,22 @@ class InputFileStepAction extends StepAction {
|
|
|
86
86
|
const target = this.context.data[this.step.targetId || 'targetId'];
|
|
87
87
|
const overrideAzureStorageUrl = featureFlagService.flags.overrideAzureStorageUrl.isEnabled();
|
|
88
88
|
const useJsInputCodeInSafari = featureFlagService.flags.useJsInputCodeInSafari.isEnabled();
|
|
89
|
+
const useJsInputCodeInFirefox = featureFlagService.flags.useJsInputCodeInFirefox.isEnabled();
|
|
89
90
|
const downloadToBase64 = featureFlagService.flags.downloadToBase64.isEnabled();
|
|
90
91
|
|
|
91
92
|
let fileUrls = await utils.addTokenToFileUrl(this.context.project.id, this.step.fileUrls, this.stepActionUtils.testimServicesApi, overrideAzureStorageUrl, logger);
|
|
92
93
|
|
|
94
|
+
|
|
95
|
+
const isSafariJsInputCode = this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1);
|
|
96
|
+
const isFirefoxJsInputCode = this.driver.isFirefox() && (useJsInputCodeInFirefox || fileUrls.length > 1);
|
|
97
|
+
|
|
93
98
|
if (downloadToBase64) {
|
|
94
99
|
fileUrls = await Promise.all(fileUrls.map(async ({ name, url }) => {
|
|
95
100
|
const res = await download(url);
|
|
96
101
|
return { name, url: `data:${res.type};base64,${Buffer.from(res.body).toString('base64')}` };
|
|
97
102
|
}));
|
|
98
103
|
}
|
|
99
|
-
|
|
100
|
-
if (this.driver.isSafari() && (useJsInputCodeInSafari || fileUrls.length > 1)) {
|
|
104
|
+
if (isSafariJsInputCode || isFirefoxJsInputCode) {
|
|
101
105
|
await this.driver.executeJSWithArray(`
|
|
102
106
|
const getLocatedElement = ${codeSnippets.getLocatedElementCode};
|
|
103
107
|
const downloadAndUploadFile = ${downloadAndUpload()};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const StepAction = require('./stepAction');
|
|
2
|
+
const { sfdc } = require('../../commons/getSessionPlayerRequire');
|
|
3
|
+
|
|
4
|
+
class SfdcStepAction extends StepAction {
|
|
5
|
+
async performAction() {
|
|
6
|
+
const page = sfdc.sfdcNewSePage(this.driver);
|
|
7
|
+
try {
|
|
8
|
+
const actions = this.context.sfdcTestActions;
|
|
9
|
+
if (actions === undefined) {
|
|
10
|
+
throw new Error('No test actions were compiled');
|
|
11
|
+
}
|
|
12
|
+
await sfdc.sfdcExecute(page, actions);
|
|
13
|
+
return { success: true };
|
|
14
|
+
} catch (err) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
reason: err.reason || err.message,
|
|
18
|
+
exception: err,
|
|
19
|
+
shouldRetry: false, // TODO - check this. Our (bFormat) steps are probably not retryable?
|
|
20
|
+
};
|
|
21
|
+
} finally {
|
|
22
|
+
page.releaseObjects();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = SfdcStepAction;
|
|
@@ -27,6 +27,7 @@ const CliJsStepAction = require('./cliJsStepAction');
|
|
|
27
27
|
const CliConditionStepAction = require('./cliConditionStepAction');
|
|
28
28
|
const NodePackageStepAction = require('./nodePackageStepAction');
|
|
29
29
|
const ExtensionOnlyStepAction = require('./extensionOnlyStepAction');
|
|
30
|
+
const SfdcStepAction = require('./sfdcStepAction');
|
|
30
31
|
|
|
31
32
|
function register(stepActionByType, stepActionFactory) {
|
|
32
33
|
Object.keys(stepActionByType).forEach(type => {
|
|
@@ -82,6 +83,17 @@ module.exports = function (driver, stepActionFactory, runMode) {
|
|
|
82
83
|
'email-code-step': JsCodeStepAction,
|
|
83
84
|
'cli-email-code-step': CliJsStepAction,
|
|
84
85
|
'tdk-hybrid': TdkHybridStepAction,
|
|
86
|
+
|
|
87
|
+
'sfdc-step-login': SfdcStepAction,
|
|
88
|
+
'sfdc-step-logout': SfdcStepAction,
|
|
89
|
+
'sfdc-step-sobjectcreate': SfdcStepAction,
|
|
90
|
+
'sfdc-step-sobjectdelete': SfdcStepAction,
|
|
91
|
+
'sfdc-step-findrecord': SfdcStepAction,
|
|
92
|
+
'sfdc-step-quickaction': SfdcStepAction,
|
|
93
|
+
'sfdc-step-edit': SfdcStepAction,
|
|
94
|
+
'sfdc-step-sobjectvalidate': SfdcStepAction,
|
|
95
|
+
'sfdc-step-launchapp': SfdcStepAction,
|
|
96
|
+
'sfdc-step-closeconsoletabs': SfdcStepAction,
|
|
85
97
|
};
|
|
86
98
|
|
|
87
99
|
register(STEP_ACTION_MAPPING, stepActionFactory);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
const
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
5
|
|
|
6
6
|
class ScreenshotUtils {
|
|
7
7
|
constructor(tabId, driver, options = { takeScreenshots: true }) {
|
|
@@ -29,7 +29,7 @@ class ScreenshotUtils {
|
|
|
29
29
|
const SCREENSHOT_RETRY_DELAY = 2000;
|
|
30
30
|
const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
|
|
31
31
|
const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
|
|
32
|
-
return
|
|
32
|
+
return pRetry(getScreenshot, { retries: MAX_RETRY_COUNT, minTimeout: SCREENSHOT_RETRY_DELAY })
|
|
33
33
|
.then(([devicePixelRatio, image]) => {
|
|
34
34
|
const base64 = image ? image.value : '';
|
|
35
35
|
const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Promise = require('bluebird');
|
|
4
|
-
const
|
|
4
|
+
const pRetry = require('p-retry');
|
|
5
5
|
const { PageNotAvailableError } = require('../../errors');
|
|
6
6
|
const logger = require('../../commons/logger').getLogger('window-utils');
|
|
7
7
|
|
|
@@ -152,12 +152,12 @@ WindowUtils.prototype.quit = function () {
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
WindowUtils.prototype.getOsAndBrowser = function () {
|
|
155
|
-
return
|
|
155
|
+
return pRetry(() => this.driver.getBrowserAndOS(), { retries: 3 })
|
|
156
156
|
.then(osAndBrowser => ({ uaBrowserName: osAndBrowser.browser, uaOs: osAndBrowser.os, userAgent: osAndBrowser.userAgent, browserVersion: osAndBrowser.browserVersion }));
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
WindowUtils.prototype.getUserAgentInfo = function () {
|
|
160
|
-
return
|
|
160
|
+
return pRetry(() => this.driver.getUserAgentInfo(), { retries: 3 });
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
module.exports = WindowUtils;
|
package/player/webdriver.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-var */
|
|
2
|
+
'use strict';
|
|
2
3
|
|
|
3
|
-
const logger = require('../commons/logger').getLogger(
|
|
4
|
+
const logger = require('../commons/logger').getLogger('webdriver');
|
|
4
5
|
const Promise = require('bluebird');
|
|
5
6
|
const parser = require('ua-parser-js');
|
|
6
7
|
const desiredCapabilitiesBuilder = require('../commons/testimDesiredCapabilitiesBuilder');
|
|
@@ -14,8 +15,7 @@ const featureFlags = require('../commons/featureFlags');
|
|
|
14
15
|
const _ = require('lodash');
|
|
15
16
|
|
|
16
17
|
const [LEFT, RIGHT] = [0, 2];
|
|
17
|
-
const { extractElementId } = utils;
|
|
18
|
-
const httpRequest = require('../commons/httpRequest');
|
|
18
|
+
const { extractElementId, getCdpAddressForHost } = utils;
|
|
19
19
|
const perf = require('../commons/performance-logger');
|
|
20
20
|
const { SeleniumPerfStats, SELENIUM_PERF_MARKS } = require('../commons/SeleniumPerfStats');
|
|
21
21
|
|
|
@@ -34,15 +34,11 @@ const playerUtils = () => {
|
|
|
34
34
|
|
|
35
35
|
async function getCdpAddress(initResult) {
|
|
36
36
|
try {
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
37
|
+
const debuggerHost = initResult && initResult.value && initResult.value['goog:chromeOptions'] && initResult.value['goog:chromeOptions'].debuggerAddress;
|
|
38
|
+
if (!debuggerHost) {
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
if (!browserEndpoint) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
return browserEndpoint.webSocketDebuggerUrl;
|
|
41
|
+
return await getCdpAddressForHost(debuggerHost);
|
|
46
42
|
} catch (e) {
|
|
47
43
|
logger.info('Error getting cdpAddress', e);
|
|
48
44
|
return undefined;
|
|
@@ -202,7 +198,10 @@ class WebDriver extends WebDriverApi {
|
|
|
202
198
|
|
|
203
199
|
switchToLocatedFrame(locatedElement) {
|
|
204
200
|
return this.getElement(locatedElement)
|
|
205
|
-
.
|
|
201
|
+
.then(async el => {
|
|
202
|
+
await this.switchToFrame(el.value);
|
|
203
|
+
return el;
|
|
204
|
+
});
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
switchToFrame(el) {
|
|
@@ -220,7 +219,7 @@ class WebDriver extends WebDriverApi {
|
|
|
220
219
|
|
|
221
220
|
getElement(locatedElement) {
|
|
222
221
|
const perfId = this.seleniumPerfStats.markStart(SELENIUM_PERF_MARKS.GET_ELEMENT);
|
|
223
|
-
if (typeof locatedElement ===
|
|
222
|
+
if (typeof locatedElement === 'string' || typeof locatedElement === 'number') { // support testimId in the meanwhile for backwards compatability
|
|
224
223
|
return this.getElementBySelector(`[testim_dom_element_id='${locatedElement}']`)
|
|
225
224
|
.finally(() => this.seleniumPerfStats.markEnd(perfId, SELENIUM_PERF_MARKS.GET_ELEMENT));
|
|
226
225
|
}
|
|
@@ -353,13 +352,13 @@ class WebDriver extends WebDriverApi {
|
|
|
353
352
|
|
|
354
353
|
function isTextElement(element) {
|
|
355
354
|
var tagName = element.tagName;
|
|
356
|
-
return (tagName ===
|
|
355
|
+
return (tagName === 'INPUT' || tagName === 'TEXTAREA');
|
|
357
356
|
}
|
|
358
357
|
|
|
359
358
|
function getFixedTextContent(element) {
|
|
360
359
|
try { // fix for salesforce's custom-elements
|
|
361
360
|
if (element.shadowRoot && Object.getOwnPropertyDescriptor(element.ownerDocument.defaultView.Node.prototype,'textContent').get.toString().indexOf('[native code]') === -1) {
|
|
362
|
-
return element.shadowRoot.textContent.replace(/(\r\n|\n|\r)/gm,
|
|
361
|
+
return element.shadowRoot.textContent.replace(/(\r\n|\n|\r)/gm, '');
|
|
363
362
|
}
|
|
364
363
|
} catch (err) { }
|
|
365
364
|
if (ignoreHiddenTagsText && Array.prototype.some.call(element.children, function (elem) { return elem.hidden; })) {
|
|
@@ -386,7 +385,7 @@ class WebDriver extends WebDriverApi {
|
|
|
386
385
|
var svgContent = new XMLSerializer().serializeToString(element);
|
|
387
386
|
copyElement = new DOMParser().parseFromString(svgContent, 'text/html').body.firstChild;
|
|
388
387
|
}
|
|
389
|
-
return clearTitleTags(copyElement).textContent.replace(/(\r\n|\n|\r)/gm,
|
|
388
|
+
return clearTitleTags(copyElement).textContent.replace(/(\r\n|\n|\r)/gm, '');
|
|
390
389
|
} else {
|
|
391
390
|
return getFixedTextContent(element);
|
|
392
391
|
}
|
|
@@ -429,7 +428,7 @@ class WebDriver extends WebDriverApi {
|
|
|
429
428
|
})
|
|
430
429
|
.catch(err => !(err instanceof IeError), err => {
|
|
431
430
|
this.seleniumPerfStats.markEnd(perfId, SELENIUM_PERF_MARKS.GET_HTML);
|
|
432
|
-
const testimInternalError = Object.assign(new Error(), { success: false, reason: err.toString(), errorType:
|
|
431
|
+
const testimInternalError = Object.assign(new Error(), { success: false, reason: err.toString(), errorType: 'internal-error' });
|
|
433
432
|
if (!this.client.requestHandler.sessionID) {
|
|
434
433
|
// we got here after the driver has been disposed of. It's impossible to run JavaScirpt on the page.
|
|
435
434
|
testimInternalError.extraInfo = 'Inside getHtml catch and trying to check if in quirks mode - but the session has already terminated';
|
|
@@ -438,10 +437,10 @@ class WebDriver extends WebDriverApi {
|
|
|
438
437
|
if (!this.isIE()) { // no need to check quirks mode if I'm not in IE
|
|
439
438
|
throw testimInternalError;
|
|
440
439
|
}
|
|
441
|
-
return this.executeJS(
|
|
440
|
+
return this.executeJS('return navigator.userAgent;')
|
|
442
441
|
.catch(() => Promise.reject(testimInternalError))
|
|
443
442
|
.then(ua => {
|
|
444
|
-
const error = this.isUsingUnsupportedCompabilityMode(ua.value) ? this.getIeError(
|
|
443
|
+
const error = this.isUsingUnsupportedCompabilityMode(ua.value) ? this.getIeError('Can’t run test in IE compatibility mode') : testimInternalError;
|
|
445
444
|
return Promise.reject(error);
|
|
446
445
|
});
|
|
447
446
|
});
|
|
@@ -527,7 +526,7 @@ class WebDriver extends WebDriverApi {
|
|
|
527
526
|
try {
|
|
528
527
|
return parseInt(parse.browser.major);
|
|
529
528
|
} catch (err) {
|
|
530
|
-
logger.error(
|
|
529
|
+
logger.error('failed to get browser version', { err: err });
|
|
531
530
|
return 0;
|
|
532
531
|
}
|
|
533
532
|
}
|
|
@@ -536,13 +535,13 @@ class WebDriver extends WebDriverApi {
|
|
|
536
535
|
function getBrowserName(ua, browserDetails) {
|
|
537
536
|
var M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
|
538
537
|
if (/trident/i.test(M[1])) {
|
|
539
|
-
return
|
|
538
|
+
return 'Internet Explorer ' + browserDetails.major;
|
|
540
539
|
}
|
|
541
540
|
if (M[1] === 'Chrome' && ua.match(/\bOPR\/(\d+)/) !== null) {
|
|
542
|
-
return
|
|
541
|
+
return 'opera';
|
|
543
542
|
}
|
|
544
543
|
if (M[1] === 'Chrome' && ua.match(/\bEdge|Edg\/(\d+)/) !== null) {
|
|
545
|
-
return
|
|
544
|
+
return 'edge';
|
|
546
545
|
}
|
|
547
546
|
M = M[2] ? [M[1], M[2]] : [ua.appName, ua.appVersion, '-?'];
|
|
548
547
|
var tem = ua.match(/version\/(\d+)/i);
|
|
@@ -558,7 +557,7 @@ class WebDriver extends WebDriverApi {
|
|
|
558
557
|
|
|
559
558
|
return this.executeJS(function () {
|
|
560
559
|
if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) {
|
|
561
|
-
return
|
|
560
|
+
return 'unknown';
|
|
562
561
|
}
|
|
563
562
|
return window.navigator.userAgent;
|
|
564
563
|
}).then(userAgent => {
|
|
@@ -566,7 +565,7 @@ class WebDriver extends WebDriverApi {
|
|
|
566
565
|
const parse = parser(rawUserAgent);
|
|
567
566
|
const osDetails = parse.os;
|
|
568
567
|
this.browserAndOS = {
|
|
569
|
-
os: osDetails.name +
|
|
568
|
+
os: osDetails.name + ' ' + osDetails.version,
|
|
570
569
|
browserMajor: this.getBrowserMajorVersion(parse),
|
|
571
570
|
browser: getBrowserName(userAgent.value, parse.browser),
|
|
572
571
|
userAgent: rawUserAgent,
|
|
@@ -595,7 +594,7 @@ class WebDriver extends WebDriverApi {
|
|
|
595
594
|
!playerUtils().isWithinBounds(-inViewCenter.y, inViewCenter.y, top)) {
|
|
596
595
|
// [NOTE] the code is not supposed to get here! - using center (0,0) instead of step offsets.
|
|
597
596
|
// this is a fallback so the action will take place even if for some reason calculation went out of element..
|
|
598
|
-
logger.warn(
|
|
597
|
+
logger.warn('using center as fallback for offset');
|
|
599
598
|
return this.getMoveActions(0, 0, element);
|
|
600
599
|
}
|
|
601
600
|
return this.getMoveActions(left, top, element);
|
|
@@ -612,20 +611,20 @@ class WebDriver extends WebDriverApi {
|
|
|
612
611
|
const moveActions = this.getRelativeMoveActions(offsets, element);
|
|
613
612
|
const clickActions = this.getClickActions(actions, button);
|
|
614
613
|
return this.actions([{
|
|
615
|
-
type:
|
|
616
|
-
id:
|
|
614
|
+
type: 'pointer',
|
|
615
|
+
id: 'mouse',
|
|
617
616
|
parameters: { pointerType: 'mouse' },
|
|
618
|
-
actions: moveActions.concat(clickActions)
|
|
617
|
+
actions: moveActions.concat(clickActions),
|
|
619
618
|
}]).catch(err => {
|
|
620
619
|
logger.error('tried to use element origin but failed because of visibility, trying absolute', err);
|
|
621
620
|
const { x, y } = this.computeAbsoluteMovement(offsets);
|
|
622
621
|
const moveActions = this.getMoveActions(x, y);
|
|
623
622
|
return this.actions([{
|
|
624
|
-
type:
|
|
625
|
-
id:
|
|
623
|
+
type: 'pointer',
|
|
624
|
+
id: 'mouse',
|
|
626
625
|
parameters: { pointerType: 'mouse' },
|
|
627
|
-
actions: moveActions.concat(clickActions)
|
|
628
|
-
}])
|
|
626
|
+
actions: moveActions.concat(clickActions),
|
|
627
|
+
}]);
|
|
629
628
|
});
|
|
630
629
|
}
|
|
631
630
|
|
|
@@ -639,7 +638,7 @@ class WebDriver extends WebDriverApi {
|
|
|
639
638
|
var getLocatedElement = ${codeSnippets().getLocatedElementCode};
|
|
640
639
|
var dispatchFocus = ${dispatchFocus.toString()};
|
|
641
640
|
var doubleClick = ${doubleClick.toString()};
|
|
642
|
-
var eventData = ${this.isEdge() ?
|
|
641
|
+
var eventData = ${this.isEdge() ? 'JSON.parse(arguments[0])' : 'arguments[0]'};
|
|
643
642
|
var done = arguments[1];
|
|
644
643
|
return doubleClick.call(null, eventData, done);
|
|
645
644
|
`, eventData.timeout, eventParam);
|
|
@@ -651,8 +650,8 @@ class WebDriver extends WebDriverApi {
|
|
|
651
650
|
|
|
652
651
|
getClickActionList(types = [], button) {
|
|
653
652
|
return [{
|
|
654
|
-
type:
|
|
655
|
-
id:
|
|
653
|
+
type: 'pointer',
|
|
654
|
+
id: 'mouse',
|
|
656
655
|
actions: this.getClickActions(types, button)
|
|
657
656
|
}];
|
|
658
657
|
}
|
|
@@ -740,26 +739,26 @@ class WebDriver extends WebDriverApi {
|
|
|
740
739
|
|
|
741
740
|
getMoveActions(x = 1, y = 1, origin = 'viewport', duration = 0) {
|
|
742
741
|
// force x != 0 ,y != 0 because of Safari issues
|
|
743
|
-
return [{ type:
|
|
742
|
+
return [{ type: 'pointerMove', duration, x: Math.floor(x) || 1, y: Math.floor(y) || 1, origin }];
|
|
744
743
|
}
|
|
745
744
|
|
|
746
745
|
moveWithActionsAPI(point) {
|
|
747
746
|
const actions = this.getMoveActions(point.x, point.y);
|
|
748
747
|
return this.actions([{
|
|
749
|
-
type:
|
|
750
|
-
id:
|
|
748
|
+
type: 'pointer',
|
|
749
|
+
id: 'mouse',
|
|
751
750
|
actions: actions
|
|
752
751
|
}]);
|
|
753
752
|
}
|
|
754
753
|
|
|
755
754
|
moveToElementWithActionsAPI(seleniumElement, offsets) {
|
|
756
755
|
return this.actions([{
|
|
757
|
-
type:
|
|
758
|
-
id:
|
|
759
|
-
actions: this.getRelativeMoveActions(offsets, seleniumElement)
|
|
756
|
+
type: 'pointer',
|
|
757
|
+
id: 'mouse',
|
|
758
|
+
actions: this.getRelativeMoveActions(offsets, seleniumElement),
|
|
760
759
|
}]).catch(err => {
|
|
761
760
|
logger.error('tried to use element origin but failed because of visibility, trying location', err);
|
|
762
|
-
const point = this.computeAbsoluteMovement(offsets)
|
|
761
|
+
const point = this.computeAbsoluteMovement(offsets);
|
|
763
762
|
return this.moveWithActionsAPI(point);
|
|
764
763
|
});
|
|
765
764
|
}
|
|
@@ -789,14 +788,14 @@ class WebDriver extends WebDriverApi {
|
|
|
789
788
|
const doDrag = this.getMoveActions(xDiff, yDiff, 'pointer', 1);
|
|
790
789
|
const endDrag = this.getClickActions(['pointerUp'], LEFT);
|
|
791
790
|
return this.actions([{
|
|
792
|
-
type:
|
|
793
|
-
id:
|
|
791
|
+
type: 'pointer',
|
|
792
|
+
id: 'mouse',
|
|
794
793
|
actions: goToDrag.concat(startDrag).concat(doDrag).concat(endDrag)
|
|
795
|
-
}])
|
|
794
|
+
}]);
|
|
796
795
|
}
|
|
797
796
|
|
|
798
797
|
drag(seleniumElement, targetRect, xElementOffset, yElementOffset, events) {
|
|
799
|
-
const { width, height } = targetRect
|
|
798
|
+
const { width, height } = targetRect;
|
|
800
799
|
const midXRelative = this.isEdge() ? xElementOffset : (xElementOffset - width / 2 + 1);
|
|
801
800
|
const midYRelative = this.isEdge() ? yElementOffset : (yElementOffset - height / 2);
|
|
802
801
|
return this.getDragCoordinates(events)
|
|
@@ -870,8 +869,8 @@ class WebDriver extends WebDriverApi {
|
|
|
870
869
|
const actions = startMovePositionActions.concat(pointerDownActions).concat(moveSequenceActions).concat(endMovePositionActions).concat(pointerUpActions);
|
|
871
870
|
|
|
872
871
|
return this.actions([{
|
|
873
|
-
type:
|
|
874
|
-
id:
|
|
872
|
+
type: 'pointer',
|
|
873
|
+
id: 'mouse',
|
|
875
874
|
actions: actions
|
|
876
875
|
}]);
|
|
877
876
|
}
|
|
@@ -957,7 +956,7 @@ class WebDriver extends WebDriverApi {
|
|
|
957
956
|
|
|
958
957
|
getElementRect(target) {
|
|
959
958
|
let defaultLocation = { width: 0, height: 0, top: 0, left: 0 };
|
|
960
|
-
return this.getElementLocation(target).catch((err) => logger.error(
|
|
959
|
+
return this.getElementLocation(target).catch((err) => logger.error('error getting element location', { err }))
|
|
961
960
|
.then(loc => {
|
|
962
961
|
if (loc && loc.value) {
|
|
963
962
|
return {
|
|
@@ -972,9 +971,9 @@ class WebDriver extends WebDriverApi {
|
|
|
972
971
|
}
|
|
973
972
|
|
|
974
973
|
end() {
|
|
975
|
-
logger.info(
|
|
974
|
+
logger.info('delete session', { sessionId: this.getSessionId() });
|
|
976
975
|
if (!this.getSessionId()) {
|
|
977
|
-
logger.warn(
|
|
976
|
+
logger.warn('failed to close session because session is undefined');
|
|
978
977
|
}
|
|
979
978
|
clearInterval(this.keepAliveTimer);
|
|
980
979
|
return super.end()
|