@mablhq/mabl-cli 2.11.6 → 2.12.7
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/browserLauncher/playwrightBrowserLauncher/playwrightBrowser.js +2 -2
- package/browserLauncher/playwrightBrowserLauncher/playwrightPage.js +0 -3
- package/commands/tests/testsUtil.js +1 -0
- package/execution/index.js +1 -1
- package/mablscript/mobile/steps/EnterTextStep.js +3 -0
- package/mablscript/mobile/steps/stepUtil.js +51 -12
- package/mablscript/mobile/tests/steps/GeneralHumanization.mobiletest.js +144 -7
- package/package.json +1 -1
- package/resources/pdf-viewer/embeddedPdfDetection.js +22 -6
- package/util/downloadUtil.js +1 -1
|
@@ -17,6 +17,9 @@ class EnterTextStep extends MablStepV2_1.MablStepV2 {
|
|
|
17
17
|
return vars;
|
|
18
18
|
}
|
|
19
19
|
stepDescription() {
|
|
20
|
+
if ((0, stepUtil_1.isPasswordField)(this.descriptor.find)) {
|
|
21
|
+
return (0, stepUtil_1.getDescriptionForMobileStepDescriptor)(`Enter password in`, this.descriptor);
|
|
22
|
+
}
|
|
20
23
|
return (0, stepUtil_1.getDescriptionForMobileStepDescriptor)(`Enter text "${this.descriptor.text}" in`, this.descriptor);
|
|
21
24
|
}
|
|
22
25
|
static fromYaml(_stepName, stepDescriptor) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isStepWithFind = exports.humanizeCustomFind = exports.getElementDescriptionFromMobileFindDescriptor = exports.getDescriptionForMobileStepDescriptor = exports.getSimpleIOSElementName = exports.getSimpleAndroidElementName = void 0;
|
|
3
|
+
exports.isPasswordField = exports.isStepWithFind = exports.humanizeCustomFind = exports.getElementDescriptionFromMobileFindDescriptor = exports.getDescriptionForMobileStepDescriptor = exports.getSimpleIOSElementName = exports.getSimpleAndroidElementName = void 0;
|
|
4
4
|
const domUtil_1 = require("../../../domUtil");
|
|
5
5
|
const mablscriptFind_1 = require("../../../mablscriptFind");
|
|
6
6
|
function getSimpleAndroidElementName(className) {
|
|
@@ -35,24 +35,49 @@ function getElementDescriptionFromMobileFindDescriptor(descriptor) {
|
|
|
35
35
|
}
|
|
36
36
|
const selector = findTarget.selector;
|
|
37
37
|
if (selector.android && Object.keys(selector.android).length) {
|
|
38
|
-
|
|
39
|
-
return `element with text "${(0, domUtil_1.normalizeText)(selector.android.text)}"`;
|
|
40
|
-
}
|
|
41
|
-
return `"${getSimpleAndroidElementName(selector.android.class)}" element`;
|
|
38
|
+
return buildElementDescriptionForAndroid(findTarget);
|
|
42
39
|
}
|
|
43
40
|
else if (selector.iOS) {
|
|
44
|
-
|
|
45
|
-
return `"${selector.iOS.name}" element`;
|
|
46
|
-
}
|
|
47
|
-
if (selector.iOS.label) {
|
|
48
|
-
return `element with text "${(0, domUtil_1.normalizeText)(selector.iOS.label)}"`;
|
|
49
|
-
}
|
|
50
|
-
return `the "${getSimpleIOSElementName(selector.iOS.type)}" element`;
|
|
41
|
+
return buildElementDescriptionForiOS(findTarget);
|
|
51
42
|
}
|
|
52
43
|
}
|
|
53
44
|
return 'unknown element';
|
|
54
45
|
}
|
|
55
46
|
exports.getElementDescriptionFromMobileFindDescriptor = getElementDescriptionFromMobileFindDescriptor;
|
|
47
|
+
function buildElementDescriptionForAndroid(findTarget) {
|
|
48
|
+
var _a, _b, _c;
|
|
49
|
+
const selector = findTarget.selector;
|
|
50
|
+
if (!selector.android) {
|
|
51
|
+
return `unknown android element`;
|
|
52
|
+
}
|
|
53
|
+
const description = `"${getSimpleAndroidElementName(selector.android.class)}" element`;
|
|
54
|
+
if ((_a = selector.android) === null || _a === void 0 ? void 0 : _a.hint) {
|
|
55
|
+
return `${description} with hint "${(0, domUtil_1.normalizeText)(selector.android.hint)}"`;
|
|
56
|
+
}
|
|
57
|
+
if ((_b = selector.android) === null || _b === void 0 ? void 0 : _b.text) {
|
|
58
|
+
return `${description} with text "${(0, domUtil_1.normalizeText)(selector.android.text)}"`;
|
|
59
|
+
}
|
|
60
|
+
if ((_c = selector.android) === null || _c === void 0 ? void 0 : _c.contentDesc) {
|
|
61
|
+
return `${description} with content-desc "${(0, domUtil_1.normalizeText)(selector.android.contentDesc)}"`;
|
|
62
|
+
}
|
|
63
|
+
return description;
|
|
64
|
+
}
|
|
65
|
+
function buildElementDescriptionForiOS(findTarget) {
|
|
66
|
+
const selector = findTarget.selector;
|
|
67
|
+
if (!selector.iOS) {
|
|
68
|
+
return `unknown iOS element`;
|
|
69
|
+
}
|
|
70
|
+
if (selector.iOS.name) {
|
|
71
|
+
return `"${selector.iOS.name}" element`;
|
|
72
|
+
}
|
|
73
|
+
const simpleDescription = selector.iOS.type
|
|
74
|
+
? `"${getSimpleIOSElementName(selector.iOS.type)}" element`
|
|
75
|
+
: `element`;
|
|
76
|
+
if (selector.iOS.label) {
|
|
77
|
+
return `${simpleDescription} with label "${(0, domUtil_1.normalizeText)(selector.iOS.label)}"`;
|
|
78
|
+
}
|
|
79
|
+
return simpleDescription;
|
|
80
|
+
}
|
|
56
81
|
function getDescriptionForWebViewFindDescriptor(webViewElementDescriptor) {
|
|
57
82
|
if ((0, domUtil_1.isFindElementDescriptor)(webViewElementDescriptor.webView.descriptor)) {
|
|
58
83
|
return (0, mablscriptFind_1.humanizeFindOneDescriptor)(webViewElementDescriptor.webView.descriptor.findTarget);
|
|
@@ -72,3 +97,17 @@ function isStepWithFind(value) {
|
|
|
72
97
|
return !!(value === null || value === void 0 ? void 0 : value.find);
|
|
73
98
|
}
|
|
74
99
|
exports.isStepWithFind = isStepWithFind;
|
|
100
|
+
function isPasswordField(descriptor) {
|
|
101
|
+
var _a, _b;
|
|
102
|
+
if ((0, domUtil_1.isMobileFindSpecification)(descriptor === null || descriptor === void 0 ? void 0 : descriptor.findTarget)) {
|
|
103
|
+
if (((_a = descriptor.findTarget.selector.android) === null || _a === void 0 ? void 0 : _a.password) === true) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (((_b = descriptor.findTarget.selector.iOS) === null || _b === void 0 ? void 0 : _b.type) ===
|
|
107
|
+
'XCUIElementTypeSecureTextField') {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
exports.isPasswordField = isPasswordField;
|
|
@@ -41,7 +41,7 @@ describe('Humanization of steps work', () => {
|
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
44
|
-
expect(step.stepDescription()).toEqual(`Tap on element with
|
|
44
|
+
expect(step.stepDescription()).toEqual(`Tap on "Button" element with label "Click here for more"`);
|
|
45
45
|
});
|
|
46
46
|
it('Humanizes an iOS Tap step properly when only type is available', () => {
|
|
47
47
|
const stepYaml = {
|
|
@@ -61,7 +61,7 @@ describe('Humanization of steps work', () => {
|
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
64
|
-
expect(step.stepDescription()).toEqual(`Tap on
|
|
64
|
+
expect(step.stepDescription()).toEqual(`Tap on "Button" element`);
|
|
65
65
|
});
|
|
66
66
|
it('Humanizes an android Tap step properly when text is available', () => {
|
|
67
67
|
const stepYaml = {
|
|
@@ -82,7 +82,7 @@ describe('Humanization of steps work', () => {
|
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
85
|
-
expect(step.stepDescription()).toEqual(`Tap on element with text "submit form"`);
|
|
85
|
+
expect(step.stepDescription()).toEqual(`Tap on "Button" element with text "submit form"`);
|
|
86
86
|
});
|
|
87
87
|
it('Humanizes an android Tap step properly when only class is available', () => {
|
|
88
88
|
const stepYaml = {
|
|
@@ -133,7 +133,7 @@ describe('Humanization of steps work', () => {
|
|
|
133
133
|
findTarget: {
|
|
134
134
|
selector: {
|
|
135
135
|
iOS: {
|
|
136
|
-
type: '
|
|
136
|
+
type: 'XCUIElementTypeTextField',
|
|
137
137
|
label: 'add name here',
|
|
138
138
|
},
|
|
139
139
|
},
|
|
@@ -142,7 +142,7 @@ describe('Humanization of steps work', () => {
|
|
|
142
142
|
},
|
|
143
143
|
};
|
|
144
144
|
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
145
|
-
expect(step.stepDescription()).toEqual(`Enter text "Bailey" in element with
|
|
145
|
+
expect(step.stepDescription()).toEqual(`Enter text "Bailey" in "TextField" element with label "add name here"`);
|
|
146
146
|
});
|
|
147
147
|
it('Humanizes an android Enter Text step properly using android text', () => {
|
|
148
148
|
const stepYaml = {
|
|
@@ -153,7 +153,7 @@ describe('Humanization of steps work', () => {
|
|
|
153
153
|
findTarget: {
|
|
154
154
|
selector: {
|
|
155
155
|
android: {
|
|
156
|
-
class: 'android.widget.
|
|
156
|
+
class: 'android.widget.EditText',
|
|
157
157
|
text: 'submit form',
|
|
158
158
|
},
|
|
159
159
|
},
|
|
@@ -162,6 +162,143 @@ describe('Humanization of steps work', () => {
|
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
165
|
-
expect(step.stepDescription()).toEqual(`Enter text "Bailey" in element with text "submit form"`);
|
|
165
|
+
expect(step.stepDescription()).toEqual(`Enter text "Bailey" in "EditText" element with text "submit form"`);
|
|
166
|
+
});
|
|
167
|
+
it('humanizes an android button with contentDesc attribute', () => {
|
|
168
|
+
const stepYaml = {
|
|
169
|
+
Tap: {
|
|
170
|
+
find: {
|
|
171
|
+
findType: domUtil_1.FindType.FIND_ONE,
|
|
172
|
+
findTarget: {
|
|
173
|
+
selector: {
|
|
174
|
+
android: {
|
|
175
|
+
displayed: true,
|
|
176
|
+
resourceId: '',
|
|
177
|
+
package: 'com.polar.android.debug',
|
|
178
|
+
checkable: false,
|
|
179
|
+
clickable: true,
|
|
180
|
+
index: 0,
|
|
181
|
+
focusable: true,
|
|
182
|
+
alternateXPaths: [
|
|
183
|
+
'//android.widget.Button[@content-desc="MySeltzer"',
|
|
184
|
+
],
|
|
185
|
+
contentDesc: 'MySeltzer',
|
|
186
|
+
enabled: true,
|
|
187
|
+
longClickable: false,
|
|
188
|
+
password: false,
|
|
189
|
+
xpath: '//hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.Button',
|
|
190
|
+
bounds: '[42,616][465,784]',
|
|
191
|
+
focused: false,
|
|
192
|
+
checked: false,
|
|
193
|
+
text: '',
|
|
194
|
+
class: 'android.widget.Button',
|
|
195
|
+
scrollable: false,
|
|
196
|
+
selected: false,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
204
|
+
expect(step.stepDescription()).toEqual(`Tap on "Button" element with content-desc "MySeltzer"`);
|
|
205
|
+
});
|
|
206
|
+
it('humanizes an android text field that contains a password', () => {
|
|
207
|
+
const stepYaml = {
|
|
208
|
+
EnterText: {
|
|
209
|
+
text: 'superSecretClassified',
|
|
210
|
+
find: {
|
|
211
|
+
findType: domUtil_1.FindType.FIND_ONE,
|
|
212
|
+
findTarget: {
|
|
213
|
+
selector: {
|
|
214
|
+
android: {
|
|
215
|
+
displayed: true,
|
|
216
|
+
resourceId: '',
|
|
217
|
+
package: 'com.polar.android.debug',
|
|
218
|
+
checkable: false,
|
|
219
|
+
clickable: true,
|
|
220
|
+
index: 0,
|
|
221
|
+
focusable: true,
|
|
222
|
+
alternateXPaths: [],
|
|
223
|
+
enabled: true,
|
|
224
|
+
longClickable: false,
|
|
225
|
+
password: true,
|
|
226
|
+
xpath: '//hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.Button',
|
|
227
|
+
bounds: '[42,616][465,784]',
|
|
228
|
+
focused: false,
|
|
229
|
+
checked: false,
|
|
230
|
+
text: '',
|
|
231
|
+
class: 'android.widget.EditText',
|
|
232
|
+
scrollable: false,
|
|
233
|
+
selected: false,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
241
|
+
expect(step.stepDescription()).toEqual(`Enter password in "EditText" element`);
|
|
242
|
+
});
|
|
243
|
+
it('humanizes an iOS text field that contains a password', () => {
|
|
244
|
+
const stepYaml = {
|
|
245
|
+
EnterText: {
|
|
246
|
+
text: 'iWillNeverTell',
|
|
247
|
+
find: {
|
|
248
|
+
findType: domUtil_1.FindType.FIND_ONE,
|
|
249
|
+
findTarget: {
|
|
250
|
+
selector: {
|
|
251
|
+
android: {},
|
|
252
|
+
iOS: {
|
|
253
|
+
type: 'XCUIElementTypeSecureTextField',
|
|
254
|
+
name: 'Password field',
|
|
255
|
+
label: 'Password field',
|
|
256
|
+
enabled: true,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
type: 'tap',
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
265
|
+
expect(step.stepDescription()).toEqual(`Enter password in "Password field" element`);
|
|
266
|
+
});
|
|
267
|
+
it('humanizes an android text field that has a hint attribute', () => {
|
|
268
|
+
const stepYaml = {
|
|
269
|
+
Tap: {
|
|
270
|
+
find: {
|
|
271
|
+
findType: domUtil_1.FindType.FIND_ONE,
|
|
272
|
+
findTarget: {
|
|
273
|
+
selector: {
|
|
274
|
+
android: {
|
|
275
|
+
displayed: true,
|
|
276
|
+
resourceId: '',
|
|
277
|
+
package: 'com.polar.android.debug',
|
|
278
|
+
checkable: false,
|
|
279
|
+
clickable: true,
|
|
280
|
+
index: 0,
|
|
281
|
+
focusable: true,
|
|
282
|
+
alternateXPaths: [],
|
|
283
|
+
enabled: true,
|
|
284
|
+
longClickable: false,
|
|
285
|
+
password: false,
|
|
286
|
+
hint: 'Search for the things',
|
|
287
|
+
xpath: '//hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.EditText',
|
|
288
|
+
bounds: '[42,616][465,784]',
|
|
289
|
+
focused: false,
|
|
290
|
+
checked: false,
|
|
291
|
+
text: '',
|
|
292
|
+
class: 'android.widget.EditText',
|
|
293
|
+
scrollable: false,
|
|
294
|
+
selected: false,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
const step = (0, StepTestsUtil_1.loadRawStepIntoMablStep)(stepYaml);
|
|
302
|
+
expect(step.stepDescription()).toEqual(`Tap on "EditText" element with hint "Search for the things"`);
|
|
166
303
|
});
|
|
167
304
|
});
|
package/package.json
CHANGED
|
@@ -117,7 +117,7 @@ mablEmbeddedPdfDetector = () => {
|
|
|
117
117
|
const pdfUrl = pdfElement.src || pdfElement.data;
|
|
118
118
|
const pdfFileName = mablGetPdfFilenameFromUrl(pdfUrl);
|
|
119
119
|
|
|
120
|
-
const embeddedPdfMablId =
|
|
120
|
+
const embeddedPdfMablId = createEmbeddedPdfMablId(pdfFileName);
|
|
121
121
|
// Mark the embedded PDF element so that we can find it to replace it later
|
|
122
122
|
pdfElement.setAttribute(
|
|
123
123
|
MABL_EMBEDDED_PDF_DETECTED_FLAG,
|
|
@@ -125,7 +125,7 @@ mablEmbeddedPdfDetector = () => {
|
|
|
125
125
|
);
|
|
126
126
|
|
|
127
127
|
// Signal embedded PDF detection and pending download
|
|
128
|
-
// Wait for PDF
|
|
128
|
+
// Wait for PDF event be acknowledged by PageFrameContextTracker before continuing
|
|
129
129
|
await window.dispatchMablEvent({
|
|
130
130
|
type: MESSAGE_TYPE_EMBEDDED_PDF_DETECTED,
|
|
131
131
|
baseUri,
|
|
@@ -133,6 +133,8 @@ mablEmbeddedPdfDetector = () => {
|
|
|
133
133
|
pdfFileName,
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
mablDownloadEmbeddedPdf(pdfFileName, pdfUrl);
|
|
137
|
+
|
|
136
138
|
// Trigger replacement of the embedded PDF with an interactable version served by PDF server
|
|
137
139
|
window.dispatchMablEvent({
|
|
138
140
|
type: MESSAGE_TYPE_EMBEDDED_PDF_MARKED,
|
|
@@ -168,10 +170,6 @@ mablEmbeddedPdfDetector = () => {
|
|
|
168
170
|
}
|
|
169
171
|
return true;
|
|
170
172
|
}
|
|
171
|
-
|
|
172
|
-
function idSanitize(originalId) {
|
|
173
|
-
return originalId && originalId.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
174
|
-
}
|
|
175
173
|
};
|
|
176
174
|
|
|
177
175
|
const CHECK_INTERVAL_IN_MILLIS = 300;
|
|
@@ -180,6 +178,14 @@ let checkTimer;
|
|
|
180
178
|
const checkStartTime = Date.now();
|
|
181
179
|
const MABL_DEFAULT_PDF_NAME = 'mablPdf';
|
|
182
180
|
|
|
181
|
+
function mablPdfIdSanitize(originalId) {
|
|
182
|
+
return originalId && originalId.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function createEmbeddedPdfMablId(pdfFileName) {
|
|
186
|
+
return '__mabl_pdf_viewer_' + mablPdfIdSanitize(pdfFileName);
|
|
187
|
+
}
|
|
188
|
+
|
|
183
189
|
function documentHasDocType() {
|
|
184
190
|
return document.doctype && document.doctype.name;
|
|
185
191
|
}
|
|
@@ -200,6 +206,16 @@ function mablGetPdfFilenameFromUrl(pdfUrl) {
|
|
|
200
206
|
return pdfUrl.startsWith('blob:') ? 'blob_pdf' : pdfFileName;
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
function mablDownloadEmbeddedPdf(fileName, pdfUrl) {
|
|
210
|
+
const aTag = document.createElement('a');
|
|
211
|
+
aTag.style.display = 'none';
|
|
212
|
+
document.body.appendChild(aTag);
|
|
213
|
+
aTag.href = pdfUrl;
|
|
214
|
+
aTag.setAttribute('download', fileName);
|
|
215
|
+
aTag.click();
|
|
216
|
+
document.body.removeChild(aTag);
|
|
217
|
+
}
|
|
218
|
+
|
|
203
219
|
if (!documentHasDocType()) {
|
|
204
220
|
// If this is executed too quickly, the DOCTYPE has not been loaded yet
|
|
205
221
|
// so wait a bit before checking again.
|
package/util/downloadUtil.js
CHANGED
|
@@ -29,7 +29,7 @@ const fs = __importStar(require("fs"));
|
|
|
29
29
|
const fsExtra = __importStar(require("fs-extra"));
|
|
30
30
|
const util = __importStar(require("util"));
|
|
31
31
|
const testsUtil_1 = require("../commands/tests/testsUtil");
|
|
32
|
-
const AWAIT_DOWNLOAD_MAX_TIMEOUT_MILLIS =
|
|
32
|
+
const AWAIT_DOWNLOAD_MAX_TIMEOUT_MILLIS = 60000;
|
|
33
33
|
const FILE_MIN_STABLE_TIME_MILLIS = 3000;
|
|
34
34
|
const AWAIT_DOWNLOAD_POLLING_INTERVAL_MILLISECONDS = 500;
|
|
35
35
|
function getAwaitDownloadTimeout() {
|