@testim/testim-cli 3.255.0 → 3.257.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/cliJsCode/service.js +11 -8
- package/cli.js +2 -2
- package/cliAgentMode.js +7 -7
- package/codim/codim-cli.js +4 -1
- package/commons/featureFlags.js +21 -7
- package/commons/httpRequest.js +1 -1
- package/commons/initializeUserWithAuth.js +7 -4
- package/commons/preloadTests.js +5 -2
- package/commons/prepareRunner.js +5 -5
- package/commons/prepareRunnerAndTestimStartUtils.js +11 -4
- package/commons/runnerFileCache.js +9 -1
- package/commons/testimServicesApi.js +36 -5
- package/npm-shrinkwrap.json +59 -60
- package/package.json +1 -1
- package/player/stepActions/apiStepAction.js +49 -43
- package/player/stepActions/baseCliJsStepAction.js +19 -14
- package/player/stepActions/baseJsStepAction.js +9 -8
- package/player/stepActions/dropFileStepAction.js +1 -3
- package/player/stepActions/inputFileStepAction.js +10 -8
- package/player/stepActions/mouseStepAction.js +21 -22
- package/player/stepActions/nodePackageStepAction.js +34 -35
- package/player/stepActions/stepAction.js +1 -0
- package/player/utils/imageCaptureUtils.js +63 -63
- package/player/utils/screenshotUtils.js +16 -13
- package/player/utils/windowUtils.js +20 -8
- package/processHandler.js +4 -0
- package/runOptions.d.ts +27 -1
- package/runOptions.js +7 -7
- package/runner.js +62 -23
- package/runners/ParallelWorkerManager.js +3 -2
- package/runners/TestPlanRunner.js +9 -6
- package/runners/buildCodeTests.js +1 -0
- package/runners/runnerUtils.js +11 -2
- package/services/branchService.js +11 -5
- package/services/localRCASaver.js +4 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const StepAction = require('./stepAction');
|
|
2
|
-
const Promise = require('bluebird');
|
|
3
2
|
const html5dndAction = require('./scripts/html5dragAction');
|
|
4
3
|
const html5dndActionV2 = require('./scripts/html5dragActionV2');
|
|
5
4
|
const doClickScript = require('./scripts/doClick');
|
|
@@ -30,7 +29,7 @@ class MouseStepAction extends StepAction {
|
|
|
30
29
|
const timeout = context.data.timeToPlayStep + 3000;
|
|
31
30
|
const events = step.events;
|
|
32
31
|
|
|
33
|
-
if (!events
|
|
32
|
+
if (!events?.length) {
|
|
34
33
|
return Promise.resolve();
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -58,12 +57,12 @@ class MouseStepAction extends StepAction {
|
|
|
58
57
|
|
|
59
58
|
return this.driver.executeCodeAsync(doClickCode, timeout, eventParam)
|
|
60
59
|
.then(result => {
|
|
61
|
-
if (result.value
|
|
62
|
-
return
|
|
60
|
+
if (result.value?.success) {
|
|
61
|
+
return { success: true };
|
|
63
62
|
}
|
|
64
|
-
return
|
|
63
|
+
return { success: false };
|
|
65
64
|
})
|
|
66
|
-
.catch(err =>
|
|
65
|
+
.catch(err => ({
|
|
67
66
|
success: false,
|
|
68
67
|
reason: err.message,
|
|
69
68
|
exception: err,
|
|
@@ -75,7 +74,7 @@ class MouseStepAction extends StepAction {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
getEventSequenceOffset() {
|
|
78
|
-
const initialPosition =
|
|
77
|
+
const initialPosition = this.step.events[0]?.pointerPosition;
|
|
79
78
|
if (!initialPosition) {
|
|
80
79
|
return { xOffset: 0, yOffset: 0 };
|
|
81
80
|
}
|
|
@@ -91,7 +90,7 @@ class MouseStepAction extends StepAction {
|
|
|
91
90
|
|
|
92
91
|
addOffsetToEvents(offsetFromElement) {
|
|
93
92
|
this.step.events.forEach(event => {
|
|
94
|
-
if (event
|
|
93
|
+
if (event?.pointerPosition) {
|
|
95
94
|
event.pointerPosition.originX += offsetFromElement.xOffset;
|
|
96
95
|
event.pointerPosition.originY += offsetFromElement.yOffset;
|
|
97
96
|
}
|
|
@@ -113,7 +112,9 @@ class MouseStepAction extends StepAction {
|
|
|
113
112
|
|
|
114
113
|
const { recordPointerMoveEvents = false } = this.context.project.defaults || {};
|
|
115
114
|
const mouseUpEvent = this.step.events.find(event => event.event === 'mouseup') || (recordPointerMoveEvents && this.step.events.find(event => event.event === 'pointerup'));
|
|
116
|
-
const lastMouseMoveEventIndex =
|
|
115
|
+
const lastMouseMoveEventIndex =
|
|
116
|
+
_.findLastIndex(this.step.events, event => event.event === 'mousemove') ||
|
|
117
|
+
(recordPointerMoveEvents && _.findLastIndex(this.step.events, event => event.event === 'pointermove'));
|
|
117
118
|
if (mouseUpEvent && lastMouseMoveEventIndex > 0 && !this.step.allEventsOnSameElement) {
|
|
118
119
|
this.step.events.splice(lastMouseMoveEventIndex + 1, 0, this.generateEventOfType(mouseUpEvent, 'mouseover'));
|
|
119
120
|
}
|
|
@@ -159,12 +160,12 @@ class MouseStepAction extends StepAction {
|
|
|
159
160
|
|
|
160
161
|
return this.driver.executeCodeAsync(doDragPathCode, timeout, eventMessage)
|
|
161
162
|
.then(result => {
|
|
162
|
-
if (result.value
|
|
163
|
-
return
|
|
163
|
+
if (result.value?.success) {
|
|
164
|
+
return { success: true };
|
|
164
165
|
}
|
|
165
|
-
return
|
|
166
|
+
return { success: false };
|
|
166
167
|
})
|
|
167
|
-
.catch(err =>
|
|
168
|
+
.catch(err => ({
|
|
168
169
|
success: false,
|
|
169
170
|
reason: err.message,
|
|
170
171
|
exception: err,
|
|
@@ -173,9 +174,7 @@ class MouseStepAction extends StepAction {
|
|
|
173
174
|
|
|
174
175
|
chooseAndRunAction() {
|
|
175
176
|
const target = this.getTarget();
|
|
176
|
-
const {
|
|
177
|
-
locatedElement, seleniumElement, rectWithoutFrameOffset, rect,
|
|
178
|
-
} = target;
|
|
177
|
+
const { locatedElement, seleniumElement, rectWithoutFrameOffset, rect } = target;
|
|
179
178
|
const { xOffset, yOffset } = this.stepActionUtils.getClickOffset(this.step.element.clickOffset, rectWithoutFrameOffset);
|
|
180
179
|
|
|
181
180
|
// used for fallback native click
|
|
@@ -220,7 +219,7 @@ class MouseStepAction extends StepAction {
|
|
|
220
219
|
if (!isIE && featureFlagService.flags.usePortedHtml5DragDrop.isEnabled()) {
|
|
221
220
|
const events = this.generateHTML5DragEventSequence();
|
|
222
221
|
const timeout = this.context.data.timeToPlayStep + 3000;
|
|
223
|
-
const
|
|
222
|
+
const contextTarget = this.context.data[this.step.targetId || 'targetId'];
|
|
224
223
|
const eventMessage = {
|
|
225
224
|
transactionId: `${this.context.testResultId}:${this.step.id}`,
|
|
226
225
|
id: this.step.id,
|
|
@@ -235,10 +234,10 @@ class MouseStepAction extends StepAction {
|
|
|
235
234
|
isDrag: this.step.isDrag,
|
|
236
235
|
useRecordedMousedown: this.step.useRecordedMousedown,
|
|
237
236
|
allEventsOnSameElement: this.step.allEventsOnSameElement,
|
|
238
|
-
elementToFocusLocatedElement:
|
|
237
|
+
elementToFocusLocatedElement: contextTarget.elementToFocusLocatedElement,
|
|
239
238
|
trackActiveElement: this.step.trackActiveElement,
|
|
240
|
-
locatedElement:
|
|
241
|
-
isRoot:
|
|
239
|
+
locatedElement: contextTarget.locatedElement,
|
|
240
|
+
isRoot: contextTarget.isRoot,
|
|
242
241
|
};
|
|
243
242
|
// hack for Edge (17/18) which does not accept properties with negative (throws Unknown Error)
|
|
244
243
|
// values between 0 and -1 -_-.
|
|
@@ -311,7 +310,7 @@ class MouseStepAction extends StepAction {
|
|
|
311
310
|
}
|
|
312
311
|
|
|
313
312
|
addDragendIfNeeded(events) {
|
|
314
|
-
if (events.
|
|
313
|
+
if (events.some(event => event.event === 'dragend')) {
|
|
315
314
|
return events;
|
|
316
315
|
}
|
|
317
316
|
const dragendDefaultEvent = {
|
|
@@ -325,7 +324,7 @@ class MouseStepAction extends StepAction {
|
|
|
325
324
|
}
|
|
326
325
|
|
|
327
326
|
getToElementPosition() {
|
|
328
|
-
if (!(this.context.data
|
|
327
|
+
if (!(this.context.data?.toElement?.rect)) {
|
|
329
328
|
return undefined;
|
|
330
329
|
}
|
|
331
330
|
const { rect } = this.context.data.toElement;
|
|
@@ -1,47 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
const StepAction = require('./stepAction');
|
|
4
|
-
const
|
|
5
|
-
const {NpmPackageError} = require('../../errors');
|
|
4
|
+
const Bluebird = require('bluebird');
|
|
5
|
+
const { NpmPackageError } = require('../../errors');
|
|
6
6
|
|
|
7
7
|
const service = require('../../agent/routers/cliJsCode/service');
|
|
8
8
|
|
|
9
9
|
class NodePackageStepAction extends StepAction {
|
|
10
|
+
async performAction() {
|
|
11
|
+
const { context } = this;
|
|
12
|
+
const { stepId, packageData, resultId, retryIndex, stepResultId, timeToPlayBeforeExec } = context;
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.catch(NpmPackageError, err => {
|
|
25
|
-
return Promise.resolve({
|
|
26
|
-
success: false,
|
|
27
|
-
code: "invalid-node-package",
|
|
28
|
-
message: err.message
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
.catch(Promise.TimeoutError, () => {
|
|
32
|
-
return Promise.resolve({
|
|
14
|
+
try {
|
|
15
|
+
const data = await service.installPackage(
|
|
16
|
+
stepId,
|
|
17
|
+
resultId,
|
|
18
|
+
retryIndex,
|
|
19
|
+
packageData,
|
|
20
|
+
stepResultId,
|
|
21
|
+
timeToPlayBeforeExec,
|
|
22
|
+
);
|
|
23
|
+
return ({ data, success: true });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err instanceof NpmPackageError) {
|
|
26
|
+
return {
|
|
33
27
|
success: false,
|
|
34
|
-
code:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
code: 'invalid-node-package',
|
|
29
|
+
message: err.message,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (err instanceof Bluebird.TimeoutError) {
|
|
33
|
+
return {
|
|
39
34
|
success: false,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
code: 'timeout',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
reason: err.message,
|
|
41
|
+
exception: err,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -22,6 +22,7 @@ class StepAction {
|
|
|
22
22
|
this.context = context;
|
|
23
23
|
this.frameHandler = frameHandler;
|
|
24
24
|
this.frameId = 0;
|
|
25
|
+
/** @type {import('../utils/stepActionUtils'))} */
|
|
25
26
|
this.stepActionUtils = stepActionUtils;
|
|
26
27
|
this.locateElementPlayer = locateElementPlayer;
|
|
27
28
|
this.exportsGlobal = exportsGlobal;
|
|
@@ -16,16 +16,6 @@ class RectIsOutsideOfImageError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* @param {never} tabId
|
|
21
|
-
* @param {import('./windowUtils')} windowUtils
|
|
22
|
-
* @param {import('./screenshotUtils')} screenshotUtils
|
|
23
|
-
* */
|
|
24
|
-
const ImageCaptureUtils = function (tabId, windowUtils, screenshotUtils) {
|
|
25
|
-
this.windowUtils = windowUtils;
|
|
26
|
-
this.screenshotUtils = screenshotUtils;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
19
|
async function cropImageFromImageData(imageData, imageInfo) {
|
|
30
20
|
const _image = imageInfo.image || imageInfo;
|
|
31
21
|
const pixelRatio = imageInfo.devicePixelRatio;
|
|
@@ -114,6 +104,7 @@ function stitchImage(fullSize, parts) {
|
|
|
114
104
|
return chromeStitchImage(fullSize, parts);
|
|
115
105
|
}
|
|
116
106
|
|
|
107
|
+
// ???????????
|
|
117
108
|
function uploadDataUrl() {
|
|
118
109
|
return Promise.resolve();
|
|
119
110
|
}
|
|
@@ -148,74 +139,80 @@ function getElementAbsoluteRectangle(elementRect, pixelRatio) {
|
|
|
148
139
|
};
|
|
149
140
|
}
|
|
150
141
|
|
|
151
|
-
ImageCaptureUtils
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
142
|
+
class ImageCaptureUtils {
|
|
143
|
+
/**
|
|
144
|
+
* @param {unknown} _unusedId
|
|
145
|
+
* @param {import('./windowUtils')} windowUtils
|
|
146
|
+
* @param {import('./screenshotUtils')} screenshotUtils
|
|
147
|
+
* */
|
|
148
|
+
constructor(_unusedId, windowUtils, screenshotUtils) {
|
|
149
|
+
this.windowUtils = windowUtils;
|
|
150
|
+
this.screenshotUtils = screenshotUtils;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async takeViewPortImage() {
|
|
154
|
+
const imageInfo = await this.screenshotUtils.takeScreenshot();
|
|
155
|
+
return ((typeof imageInfo === 'string') ? imageInfo : imageInfo.image);
|
|
156
|
+
}
|
|
156
157
|
|
|
157
158
|
takeImageForComparison() {
|
|
158
159
|
return this.takeViewPortImage();
|
|
159
|
-
}
|
|
160
|
+
}
|
|
160
161
|
|
|
161
|
-
takeAreaDataUrl(areas, format) {
|
|
162
|
+
async takeAreaDataUrl(areas, format) {
|
|
162
163
|
// Future changes in clickim will pass parameters to this function as a single object
|
|
163
164
|
if (areas.areas) {
|
|
164
165
|
areas = areas.areas;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
},
|
|
168
|
+
const imageInfo = await this.screenshotUtils.takeScreenshot(format);
|
|
169
|
+
const result = await cropImageFromImageData(areas, imageInfo);
|
|
170
|
+
result.screenImage = imageInfo.image;
|
|
171
|
+
result.absoluteScreenHighlight = getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio);
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
174
|
|
|
175
|
-
takeArea(areas) {
|
|
175
|
+
async takeArea(areas) {
|
|
176
176
|
// Future changes in clickim will pass parameters to this function as a single object
|
|
177
177
|
if (areas.areas) {
|
|
178
178
|
areas = areas.areas;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}).then(uploadAllDataUrls);
|
|
188
|
-
},
|
|
181
|
+
const imageInfo = await this.screenshotUtils.takeScreenshot();
|
|
182
|
+
return uploadAllDataUrls({
|
|
183
|
+
screenImage: imageInfo.image,
|
|
184
|
+
absoluteScreenHighlight: getElementAbsoluteRectangle(areas.elementRect, imageInfo.devicePixelRatio),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
189
187
|
|
|
190
188
|
forcePixelRatio(forceRatio) {
|
|
191
189
|
return this.screenshotUtils.forcePixelRatio(forceRatio);
|
|
192
|
-
}
|
|
190
|
+
}
|
|
193
191
|
|
|
194
192
|
getCurrentDevicePixelRatio() {
|
|
195
193
|
return this.screenshotUtils.getCurrentDevicePixelRatio();
|
|
196
|
-
}
|
|
194
|
+
}
|
|
197
195
|
|
|
198
196
|
async takeStitchedDataUrl(useImprovedScreenshotStitching) {
|
|
199
|
-
const
|
|
200
|
-
const getCurrentScrollPosition = windowUtil.getCurrentScrollPosition.bind(windowUtil);
|
|
197
|
+
const { windowUtils, screenshotUtils } = this;
|
|
201
198
|
|
|
202
|
-
const that = this;
|
|
203
199
|
const stabilize = () => utils.delay(250);
|
|
204
200
|
const usingImprovedStitching = Boolean(useImprovedScreenshotStitching);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
201
|
+
|
|
202
|
+
async function createPart(position, crop) {
|
|
203
|
+
if (usingImprovedStitching) {
|
|
204
|
+
await windowUtils.scrollToPositionWithoutAnimation(position);
|
|
205
|
+
} else {
|
|
206
|
+
await windowUtils.scrollToPosition(position);
|
|
207
|
+
}
|
|
208
|
+
await stabilize();
|
|
209
|
+
const imageInfo = await screenshotUtils.takeScreenshot();
|
|
210
|
+
const cropResult = await cropImageFromImageData({ elementRect: crop }, imageInfo);
|
|
211
|
+
return ({
|
|
212
|
+
position: { left: position.x + crop.left, top: position.y + crop.top },
|
|
213
|
+
size: { width: crop.width, height: crop.height },
|
|
214
|
+
image: cropResult.elementImage,
|
|
215
|
+
});
|
|
219
216
|
}
|
|
220
217
|
|
|
221
218
|
async function takeAllParts(positionsData) {
|
|
@@ -232,36 +229,39 @@ ImageCaptureUtils.prototype = {
|
|
|
232
229
|
const VPW = viewPortSize.width;
|
|
233
230
|
const FPH = Math.max(fullPageSize.height, viewPortSize.height);
|
|
234
231
|
const VPH = viewPortSize.height;
|
|
235
|
-
const Ws =
|
|
232
|
+
const Ws = Array.from({ length: Math.ceil(FPW / VPW) }, (__, i) => ({
|
|
236
233
|
scrollX: Math.min(i * VPW, FPW - VPW),
|
|
237
234
|
cropX: i * VPW - Math.min(i * VPW, FPW - VPW),
|
|
238
235
|
cropW: VPW - (i * VPW - Math.min(i * VPW, FPW - VPW)),
|
|
239
236
|
}));
|
|
240
|
-
const Hs =
|
|
237
|
+
const Hs = Array.from({ length: Math.ceil(FPH / VPH) }, (__, i) => ({
|
|
241
238
|
scrollY: Math.min(i * VPH, FPH - VPH),
|
|
242
239
|
cropY: i * VPH - Math.min(i * VPH, FPH - VPH),
|
|
243
240
|
cropH: VPH - (i * VPH - Math.min(i * VPH, FPH - VPH)),
|
|
244
241
|
}));
|
|
245
|
-
|
|
242
|
+
return Ws.flatMap(w => Hs.map(h => ({
|
|
246
243
|
scrollPos: { x: w.scrollX, y: h.scrollY },
|
|
247
244
|
cropData: {
|
|
248
|
-
top: h.cropY,
|
|
245
|
+
top: h.cropY,
|
|
246
|
+
left: w.cropX,
|
|
247
|
+
width: w.cropW,
|
|
248
|
+
height: h.cropH,
|
|
249
249
|
},
|
|
250
|
-
}))
|
|
251
|
-
|
|
250
|
+
}))
|
|
251
|
+
);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
async function createStitchImage(fullPageSize, viewPortSize) {
|
|
255
|
-
const originalPosition = await getCurrentScrollPosition();
|
|
255
|
+
const originalPosition = await windowUtils.getCurrentScrollPosition();
|
|
256
256
|
const positions = getPartsPositions(fullPageSize, viewPortSize);
|
|
257
257
|
const parts = await takeAllParts(positions);
|
|
258
|
-
await
|
|
258
|
+
await windowUtils.scrollToPosition(originalPosition);
|
|
259
259
|
return stitchImage(fullPageSize, parts);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
const [fullPageSize, viewPortSize] = await Promise.all([
|
|
262
|
+
const [fullPageSize, viewPortSize] = await Promise.all([windowUtils.getFullPageSize(), windowUtils.getViewportSize()]);
|
|
263
263
|
return await createStitchImage(fullPageSize, viewPortSize);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
266
|
|
|
267
267
|
module.exports = ImageCaptureUtils;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const Promise = require('bluebird');
|
|
4
6
|
const pRetry = require('p-retry');
|
|
5
7
|
|
|
6
8
|
class ScreenshotUtils {
|
|
9
|
+
/**
|
|
10
|
+
* @param {any} tabId
|
|
11
|
+
* @param {import('../webdriver')} driver
|
|
12
|
+
*/
|
|
7
13
|
constructor(tabId, driver, options = { takeScreenshots: true }) {
|
|
8
14
|
this.tabId = tabId;
|
|
9
15
|
this.driver = driver;
|
|
@@ -21,23 +27,21 @@ class ScreenshotUtils {
|
|
|
21
27
|
return this.options.takeScreenshots;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
takeScreenshot() {
|
|
30
|
+
async takeScreenshot() {
|
|
25
31
|
if (!this.shouldTakeScreenshots()) {
|
|
26
|
-
return
|
|
32
|
+
return { devicePixelRatio: 1, image: '' };
|
|
27
33
|
}
|
|
28
34
|
const MAX_RETRY_COUNT = 3;
|
|
29
35
|
const SCREENSHOT_RETRY_DELAY = 2000;
|
|
30
36
|
const devicePixelRatioPromise = this.currentDevicePixelRatio ? Promise.resolve(this.currentDevicePixelRatio) : this.getDevicePixelRatio();
|
|
31
37
|
const getScreenshot = () => Promise.all([devicePixelRatioPromise, this.driver.takeScreenshot()]);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
});
|
|
38
|
+
const [devicePixelRatio, image] = await pRetry(getScreenshot, { retries: MAX_RETRY_COUNT, minTimeout: SCREENSHOT_RETRY_DELAY });
|
|
39
|
+
const base64 = image ? image.value : '';
|
|
40
|
+
const dataUrl = `data:image/png;base64,${this.base64AddPadding(base64.replace(/[\r\n]/g, ''))}`;
|
|
41
|
+
return {
|
|
42
|
+
image: dataUrl,
|
|
43
|
+
devicePixelRatio,
|
|
44
|
+
};
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
getDevicePixelRatio() {
|
|
@@ -49,7 +53,7 @@ class ScreenshotUtils {
|
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
return this.driver.executeJS(devicePixelRatioJS).then(result =>
|
|
56
|
+
return this.driver.executeJS(devicePixelRatioJS).then(result => result.value);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
forcePixelRatio(forceRatio = 1) {
|
|
@@ -63,4 +67,3 @@ class ScreenshotUtils {
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
module.exports = ScreenshotUtils;
|
|
66
|
-
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const Promise = require('bluebird');
|
|
@@ -7,6 +9,10 @@ const { PageNotAvailableError } = require('../../errors');
|
|
|
7
9
|
const logger = require('../../commons/logger').getLogger('window-utils');
|
|
8
10
|
|
|
9
11
|
class WindowUtils {
|
|
12
|
+
/**
|
|
13
|
+
* @param {number} id tab or worker id
|
|
14
|
+
* @param {import('../webdriver')} driver
|
|
15
|
+
* */
|
|
10
16
|
constructor(id, driver) {
|
|
11
17
|
this.id = id;
|
|
12
18
|
this.driver = driver;
|
|
@@ -131,12 +137,12 @@ class WindowUtils {
|
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
setViewportSize(size) {
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
async setViewportSize(size) {
|
|
141
|
+
await this.driver.setViewportSize(size.width, size.height);
|
|
142
|
+
return await this.checkSize(size);
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
validatePageIsAvailable() {
|
|
145
|
+
async validatePageIsAvailable() {
|
|
140
146
|
/* eslint-disable */
|
|
141
147
|
function pageIsAvailable() {
|
|
142
148
|
var locationObj;
|
|
@@ -153,7 +159,8 @@ class WindowUtils {
|
|
|
153
159
|
}
|
|
154
160
|
/* eslint-enable */
|
|
155
161
|
|
|
156
|
-
|
|
162
|
+
const result = await this.driver.executeJS(pageIsAvailable);
|
|
163
|
+
return await (result.value ? Promise.resolve() : Promise.reject(new PageNotAvailableError()));
|
|
157
164
|
}
|
|
158
165
|
|
|
159
166
|
focusTab() {
|
|
@@ -164,9 +171,14 @@ class WindowUtils {
|
|
|
164
171
|
return undefined;
|
|
165
172
|
}
|
|
166
173
|
|
|
167
|
-
getOsAndBrowser() {
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
async getOsAndBrowser() {
|
|
175
|
+
const osAndBrowser = await pRetry(() => this.driver.getBrowserAndOS(), { retries: 3 });
|
|
176
|
+
return {
|
|
177
|
+
uaBrowserName: osAndBrowser.browser,
|
|
178
|
+
uaOs: osAndBrowser.os,
|
|
179
|
+
userAgent: osAndBrowser.userAgent,
|
|
180
|
+
browserVersion: osAndBrowser.browserVersion,
|
|
181
|
+
};
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
getUserAgentInfo() {
|
package/processHandler.js
CHANGED
|
@@ -7,6 +7,10 @@ const logger = require('./commons/logger').getLogger('process-handler');
|
|
|
7
7
|
|
|
8
8
|
const exitHooks = [];
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {(e: any) => void} onExit
|
|
12
|
+
* @param {NodeJS.Process=} _process
|
|
13
|
+
*/
|
|
10
14
|
module.exports = function (onExit, _process = process) {
|
|
11
15
|
async function cleanup(err) {
|
|
12
16
|
// give cleanup and socket reports a chance to run
|
package/runOptions.d.ts
CHANGED
|
@@ -70,6 +70,11 @@ interface LightweightSettings {
|
|
|
70
70
|
disableProjectDefaults: boolean;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
type InitUserWithAuth = Awaited<ReturnType<typeof import('./commons/testimServicesApi')['initializeUserWithAuth']>>;
|
|
74
|
+
interface ProjectData extends Pick<InitUserWithAuth['projectById'], 'type' | 'name' | 'defaults'> {
|
|
75
|
+
projectId: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
73
78
|
// TODO: consider typing better, based on the validations we do (example: must have one of grid/grid-id/host&port/test plan)
|
|
74
79
|
interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | 'tunnel'>> {
|
|
75
80
|
token: string;
|
|
@@ -103,6 +108,26 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
|
|
|
103
108
|
retentionDays?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
|
104
109
|
retries?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20;
|
|
105
110
|
|
|
111
|
+
// #region Data set based on values from server
|
|
112
|
+
company: {
|
|
113
|
+
companyId: string;
|
|
114
|
+
onprem: boolean;
|
|
115
|
+
storageBaseUrl: string;
|
|
116
|
+
storageType: string;
|
|
117
|
+
name: string;
|
|
118
|
+
planType: 'pro' | 'free' | 'trial';
|
|
119
|
+
isPOC: boolean;
|
|
120
|
+
isStartUp: boolean;
|
|
121
|
+
activePlan: import('services/src/commons/mongo/db-dto-definitions/DbCompanyProjects').Plan;
|
|
122
|
+
};
|
|
123
|
+
gridData?: Awaited<ReturnType<typeof import('./services/gridService')['getGridData']>>;
|
|
124
|
+
sfdcCredential?: string;
|
|
125
|
+
editorUrl?: string;
|
|
126
|
+
allGrids?: InitUserWithAuth['allGrids'];
|
|
127
|
+
authData?: InitUserWithAuth['authData'];
|
|
128
|
+
projectData?: ProjectData;
|
|
129
|
+
// #endregion
|
|
130
|
+
|
|
106
131
|
// #region What to execute
|
|
107
132
|
testId: string[];
|
|
108
133
|
name: string[];
|
|
@@ -161,7 +186,7 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
|
|
|
161
186
|
|
|
162
187
|
// #region Extension debugging
|
|
163
188
|
ext?: string;
|
|
164
|
-
extensionLocation
|
|
189
|
+
extensionLocation: string[];
|
|
165
190
|
extensionPath?: string;
|
|
166
191
|
// #endregion
|
|
167
192
|
|
|
@@ -243,6 +268,7 @@ interface RunnerOptions extends Partial<Omit<TunnelOptions, 'tunnelOnlyMode' | '
|
|
|
243
268
|
lightweightMode?: LightweightSettings;
|
|
244
269
|
createPrefechedData?: boolean;
|
|
245
270
|
saveRCALocally?: boolean | string;
|
|
271
|
+
localRCASaver?: string;
|
|
246
272
|
// #endregion
|
|
247
273
|
|
|
248
274
|
// #region intersections
|
package/runOptions.js
CHANGED
|
@@ -804,7 +804,7 @@ module.exports = {
|
|
|
804
804
|
|
|
805
805
|
program.port = program.port && Number(program.port);
|
|
806
806
|
|
|
807
|
-
if (program.retries <= 0 ||
|
|
807
|
+
if (program.retries <= 0 || Number.isNaN(program.retries)) {
|
|
808
808
|
throw new ArgError('test failure retry count could not be a negative number or string, --retries <max_num_of_retries>');
|
|
809
809
|
}
|
|
810
810
|
|
|
@@ -823,27 +823,27 @@ module.exports = {
|
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
825
|
|
|
826
|
-
if (program.browserTimeout <= 0 ||
|
|
826
|
+
if (program.browserTimeout <= 0 || Number.isNaN(program.browserTimeout)) {
|
|
827
827
|
throw new ArgError('get browser timeout could not be a negative number, --browser-timeout <get-browser-timeout>');
|
|
828
828
|
}
|
|
829
829
|
|
|
830
|
-
if (program.newBrowserWaitTimeout <= 0 ||
|
|
830
|
+
if (program.newBrowserWaitTimeout <= 0 || Number.isNaN(program.newBrowserWaitTimeout)) {
|
|
831
831
|
throw new ArgError('max new browser wait timeout could not be a negative number, --new-browser-wait-timeout <max-wait-to-browser>');
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
-
if (program.timeout <= 0 ||
|
|
834
|
+
if (program.timeout <= 0 || Number.isNaN(program.timeout)) {
|
|
835
835
|
throw new ArgError('test run timeout could not be a negative number, --timeout <run-timeout>');
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
-
if (program.beforeParallel <= 0 ||
|
|
838
|
+
if (program.beforeParallel <= 0 || Number.isNaN(program.beforeParallel)) {
|
|
839
839
|
throw new ArgError('before-parallel could not be a negative number or not number, --before-parallel <number-of-tests>');
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
-
if (program.parallel <= 0 ||
|
|
842
|
+
if (program.parallel <= 0 || Number.isNaN(program.parallel)) {
|
|
843
843
|
throw new ArgError('parallel could not be a negative number or not number, --parallel <number-of-tests>');
|
|
844
844
|
}
|
|
845
845
|
|
|
846
|
-
if (program.afterParallel <= 0 ||
|
|
846
|
+
if (program.afterParallel <= 0 || Number.isNaN(program.afterParallel)) {
|
|
847
847
|
throw new ArgError('after-parallel could not be a negative number or not number, --after-parallel <number-of-tests>');
|
|
848
848
|
}
|
|
849
849
|
|