browsertime 26.3.1 → 27.0.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/CHANGELOG.md +71 -2
- 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 +39 -0
- 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 +239 -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 +269 -137
- package/lib/firefox/networkManager.js +22 -30
- package/lib/firefox/webdriver/builder.js +14 -0
- package/lib/firefox/webdriver/firefox.js +1 -1
- 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 +12 -12
- 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
|
+
* Uses the Selenium Actions API to generate real OS-level mouse events, which means
|
|
10
|
+
* the element must be visible and interactable. If you need to click a hidden element,
|
|
11
|
+
* use {@link JavaScript#run commands.js.run} to trigger a JavaScript click instead.
|
|
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,399 @@ 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
|
+
await driver.actions({ async: true }).click(element).perform();
|
|
59
|
+
return driver.actions().clear();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clicks on an element using a unified selector string.
|
|
64
|
+
* Supports CSS selectors (default), and prefix-based strategies:
|
|
65
|
+
* 'id:myId', 'xpath://button', 'text:Submit', 'link:Click here', 'name:email', 'class:btn'.
|
|
66
|
+
*
|
|
67
|
+
* @async
|
|
68
|
+
* @param {string} selector - The selector string. CSS by default, or use a prefix like 'id:', 'xpath:', 'text:', 'link:', 'name:', 'class:'.
|
|
69
|
+
* @param {Object} [options] - Options for the click action.
|
|
70
|
+
* @param {boolean} [options.waitForNavigation=false] - If true, waits for the page complete check after clicking.
|
|
71
|
+
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
72
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
73
|
+
*/
|
|
74
|
+
async run(selector, options = {}) {
|
|
75
|
+
const { locator, description } = parseSelector(selector);
|
|
76
|
+
return executeCommand(
|
|
77
|
+
log,
|
|
78
|
+
'Could not find element by %s',
|
|
79
|
+
description,
|
|
80
|
+
async () => {
|
|
81
|
+
const driver = this.browser.getDriver();
|
|
82
|
+
await this._waitForElement(driver, locator);
|
|
83
|
+
const element = await driver.findElement(locator);
|
|
84
|
+
await this._clickElement(element);
|
|
85
|
+
if (options.waitForNavigation) {
|
|
86
|
+
await this.browser.extraWait(this.pageCompleteCheck);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
this.browser
|
|
90
|
+
);
|
|
29
91
|
}
|
|
30
92
|
|
|
31
93
|
/**
|
|
32
94
|
* Clicks on an element identified by its class name.
|
|
33
95
|
*
|
|
34
96
|
* @async
|
|
97
|
+
* @private
|
|
35
98
|
* @param {string} className - The class name of the element to click.
|
|
36
99
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
37
100
|
* @throws {Error} Throws an error if the element is not found.
|
|
38
101
|
*/
|
|
39
102
|
async byClassName(className) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
103
|
+
return executeCommand(
|
|
104
|
+
log,
|
|
105
|
+
'Could not find element by class name %s',
|
|
106
|
+
className,
|
|
107
|
+
async () => {
|
|
108
|
+
const driver = this.browser.getDriver();
|
|
109
|
+
const locator = By.className(className);
|
|
110
|
+
await this._waitForElement(driver, locator);
|
|
111
|
+
const element = await driver.findElement(locator);
|
|
112
|
+
return this._clickElement(element);
|
|
113
|
+
},
|
|
114
|
+
this.browser
|
|
115
|
+
);
|
|
48
116
|
}
|
|
49
117
|
|
|
50
118
|
/**
|
|
51
119
|
* Clicks on an element identified by its class name and waits for the page complete check to finish.
|
|
52
120
|
*
|
|
53
121
|
* @async
|
|
122
|
+
* @private
|
|
54
123
|
* @param {string} className - The class name of the element to click.
|
|
55
124
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
56
125
|
* @throws {Error} Throws an error if the element is not found.
|
|
57
126
|
*/
|
|
58
127
|
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
|
-
}
|
|
128
|
+
return this._andWait(this.byClassName, className);
|
|
68
129
|
}
|
|
69
130
|
|
|
70
131
|
/**
|
|
71
132
|
* Clicks on a link whose visible text matches the given string.
|
|
72
133
|
*
|
|
73
134
|
* @async
|
|
135
|
+
* @private
|
|
74
136
|
* @param {string} text - The visible text of the link to click.
|
|
75
137
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
76
138
|
* @throws {Error} Throws an error if the link is not found.
|
|
77
139
|
*/
|
|
78
140
|
async byLinkText(text) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
141
|
+
return executeCommand(
|
|
142
|
+
log,
|
|
143
|
+
'Could not find link by text %s',
|
|
144
|
+
text,
|
|
145
|
+
() => this.byXpath(`//a[text()='${text}']`),
|
|
146
|
+
this.browser
|
|
147
|
+
);
|
|
87
148
|
}
|
|
88
149
|
|
|
89
150
|
/**
|
|
90
151
|
* Clicks on a link whose visible text matches the given string and waits for the page complete check to finish.
|
|
91
152
|
*
|
|
92
153
|
* @async
|
|
154
|
+
* @private
|
|
93
155
|
* @param {string} text - The visible text of the link to click.
|
|
94
156
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
95
157
|
* @throws {Error} Throws an error if the link is not found.
|
|
96
158
|
*/
|
|
97
159
|
async byLinkTextAndWait(text) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
160
|
+
return executeCommand(
|
|
161
|
+
log,
|
|
162
|
+
'Could not find link by text %s',
|
|
163
|
+
text,
|
|
164
|
+
() => this.byXpathAndWait(`//a[text()='${text}']`),
|
|
165
|
+
this.browser
|
|
166
|
+
);
|
|
106
167
|
}
|
|
107
168
|
|
|
108
169
|
/**
|
|
109
170
|
* Clicks on a link whose visible text contains the given substring.
|
|
110
171
|
*
|
|
111
172
|
* @async
|
|
173
|
+
* @private
|
|
112
174
|
* @param {string} text - The substring of the visible text of the link to click.
|
|
113
175
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
114
176
|
* @throws {Error} Throws an error if the link is not found.
|
|
115
177
|
*/
|
|
116
178
|
async byPartialLinkText(text) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
179
|
+
return executeCommand(
|
|
180
|
+
log,
|
|
181
|
+
'Could not find link by partial text %s',
|
|
182
|
+
text,
|
|
183
|
+
() => this.byXpath(`//a[contains(text(),'${text}')]`),
|
|
184
|
+
this.browser
|
|
185
|
+
);
|
|
125
186
|
}
|
|
126
187
|
|
|
127
188
|
/**
|
|
128
189
|
* Clicks on a link whose visible text contains the given substring and waits for the page complete check to finish.
|
|
129
190
|
*
|
|
130
191
|
* @async
|
|
192
|
+
* @private
|
|
131
193
|
* @param {string} text - The substring of the visible text of the link to click.
|
|
132
194
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
133
195
|
* @throws {Error} Throws an error if the link is not found.
|
|
134
196
|
*/
|
|
135
197
|
async byPartialLinkTextAndWait(text) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
198
|
+
return executeCommand(
|
|
199
|
+
log,
|
|
200
|
+
'Could not find link by partial text %s',
|
|
201
|
+
text,
|
|
202
|
+
() => this.byXpathAndWait(`//a[contains(text(),'${text}')]`),
|
|
203
|
+
this.browser
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Clicks on an element whose visible text matches the given string.
|
|
209
|
+
* This works on any element type, not just links.
|
|
210
|
+
*
|
|
211
|
+
* @async
|
|
212
|
+
* @private
|
|
213
|
+
* @param {string} text - The visible text of the element to click.
|
|
214
|
+
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
215
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
216
|
+
*/
|
|
217
|
+
async byText(text) {
|
|
218
|
+
return executeCommand(
|
|
219
|
+
log,
|
|
220
|
+
'Could not find element by text %s',
|
|
221
|
+
text,
|
|
222
|
+
() => this.byXpath(`//*[normalize-space(text())='${text}']`),
|
|
223
|
+
this.browser
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Clicks on an element whose visible text matches the given string and waits for the page complete check to finish.
|
|
229
|
+
* This works on any element type, not just links.
|
|
230
|
+
*
|
|
231
|
+
* @async
|
|
232
|
+
* @private
|
|
233
|
+
* @param {string} text - The visible text of the element to click.
|
|
234
|
+
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
235
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
236
|
+
*/
|
|
237
|
+
async byTextAndWait(text) {
|
|
238
|
+
return executeCommand(
|
|
239
|
+
log,
|
|
240
|
+
'Could not find element by text %s',
|
|
241
|
+
text,
|
|
242
|
+
() => this.byXpathAndWait(`//*[normalize-space(text())='${text}']`),
|
|
243
|
+
this.browser
|
|
244
|
+
);
|
|
144
245
|
}
|
|
145
246
|
|
|
146
247
|
/**
|
|
147
248
|
* Clicks on an element that matches a given XPath selector.
|
|
148
249
|
*
|
|
149
250
|
* @async
|
|
251
|
+
* @private
|
|
150
252
|
* @param {string} xpath - The XPath selector of the element to click.
|
|
151
253
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
152
254
|
* @throws {Error} Throws an error if the element is not found.
|
|
153
255
|
*/
|
|
154
256
|
async byXpath(xpath) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
257
|
+
return executeCommand(
|
|
258
|
+
log,
|
|
259
|
+
'Could not find element by xpath %s',
|
|
260
|
+
xpath,
|
|
261
|
+
async () => {
|
|
262
|
+
const driver = this.browser.getDriver();
|
|
263
|
+
const locator = By.xpath(xpath);
|
|
264
|
+
await this._waitForElement(driver, locator);
|
|
265
|
+
const element = await driver.findElement(locator);
|
|
266
|
+
return this._clickElement(element);
|
|
267
|
+
},
|
|
268
|
+
this.browser
|
|
269
|
+
);
|
|
165
270
|
}
|
|
166
271
|
/**
|
|
167
272
|
* Clicks on an element that matches a given XPath selector and waits for the page complete check to finish.
|
|
168
273
|
*
|
|
169
274
|
* @async
|
|
275
|
+
* @private
|
|
170
276
|
* @param {string} xpath - The XPath selector of the element to click.
|
|
171
277
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
172
278
|
* @throws {Error} Throws an error if the element is not found.
|
|
173
279
|
*/
|
|
174
280
|
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
|
-
}
|
|
281
|
+
return this._andWait(this.byXpath, xpath);
|
|
186
282
|
}
|
|
187
283
|
|
|
188
284
|
/**
|
|
189
285
|
* Clicks on an element located by evaluating a JavaScript expression.
|
|
190
286
|
*
|
|
191
287
|
* @async
|
|
288
|
+
* @private
|
|
192
289
|
* @param {string} js - The JavaScript expression that evaluates to an element or list of elements.
|
|
193
290
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
194
291
|
* @throws {Error} Throws an error if the element is not found.
|
|
195
292
|
*/
|
|
196
293
|
async byJs(js) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
294
|
+
return executeCommand(
|
|
295
|
+
log,
|
|
296
|
+
'Could not find element by JavaScript %s',
|
|
297
|
+
js,
|
|
298
|
+
async () => {
|
|
299
|
+
const trimmed = js.trim();
|
|
300
|
+
const script = trimmed.endsWith(';')
|
|
301
|
+
? `return ${trimmed.slice(0, -1)};`
|
|
302
|
+
: `return ${trimmed};`;
|
|
303
|
+
const element = await this.browser.getDriver().executeScript(script);
|
|
304
|
+
return this._clickElement(element);
|
|
305
|
+
},
|
|
306
|
+
this.browser
|
|
307
|
+
);
|
|
205
308
|
}
|
|
206
309
|
|
|
207
310
|
/**
|
|
208
311
|
* Clicks on an element located by evaluating a JavaScript expression and waits for the page complete check to finish.
|
|
209
312
|
*
|
|
210
313
|
* @async
|
|
314
|
+
* @private
|
|
211
315
|
* @param {string} js - The JavaScript expression that evaluates to an element or list of elements.
|
|
212
316
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
213
317
|
* @throws {Error} Throws an error if the element is not found.
|
|
214
318
|
*/
|
|
215
319
|
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
|
-
}
|
|
320
|
+
return this._andWait(this.byJs, js);
|
|
225
321
|
}
|
|
226
322
|
|
|
227
323
|
/**
|
|
228
324
|
* Clicks on an element located by its ID.
|
|
229
325
|
*
|
|
230
326
|
* @async
|
|
327
|
+
* @private
|
|
231
328
|
* @param {string} id - The ID of the element to click.
|
|
232
329
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
233
330
|
* @throws {Error} Throws an error if the element is not found.
|
|
234
331
|
*/
|
|
235
332
|
async byId(id) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
333
|
+
return executeCommand(
|
|
334
|
+
log,
|
|
335
|
+
'Could not find element by id %s',
|
|
336
|
+
id,
|
|
337
|
+
async () => {
|
|
338
|
+
const driver = this.browser.getDriver();
|
|
339
|
+
const locator = By.id(id);
|
|
340
|
+
await this._waitForElement(driver, locator);
|
|
341
|
+
const element = await driver.findElement(locator);
|
|
342
|
+
return this._clickElement(element);
|
|
343
|
+
},
|
|
344
|
+
this.browser
|
|
345
|
+
);
|
|
244
346
|
}
|
|
245
347
|
|
|
246
348
|
/**
|
|
247
349
|
* Clicks on an element located by its name attribute.
|
|
248
350
|
*
|
|
249
351
|
* @async
|
|
352
|
+
* @private
|
|
250
353
|
* @param {string} name - The name attribute of the element to click.
|
|
251
354
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
252
355
|
* @throws {Error} Throws an error if the element is not found.
|
|
253
356
|
*/
|
|
254
357
|
async byName(name) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
358
|
+
return executeCommand(
|
|
359
|
+
log,
|
|
360
|
+
'Could not find element by name %s',
|
|
361
|
+
name,
|
|
362
|
+
async () => {
|
|
363
|
+
const driver = this.browser.getDriver();
|
|
364
|
+
const locator = By.name(name);
|
|
365
|
+
await this._waitForElement(driver, locator);
|
|
366
|
+
const element = await driver.findElement(locator);
|
|
367
|
+
return this._clickElement(element);
|
|
368
|
+
},
|
|
369
|
+
this.browser
|
|
370
|
+
);
|
|
263
371
|
}
|
|
264
372
|
|
|
265
373
|
/**
|
|
266
374
|
* Click on link located by the ID attribute. Uses document.getElementById() to find the element. And wait for page complete check to finish.
|
|
375
|
+
* @private
|
|
267
376
|
* @param {string} id
|
|
268
377
|
* @returns {Promise<void>} Promise object represents when the element has been clicked and the pageCompleteCheck has finished.
|
|
269
378
|
* @throws Will throw an error if the element is not found
|
|
270
379
|
*/
|
|
271
380
|
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
|
-
}
|
|
381
|
+
return this._andWait(this.byId, id);
|
|
281
382
|
}
|
|
282
383
|
|
|
283
384
|
/**
|
|
284
385
|
* Clicks on an element located by its CSS selector.
|
|
285
386
|
*
|
|
286
387
|
* @async
|
|
388
|
+
* @private
|
|
287
389
|
* @param {string} selector - The CSS selector of the element to click.
|
|
288
390
|
* @returns {Promise<void>} A promise that resolves when the click action is performed.
|
|
289
391
|
* @throws {Error} Throws an error if the element is not found.
|
|
290
392
|
*/
|
|
291
393
|
async bySelector(selector) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
394
|
+
return executeCommand(
|
|
395
|
+
log,
|
|
396
|
+
'Could not click using selector %s',
|
|
397
|
+
selector,
|
|
398
|
+
async () => {
|
|
399
|
+
const driver = this.browser.getDriver();
|
|
400
|
+
const locator = By.css(selector);
|
|
401
|
+
await this._waitForElement(driver, locator);
|
|
402
|
+
const element = await driver.findElement(locator);
|
|
403
|
+
return this._clickElement(element);
|
|
404
|
+
},
|
|
405
|
+
this.browser
|
|
406
|
+
);
|
|
300
407
|
}
|
|
301
408
|
|
|
302
409
|
/**
|
|
303
410
|
* Clicks on an element located by its CSS selector and waits for the page complete check to finish.
|
|
304
411
|
*
|
|
305
412
|
* @async
|
|
413
|
+
* @private
|
|
306
414
|
* @param {string} selector - The CSS selector of the element to click.
|
|
307
415
|
* @returns {Promise<void>} A promise that resolves when the click action and page complete check are finished.
|
|
308
416
|
* @throws {Error} Throws an error if the element is not found.
|
|
309
417
|
*/
|
|
310
418
|
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
|
-
}
|
|
419
|
+
return this._andWait(this.bySelector, selector);
|
|
320
420
|
}
|
|
321
421
|
}
|
|
@@ -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
|
+
}
|