browsertime 26.3.2 → 27.0.1
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/CHANGELOG.md +72 -0
- package/README.md +1 -1
- package/browserscripts/timings/softNavigations.js +94 -0
- package/lib/chrome/har.js +36 -5
- package/lib/chrome/settings/chromeAndroidOptions.js +2 -1
- package/lib/chrome/webdriver/chromium.js +40 -1
- package/lib/chrome/webdriver/setupChromiumOptions.js +86 -31
- package/lib/core/engine/collector.js +340 -317
- package/lib/core/engine/command/addText.js +93 -12
- package/lib/core/engine/command/click.js +268 -139
- package/lib/core/engine/command/commandHelper.js +45 -0
- package/lib/core/engine/command/cookie.js +85 -0
- package/lib/core/engine/command/element.js +37 -2
- package/lib/core/engine/command/javaScript.js +14 -24
- package/lib/core/engine/command/measure/screenshots.js +114 -0
- package/lib/core/engine/command/measure/video.js +85 -0
- package/lib/core/engine/command/measure.js +104 -180
- package/lib/core/engine/command/mouse/clickAndHold.js +6 -3
- package/lib/core/engine/command/mouse/contextClick.js +56 -4
- package/lib/core/engine/command/mouse/doubleClick.js +49 -1
- package/lib/core/engine/command/mouse/mouseMove.js +50 -1
- package/lib/core/engine/command/mouse/singleClick.js +165 -129
- package/lib/core/engine/command/navigation.js +6 -6
- package/lib/core/engine/command/select.js +95 -3
- package/lib/core/engine/command/selectorParser.js +58 -0
- package/lib/core/engine/command/set.js +55 -1
- package/lib/core/engine/command/wait.js +41 -2
- package/lib/core/engine/commands.js +550 -42
- package/lib/core/engine/index.js +206 -150
- package/lib/core/engine/iteration.js +277 -137
- package/lib/firefox/networkManager.js +22 -30
- package/lib/firefox/webdriver/builder.js +14 -0
- package/lib/safari/har.js +131 -0
- package/lib/safari/native/ios-capture +0 -0
- package/lib/safari/native/ios-capture.m +340 -0
- package/lib/safari/safariInspectorClient.js +322 -0
- package/lib/safari/webdriver/safari.js +303 -13
- package/lib/safari/webkitToCdpAdapter.js +241 -0
- package/lib/support/cli.js +13 -0
- package/lib/support/engineUtils.js +16 -5
- package/lib/support/getViewPort.js +5 -0
- package/lib/support/har/index.js +41 -3
- package/lib/support/usbPower.js +2 -3
- package/lib/video/postprocessing/finetune/addTextToVideo.js +4 -0
- package/lib/video/postprocessing/finetune/getFont.js +3 -3
- package/lib/video/postprocessing/finetune/getTimingMetrics.js +7 -0
- package/lib/video/postprocessing/finetune/index.js +26 -9
- package/lib/video/screenRecording/ios/iosDeviceRecorder.js +109 -0
- package/lib/video/screenRecording/recorder.js +2 -2
- package/lib/video/screenRecording/setOrangeBackground.js +3 -0
- package/package.json +11 -11
- package/types/core/engine/command/addText.d.ts +35 -6
- package/types/core/engine/command/addText.d.ts.map +1 -1
- package/types/core/engine/command/click.d.ts +87 -16
- package/types/core/engine/command/click.d.ts.map +1 -1
- package/types/core/engine/command/commandHelper.d.ts +16 -0
- package/types/core/engine/command/commandHelper.d.ts.map +1 -0
- package/types/core/engine/command/cookie.d.ts +65 -0
- package/types/core/engine/command/cookie.d.ts.map +1 -0
- package/types/core/engine/command/element.d.ts +19 -1
- package/types/core/engine/command/element.d.ts.map +1 -1
- package/types/core/engine/command/javaScript.d.ts +4 -0
- package/types/core/engine/command/javaScript.d.ts.map +1 -1
- package/types/core/engine/command/measure/screenshots.d.ts +15 -0
- package/types/core/engine/command/measure/screenshots.d.ts.map +1 -0
- package/types/core/engine/command/measure/video.d.ts +26 -0
- package/types/core/engine/command/measure/video.d.ts.map +1 -0
- package/types/core/engine/command/measure.d.ts +59 -33
- package/types/core/engine/command/measure.d.ts.map +1 -1
- package/types/core/engine/command/mouse/clickAndHold.d.ts.map +1 -1
- package/types/core/engine/command/mouse/contextClick.d.ts +28 -4
- package/types/core/engine/command/mouse/contextClick.d.ts.map +1 -1
- package/types/core/engine/command/mouse/doubleClick.d.ts +24 -4
- package/types/core/engine/command/mouse/doubleClick.d.ts.map +1 -1
- package/types/core/engine/command/mouse/mouseMove.d.ts +30 -5
- package/types/core/engine/command/mouse/mouseMove.d.ts.map +1 -1
- package/types/core/engine/command/mouse/singleClick.d.ts +50 -13
- package/types/core/engine/command/mouse/singleClick.d.ts.map +1 -1
- package/types/core/engine/command/navigation.d.ts +3 -3
- package/types/core/engine/command/select.d.ts +45 -8
- package/types/core/engine/command/select.d.ts.map +1 -1
- package/types/core/engine/command/selectorParser.d.ts +20 -0
- package/types/core/engine/command/selectorParser.d.ts.map +1 -0
- package/types/core/engine/command/set.d.ts +34 -7
- package/types/core/engine/command/set.d.ts.map +1 -1
- package/types/core/engine/command/wait.d.ts +29 -6
- package/types/core/engine/command/wait.d.ts.map +1 -1
- package/types/core/engine/commands.d.ts +296 -28
- package/types/core/engine/commands.d.ts.map +1 -1
- package/types/support/engineUtils.d.ts.map +1 -1
- package/types/support/getViewPort.d.ts.map +1 -1
- package/types/support/usbPower.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/getTimingMetrics.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/index.d.ts.map +1 -1
- package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts +28 -0
- package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts.map +1 -0
- package/types/video/screenRecording/recorder.d.ts +2 -2
- package/types/video/screenRecording/recorder.d.ts.map +1 -1
- package/types/video/screenRecording/setOrangeBackground.d.ts.map +1 -1
- package/types/video/video.d.ts +1 -1
- package/types/video/video.d.ts.map +1 -1
- package/visualmetrics/visualmetrics-portable.py +136 -136
- package/lib/video/screenRecording/ios/convertToMp4.js +0 -22
- package/lib/video/screenRecording/ios/iosRecorder.js +0 -68
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
+
import webdriver, { By } from 'selenium-webdriver';
|
|
3
|
+
import { executeCommand } from './commandHelper.js';
|
|
4
|
+
import { parseSelector } from './selectorParser.js';
|
|
2
5
|
const log = getLogger('browsertime.command.click');
|
|
3
6
|
|
|
4
|
-
function addClick(js) {
|
|
5
|
-
const trimmed = js.trim();
|
|
6
|
-
let script = `${trimmed}.click();`;
|
|
7
|
-
if (trimmed.endsWith(';')) {
|
|
8
|
-
script = `${trimmed.slice(0, -1)}.click();`;
|
|
9
|
-
}
|
|
10
|
-
return script;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
/**
|
|
14
8
|
* Provides functionality to perform click actions on elements in a web page using various selectors.
|
|
9
|
+
* Tries the Selenium Actions API first to generate real OS-level mouse events; if the element is
|
|
10
|
+
* not interactable (commonly because the page hides content before clicking — a recommended
|
|
11
|
+
* pattern for visual-metric scripts), falls back to a JavaScript click so the script keeps working.
|
|
15
12
|
*
|
|
16
13
|
* @class
|
|
17
14
|
* @hideconstructor
|
|
18
15
|
*/
|
|
19
16
|
export class Click {
|
|
20
|
-
constructor(browser, pageCompleteCheck) {
|
|
17
|
+
constructor(browser, pageCompleteCheck, options) {
|
|
21
18
|
/**
|
|
22
19
|
* @private
|
|
23
20
|
*/
|
|
@@ -26,296 +23,428 @@ export class Click {
|
|
|
26
23
|
* @private
|
|
27
24
|
*/
|
|
28
25
|
this.pageCompleteCheck = pageCompleteCheck;
|
|
26
|
+
/**
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
async _waitForElement(driver, locator) {
|
|
36
|
+
const timeout = this.options?.timeouts?.elementWait ?? 0;
|
|
37
|
+
if (timeout > 0) {
|
|
38
|
+
await driver.wait(webdriver.until.elementLocated(locator), timeout);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
async _andWait(baseMethod, ...args) {
|
|
46
|
+
log.info(
|
|
47
|
+
'The AndWait methods are deprecated. Use commands.click(selector, { wait: true }) instead.'
|
|
48
|
+
);
|
|
49
|
+
await baseMethod.apply(this, args);
|
|
50
|
+
return this.browser.extraWait(this.pageCompleteCheck);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
async _clickElement(element) {
|
|
57
|
+
const driver = this.browser.getDriver();
|
|
58
|
+
// The Actions API moves the pointer to the element's bounding-box center
|
|
59
|
+
// and clicks. A `display:none` element has a zero-size box, so the click
|
|
60
|
+
// silently lands at (0,0) instead of throwing — we have to detect that
|
|
61
|
+
// up front and use JS instead. Other interactability problems (overlays,
|
|
62
|
+
// pointer-events:none, etc.) that do throw are caught below.
|
|
63
|
+
const isDisplayed = await element.isDisplayed().catch(() => false);
|
|
64
|
+
if (!isDisplayed) {
|
|
65
|
+
log.info(
|
|
66
|
+
'Element is not displayed — using a JavaScript click instead of the Actions API.'
|
|
67
|
+
);
|
|
68
|
+
return driver.executeScript('arguments[0].click();', element);
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await driver.actions({ async: true }).click(element).perform();
|
|
72
|
+
return driver.actions().clear();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
log.info(
|
|
75
|
+
'Actions API click failed (%s) — falling back to a JavaScript click.',
|
|
76
|
+
error && error.name
|
|
77
|
+
);
|
|
78
|
+
try {
|
|
79
|
+
await driver.actions().clear();
|
|
80
|
+
await driver.executeScript('arguments[0].click();', element);
|
|
81
|
+
} catch (jsError) {
|
|
82
|
+
log.error(
|
|
83
|
+
'Both the Actions API and the JavaScript click failed: %s',
|
|
84
|
+
(jsError && jsError.message) || jsError
|
|
85
|
+
);
|
|
86
|
+
throw jsError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clicks on an element using a unified selector string.
|
|
93
|
+
* Supports CSS selectors (default), and prefix-based strategies:
|
|
94
|
+
* 'id:myId', 'xpath://button', 'text:Submit', 'link:Click here', 'name:email', 'class:btn'.
|
|
95
|
+
*
|
|
96
|
+
* @async
|
|
97
|
+
* @param {string} selector - The selector string. CSS by default, or use a prefix like 'id:', 'xpath:', 'text:', 'link:', 'name:', 'class:'.
|
|
98
|
+
* @param {Object} [options] - Options for the click action.
|
|
99
|
+
* @param {boolean} [options.waitForNavigation=false] - If true, waits for the page complete check after clicking.
|
|
100
|
+
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
101
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
102
|
+
*/
|
|
103
|
+
async run(selector, options = {}) {
|
|
104
|
+
const { locator, description } = parseSelector(selector);
|
|
105
|
+
return executeCommand(
|
|
106
|
+
log,
|
|
107
|
+
'Could not find element by %s',
|
|
108
|
+
description,
|
|
109
|
+
async () => {
|
|
110
|
+
const driver = this.browser.getDriver();
|
|
111
|
+
await this._waitForElement(driver, locator);
|
|
112
|
+
const element = await driver.findElement(locator);
|
|
113
|
+
await this._clickElement(element);
|
|
114
|
+
if (options.waitForNavigation) {
|
|
115
|
+
await this.browser.extraWait(this.pageCompleteCheck);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
this.browser
|
|
119
|
+
);
|
|
29
120
|
}
|
|
30
121
|
|
|
31
122
|
/**
|
|
32
123
|
* Clicks on an element identified by its class name.
|
|
33
124
|
*
|
|
34
125
|
* @async
|
|
126
|
+
* @private
|
|
35
127
|
* @param {string} className - The class name of the element to click.
|
|
36
128
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
37
129
|
* @throws {Error} Throws an error if the element is not found.
|
|
38
130
|
*/
|
|
39
131
|
async byClassName(className) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
132
|
+
return executeCommand(
|
|
133
|
+
log,
|
|
134
|
+
'Could not find element by class name %s',
|
|
135
|
+
className,
|
|
136
|
+
async () => {
|
|
137
|
+
const driver = this.browser.getDriver();
|
|
138
|
+
const locator = By.className(className);
|
|
139
|
+
await this._waitForElement(driver, locator);
|
|
140
|
+
const element = await driver.findElement(locator);
|
|
141
|
+
return this._clickElement(element);
|
|
142
|
+
},
|
|
143
|
+
this.browser
|
|
144
|
+
);
|
|
48
145
|
}
|
|
49
146
|
|
|
50
147
|
/**
|
|
51
148
|
* Clicks on an element identified by its class name and waits for the page complete check to finish.
|
|
52
149
|
*
|
|
53
150
|
* @async
|
|
151
|
+
* @private
|
|
54
152
|
* @param {string} className - The class name of the element to click.
|
|
55
153
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
56
154
|
* @throws {Error} Throws an error if the element is not found.
|
|
57
155
|
*/
|
|
58
156
|
async byClassNameAndWait(className) {
|
|
59
|
-
|
|
60
|
-
const script = `document.getElementsByClassName('${className}')[0].click();`;
|
|
61
|
-
await this.browser.runScript(script, 'CUSTOM');
|
|
62
|
-
return this.browser.extraWait(this.pageCompleteCheck);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
log.error('Could not find element by class name %s', className);
|
|
65
|
-
log.verbose(error);
|
|
66
|
-
throw new Error('Could not find element by class name ' + className);
|
|
67
|
-
}
|
|
157
|
+
return this._andWait(this.byClassName, className);
|
|
68
158
|
}
|
|
69
159
|
|
|
70
160
|
/**
|
|
71
161
|
* Clicks on a link whose visible text matches the given string.
|
|
72
162
|
*
|
|
73
163
|
* @async
|
|
164
|
+
* @private
|
|
74
165
|
* @param {string} text - The visible text of the link to click.
|
|
75
166
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
76
167
|
* @throws {Error} Throws an error if the link is not found.
|
|
77
168
|
*/
|
|
78
169
|
async byLinkText(text) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
170
|
+
return executeCommand(
|
|
171
|
+
log,
|
|
172
|
+
'Could not find link by text %s',
|
|
173
|
+
text,
|
|
174
|
+
() => this.byXpath(`//a[text()='${text}']`),
|
|
175
|
+
this.browser
|
|
176
|
+
);
|
|
87
177
|
}
|
|
88
178
|
|
|
89
179
|
/**
|
|
90
180
|
* Clicks on a link whose visible text matches the given string and waits for the page complete check to finish.
|
|
91
181
|
*
|
|
92
182
|
* @async
|
|
183
|
+
* @private
|
|
93
184
|
* @param {string} text - The visible text of the link to click.
|
|
94
185
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
95
186
|
* @throws {Error} Throws an error if the link is not found.
|
|
96
187
|
*/
|
|
97
188
|
async byLinkTextAndWait(text) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
189
|
+
return executeCommand(
|
|
190
|
+
log,
|
|
191
|
+
'Could not find link by text %s',
|
|
192
|
+
text,
|
|
193
|
+
() => this.byXpathAndWait(`//a[text()='${text}']`),
|
|
194
|
+
this.browser
|
|
195
|
+
);
|
|
106
196
|
}
|
|
107
197
|
|
|
108
198
|
/**
|
|
109
199
|
* Clicks on a link whose visible text contains the given substring.
|
|
110
200
|
*
|
|
111
201
|
* @async
|
|
202
|
+
* @private
|
|
112
203
|
* @param {string} text - The substring of the visible text of the link to click.
|
|
113
204
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
114
205
|
* @throws {Error} Throws an error if the link is not found.
|
|
115
206
|
*/
|
|
116
207
|
async byPartialLinkText(text) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
208
|
+
return executeCommand(
|
|
209
|
+
log,
|
|
210
|
+
'Could not find link by partial text %s',
|
|
211
|
+
text,
|
|
212
|
+
() => this.byXpath(`//a[contains(text(),'${text}')]`),
|
|
213
|
+
this.browser
|
|
214
|
+
);
|
|
125
215
|
}
|
|
126
216
|
|
|
127
217
|
/**
|
|
128
218
|
* Clicks on a link whose visible text contains the given substring and waits for the page complete check to finish.
|
|
129
219
|
*
|
|
130
220
|
* @async
|
|
221
|
+
* @private
|
|
131
222
|
* @param {string} text - The substring of the visible text of the link to click.
|
|
132
223
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
133
224
|
* @throws {Error} Throws an error if the link is not found.
|
|
134
225
|
*/
|
|
135
226
|
async byPartialLinkTextAndWait(text) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
227
|
+
return executeCommand(
|
|
228
|
+
log,
|
|
229
|
+
'Could not find link by partial text %s',
|
|
230
|
+
text,
|
|
231
|
+
() => this.byXpathAndWait(`//a[contains(text(),'${text}')]`),
|
|
232
|
+
this.browser
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Clicks on an element whose visible text matches the given string.
|
|
238
|
+
* This works on any element type, not just links.
|
|
239
|
+
*
|
|
240
|
+
* @async
|
|
241
|
+
* @private
|
|
242
|
+
* @param {string} text - The visible text of the element to click.
|
|
243
|
+
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
244
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
245
|
+
*/
|
|
246
|
+
async byText(text) {
|
|
247
|
+
return executeCommand(
|
|
248
|
+
log,
|
|
249
|
+
'Could not find element by text %s',
|
|
250
|
+
text,
|
|
251
|
+
() => this.byXpath(`//*[normalize-space(text())='${text}']`),
|
|
252
|
+
this.browser
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Clicks on an element whose visible text matches the given string and waits for the page complete check to finish.
|
|
258
|
+
* This works on any element type, not just links.
|
|
259
|
+
*
|
|
260
|
+
* @async
|
|
261
|
+
* @private
|
|
262
|
+
* @param {string} text - The visible text of the element to click.
|
|
263
|
+
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
264
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
265
|
+
*/
|
|
266
|
+
async byTextAndWait(text) {
|
|
267
|
+
return executeCommand(
|
|
268
|
+
log,
|
|
269
|
+
'Could not find element by text %s',
|
|
270
|
+
text,
|
|
271
|
+
() => this.byXpathAndWait(`//*[normalize-space(text())='${text}']`),
|
|
272
|
+
this.browser
|
|
273
|
+
);
|
|
144
274
|
}
|
|
145
275
|
|
|
146
276
|
/**
|
|
147
277
|
* Clicks on an element that matches a given XPath selector.
|
|
148
278
|
*
|
|
149
279
|
* @async
|
|
280
|
+
* @private
|
|
150
281
|
* @param {string} xpath - The XPath selector of the element to click.
|
|
151
282
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
152
283
|
* @throws {Error} Throws an error if the element is not found.
|
|
153
284
|
*/
|
|
154
285
|
async byXpath(xpath) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
286
|
+
return executeCommand(
|
|
287
|
+
log,
|
|
288
|
+
'Could not find element by xpath %s',
|
|
289
|
+
xpath,
|
|
290
|
+
async () => {
|
|
291
|
+
const driver = this.browser.getDriver();
|
|
292
|
+
const locator = By.xpath(xpath);
|
|
293
|
+
await this._waitForElement(driver, locator);
|
|
294
|
+
const element = await driver.findElement(locator);
|
|
295
|
+
return this._clickElement(element);
|
|
296
|
+
},
|
|
297
|
+
this.browser
|
|
298
|
+
);
|
|
165
299
|
}
|
|
166
300
|
/**
|
|
167
301
|
* Clicks on an element that matches a given XPath selector and waits for the page complete check to finish.
|
|
168
302
|
*
|
|
169
303
|
* @async
|
|
304
|
+
* @private
|
|
170
305
|
* @param {string} xpath - The XPath selector of the element to click.
|
|
171
306
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
172
307
|
* @throws {Error} Throws an error if the element is not found.
|
|
173
308
|
*/
|
|
174
309
|
async byXpathAndWait(xpath) {
|
|
175
|
-
|
|
176
|
-
// This is how Selenium do internally
|
|
177
|
-
const replaced = xpath.replaceAll('"', "'");
|
|
178
|
-
const script = `document.evaluate("${replaced}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click();`;
|
|
179
|
-
await this.browser.runScript(script, 'CUSTOM');
|
|
180
|
-
return this.browser.extraWait(this.pageCompleteCheck);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
log.error('Could not find element by xpath %s', xpath);
|
|
183
|
-
log.verbose(error);
|
|
184
|
-
throw new Error('Could not find element by xpath ' + xpath);
|
|
185
|
-
}
|
|
310
|
+
return this._andWait(this.byXpath, xpath);
|
|
186
311
|
}
|
|
187
312
|
|
|
188
313
|
/**
|
|
189
314
|
* Clicks on an element located by evaluating a JavaScript expression.
|
|
190
315
|
*
|
|
191
316
|
* @async
|
|
317
|
+
* @private
|
|
192
318
|
* @param {string} js - The JavaScript expression that evaluates to an element or list of elements.
|
|
193
319
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
194
320
|
* @throws {Error} Throws an error if the element is not found.
|
|
195
321
|
*/
|
|
196
322
|
async byJs(js) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
323
|
+
return executeCommand(
|
|
324
|
+
log,
|
|
325
|
+
'Could not find element by JavaScript %s',
|
|
326
|
+
js,
|
|
327
|
+
async () => {
|
|
328
|
+
const trimmed = js.trim();
|
|
329
|
+
const script = trimmed.endsWith(';')
|
|
330
|
+
? `return ${trimmed.slice(0, -1)};`
|
|
331
|
+
: `return ${trimmed};`;
|
|
332
|
+
const element = await this.browser.getDriver().executeScript(script);
|
|
333
|
+
return this._clickElement(element);
|
|
334
|
+
},
|
|
335
|
+
this.browser
|
|
336
|
+
);
|
|
205
337
|
}
|
|
206
338
|
|
|
207
339
|
/**
|
|
208
340
|
* Clicks on an element located by evaluating a JavaScript expression and waits for the page complete check to finish.
|
|
209
341
|
*
|
|
210
342
|
* @async
|
|
343
|
+
* @private
|
|
211
344
|
* @param {string} js - The JavaScript expression that evaluates to an element or list of elements.
|
|
212
345
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
213
346
|
* @throws {Error} Throws an error if the element is not found.
|
|
214
347
|
*/
|
|
215
348
|
async byJsAndWait(js) {
|
|
216
|
-
|
|
217
|
-
const script = addClick(js);
|
|
218
|
-
await this.browser.runScript(script, 'CUSTOM');
|
|
219
|
-
return this.browser.extraWait(this.pageCompleteCheck);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
log.error('Could not find element by JavaScript %s', js);
|
|
222
|
-
log.verbose(error);
|
|
223
|
-
throw new Error('Could not find element by JavaScript ' + js);
|
|
224
|
-
}
|
|
349
|
+
return this._andWait(this.byJs, js);
|
|
225
350
|
}
|
|
226
351
|
|
|
227
352
|
/**
|
|
228
353
|
* Clicks on an element located by its ID.
|
|
229
354
|
*
|
|
230
355
|
* @async
|
|
356
|
+
* @private
|
|
231
357
|
* @param {string} id - The ID of the element to click.
|
|
232
358
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
233
359
|
* @throws {Error} Throws an error if the element is not found.
|
|
234
360
|
*/
|
|
235
361
|
async byId(id) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
362
|
+
return executeCommand(
|
|
363
|
+
log,
|
|
364
|
+
'Could not find element by id %s',
|
|
365
|
+
id,
|
|
366
|
+
async () => {
|
|
367
|
+
const driver = this.browser.getDriver();
|
|
368
|
+
const locator = By.id(id);
|
|
369
|
+
await this._waitForElement(driver, locator);
|
|
370
|
+
const element = await driver.findElement(locator);
|
|
371
|
+
return this._clickElement(element);
|
|
372
|
+
},
|
|
373
|
+
this.browser
|
|
374
|
+
);
|
|
244
375
|
}
|
|
245
376
|
|
|
246
377
|
/**
|
|
247
378
|
* Clicks on an element located by its name attribute.
|
|
248
379
|
*
|
|
249
380
|
* @async
|
|
381
|
+
* @private
|
|
250
382
|
* @param {string} name - The name attribute of the element to click.
|
|
251
383
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
252
384
|
* @throws {Error} Throws an error if the element is not found.
|
|
253
385
|
*/
|
|
254
386
|
async byName(name) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
387
|
+
return executeCommand(
|
|
388
|
+
log,
|
|
389
|
+
'Could not find element by name %s',
|
|
390
|
+
name,
|
|
391
|
+
async () => {
|
|
392
|
+
const driver = this.browser.getDriver();
|
|
393
|
+
const locator = By.name(name);
|
|
394
|
+
await this._waitForElement(driver, locator);
|
|
395
|
+
const element = await driver.findElement(locator);
|
|
396
|
+
return this._clickElement(element);
|
|
397
|
+
},
|
|
398
|
+
this.browser
|
|
399
|
+
);
|
|
263
400
|
}
|
|
264
401
|
|
|
265
402
|
/**
|
|
266
403
|
* Click on link located by the ID attribute. Uses document.getElementById() to find the element. And wait for page complete check to finish.
|
|
404
|
+
* @private
|
|
267
405
|
* @param {string} id
|
|
268
406
|
* @returns {Promise<void>} Promise object represents when the element has been clicked and the pageCompleteCheck has finished.
|
|
269
407
|
* @throws Will throw an error if the element is not found
|
|
270
408
|
*/
|
|
271
409
|
async byIdAndWait(id) {
|
|
272
|
-
|
|
273
|
-
const script = `document.getElementById('${id}').click();`;
|
|
274
|
-
await this.browser.runScript(script, 'CUSTOM');
|
|
275
|
-
return this.browser.extraWait(this.pageCompleteCheck);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
log.error('Could not find element by id %s', id);
|
|
278
|
-
log.verbose(error);
|
|
279
|
-
throw new Error('Could not find element by id ' + id);
|
|
280
|
-
}
|
|
410
|
+
return this._andWait(this.byId, id);
|
|
281
411
|
}
|
|
282
412
|
|
|
283
413
|
/**
|
|
284
414
|
* Clicks on an element located by its CSS selector.
|
|
285
415
|
*
|
|
286
416
|
* @async
|
|
417
|
+
* @private
|
|
287
418
|
* @param {string} selector - The CSS selector of the element to click.
|
|
288
419
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
289
420
|
* @throws {Error} Throws an error if the element is not found.
|
|
290
421
|
*/
|
|
291
422
|
async bySelector(selector) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
423
|
+
return executeCommand(
|
|
424
|
+
log,
|
|
425
|
+
'Could not click using selector %s',
|
|
426
|
+
selector,
|
|
427
|
+
async () => {
|
|
428
|
+
const driver = this.browser.getDriver();
|
|
429
|
+
const locator = By.css(selector);
|
|
430
|
+
await this._waitForElement(driver, locator);
|
|
431
|
+
const element = await driver.findElement(locator);
|
|
432
|
+
return this._clickElement(element);
|
|
433
|
+
},
|
|
434
|
+
this.browser
|
|
435
|
+
);
|
|
300
436
|
}
|
|
301
437
|
|
|
302
438
|
/**
|
|
303
439
|
* Clicks on an element located by its CSS selector and waits for the page complete check to finish.
|
|
304
440
|
*
|
|
305
441
|
* @async
|
|
442
|
+
* @private
|
|
306
443
|
* @param {string} selector - The CSS selector of the element to click.
|
|
307
444
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
308
445
|
* @throws {Error} Throws an error if the element is not found.
|
|
309
446
|
*/
|
|
310
447
|
async bySelectorAndWait(selector) {
|
|
311
|
-
|
|
312
|
-
const script = `document.querySelector('${selector}').click();`;
|
|
313
|
-
await this.browser.runScript(script, 'CUSTOM');
|
|
314
|
-
return this.browser.extraWait(this.pageCompleteCheck);
|
|
315
|
-
} catch (error) {
|
|
316
|
-
log.error('Could not click using selector %s', selector);
|
|
317
|
-
log.verbose(error);
|
|
318
|
-
throw new Error('Could not click using selector ' + selector);
|
|
319
|
-
}
|
|
448
|
+
return this._andWait(this.bySelector, selector);
|
|
320
449
|
}
|
|
321
450
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared error handling wrapper for command methods.
|
|
3
|
+
*
|
|
4
|
+
* Most command methods follow the same try/catch pattern:
|
|
5
|
+
* log.error + log.verbose + throw new Error. This helper
|
|
6
|
+
* eliminates that boilerplate.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} log - The logger instance.
|
|
9
|
+
* @param {string} errorMessage - printf-style message (with %s placeholder).
|
|
10
|
+
* @param {string|undefined} identifier - Value to interpolate into the message, or undefined if none.
|
|
11
|
+
* @param {Function} fn - The async function to execute.
|
|
12
|
+
* @param {Object} [browser] - Optional browser instance. When provided, the current page URL is appended to the error message.
|
|
13
|
+
* @returns {Promise<*>} The return value of fn.
|
|
14
|
+
*/
|
|
15
|
+
export async function executeCommand(
|
|
16
|
+
log,
|
|
17
|
+
errorMessage,
|
|
18
|
+
identifier,
|
|
19
|
+
fn,
|
|
20
|
+
browser
|
|
21
|
+
) {
|
|
22
|
+
try {
|
|
23
|
+
return await fn();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
let url = '';
|
|
26
|
+
if (browser) {
|
|
27
|
+
try {
|
|
28
|
+
url = ` on ${await browser.getDriver().getCurrentUrl()}`;
|
|
29
|
+
} catch {
|
|
30
|
+
// If we can't get the URL, just skip it
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const baseMessage =
|
|
34
|
+
identifier === undefined
|
|
35
|
+
? errorMessage
|
|
36
|
+
: errorMessage.replace('%s', String(identifier));
|
|
37
|
+
if (identifier === undefined) {
|
|
38
|
+
log.error(errorMessage);
|
|
39
|
+
} else {
|
|
40
|
+
log.error(errorMessage, identifier);
|
|
41
|
+
}
|
|
42
|
+
log.verbose(error);
|
|
43
|
+
throw new Error(`${baseMessage}${url}`);
|
|
44
|
+
}
|
|
45
|
+
}
|