selenium-webdriver 4.35.0 → 4.47.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.
- checksums.yaml +4 -4
- data/CHANGES +111 -0
- data/Gemfile +1 -4
- data/LICENSE +1 -1
- data/NOTICE +1 -1
- data/README.md +43 -23
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
- data/bin/selenium-manager.cdx.json +11629 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/server.rb +30 -5
- data/lib/selenium/webdriver/atoms/findElements.js +272 -54
- data/lib/selenium/webdriver/atoms/getAttribute.js +152 -6
- data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -28
- data/lib/selenium/webdriver/bidi/browser.rb +71 -0
- data/lib/selenium/webdriver/bidi/browsing_context.rb +3 -2
- data/lib/selenium/webdriver/bidi/{log/filter_by.rb → error.rb} +20 -16
- data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
- data/lib/selenium/webdriver/bidi/network/cookies.rb +13 -9
- data/lib/selenium/webdriver/bidi/network/credentials.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/headers.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +4 -0
- data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +20 -4
- data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +24 -6
- data/lib/selenium/webdriver/bidi/network/url_pattern.rb +4 -0
- data/lib/selenium/webdriver/bidi/network.rb +18 -9
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
- data/lib/selenium/webdriver/bidi/{log/base_log_entry.rb → protocol/domain.rb} +22 -11
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
- data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
- data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
- data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
- data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
- data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
- data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
- data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
- data/lib/selenium/webdriver/bidi/session.rb +4 -0
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
- data/lib/selenium/webdriver/bidi/transport.rb +57 -0
- data/lib/selenium/webdriver/bidi.rb +4 -1
- data/lib/selenium/webdriver/chrome/driver.rb +4 -3
- data/lib/selenium/webdriver/chrome/service.rb +13 -0
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +23 -1
- data/lib/selenium/webdriver/chromium/profile.rb +6 -0
- data/lib/selenium/webdriver/common/child_process.rb +2 -1
- data/lib/selenium/webdriver/common/client_config.rb +97 -0
- data/lib/selenium/webdriver/common/driver.rb +13 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/{has_bidi.rb → has_session_events.rb} +19 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +35 -25
- data/lib/selenium/webdriver/common/error.rb +10 -3
- data/lib/selenium/webdriver/common/local_driver.rb +29 -12
- data/lib/selenium/webdriver/common/logger.rb +28 -0
- data/lib/selenium/webdriver/common/manager.rb +2 -0
- data/lib/selenium/webdriver/common/options.rb +41 -2
- data/lib/selenium/webdriver/common/platform.rb +1 -3
- data/lib/selenium/webdriver/common/proxy.rb +1 -9
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/service.rb +4 -8
- data/lib/selenium/webdriver/common/service_manager.rb +36 -4
- data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +1 -1
- data/lib/selenium/webdriver/common/wait.rb +4 -1
- data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
- data/lib/selenium/webdriver/common.rb +2 -1
- data/lib/selenium/webdriver/devtools/response.rb +3 -3
- data/lib/selenium/webdriver/devtools.rb +1 -1
- data/lib/selenium/webdriver/edge/driver.rb +4 -3
- data/lib/selenium/webdriver/edge/service.rb +14 -0
- data/lib/selenium/webdriver/firefox/driver.rb +4 -6
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/firefox/service.rb +31 -2
- data/lib/selenium/webdriver/ie/driver.rb +4 -3
- data/lib/selenium/webdriver/ie/service.rb +10 -0
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -10
- data/lib/selenium/webdriver/remote/bridge.rb +20 -14
- data/lib/selenium/webdriver/remote/driver.rb +15 -4
- data/lib/selenium/webdriver/remote/features.rb +26 -1
- data/lib/selenium/webdriver/remote/http/common.rb +57 -12
- data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
- data/lib/selenium/webdriver/remote/http/default.rb +56 -39
- data/lib/selenium/webdriver/safari/driver.rb +4 -3
- data/lib/selenium/webdriver/safari/options.rb +23 -4
- data/lib/selenium/webdriver/safari.rb +1 -6
- data/lib/selenium/webdriver/support/block_event_listener.rb +5 -1
- data/lib/selenium/webdriver/support/color.rb +14 -14
- data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +30 -14
- data/lib/selenium/webdriver/support/guards.rb +11 -4
- data/lib/selenium/webdriver/version.rb +1 -1
- data/lib/selenium/webdriver.rb +7 -2
- data/selenium-webdriver.gemspec +1 -1
- metadata +33 -11
- data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
- data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +0 -33
- data/lib/selenium/webdriver/bidi/log_inspector.rb +0 -147
|
Binary file
|
data/lib/selenium/server.rb
CHANGED
|
@@ -21,6 +21,7 @@ require 'selenium/webdriver/common/child_process'
|
|
|
21
21
|
require 'selenium/webdriver/common/port_prober'
|
|
22
22
|
require 'selenium/webdriver/common/socket_poller'
|
|
23
23
|
require 'net/http'
|
|
24
|
+
require 'json'
|
|
24
25
|
|
|
25
26
|
module Selenium
|
|
26
27
|
#
|
|
@@ -118,7 +119,7 @@ module Selenium
|
|
|
118
119
|
json = http.get('/repos/seleniumhq/selenium/releases').body
|
|
119
120
|
all_assets = JSON.parse(json).map { |release| release['assets'] }.flatten
|
|
120
121
|
server_assets = all_assets.select { |asset| asset['name'].match(/selenium-server-(\d+\.\d+\.\d+)\.jar/) }
|
|
121
|
-
server_assets.
|
|
122
|
+
server_assets.to_h { |asset| [asset.delete('name'), asset] }
|
|
122
123
|
end
|
|
123
124
|
end
|
|
124
125
|
|
|
@@ -203,7 +204,7 @@ module Selenium
|
|
|
203
204
|
|
|
204
205
|
def start
|
|
205
206
|
process.start
|
|
206
|
-
|
|
207
|
+
poll_for_ready
|
|
207
208
|
|
|
208
209
|
process.wait unless @background
|
|
209
210
|
end
|
|
@@ -219,6 +220,20 @@ module Selenium
|
|
|
219
220
|
"http://#{@host}:#{@port}/wd/hub"
|
|
220
221
|
end
|
|
221
222
|
|
|
223
|
+
def status_ok?
|
|
224
|
+
return false unless @process&.alive? && socket_connected?
|
|
225
|
+
|
|
226
|
+
Net::HTTP.start(@host, @port, open_timeout: 2, read_timeout: 2) do |http|
|
|
227
|
+
response = http.get('/status')
|
|
228
|
+
return false unless response.is_a?(Net::HTTPSuccess)
|
|
229
|
+
|
|
230
|
+
status = JSON.parse(response.body)
|
|
231
|
+
status.dig('value', 'ready') == true
|
|
232
|
+
end
|
|
233
|
+
rescue StandardError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
222
237
|
def <<(arg)
|
|
223
238
|
if arg.is_a?(Array)
|
|
224
239
|
@additional_args += arg
|
|
@@ -257,10 +272,20 @@ module Selenium
|
|
|
257
272
|
end
|
|
258
273
|
end
|
|
259
274
|
|
|
260
|
-
def
|
|
261
|
-
|
|
275
|
+
def poll_for_ready
|
|
276
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
277
|
+
loop do
|
|
278
|
+
return if status_ok?
|
|
279
|
+
|
|
280
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
281
|
+
raise Error, "remote server not ready in #{@timeout} seconds" if elapsed > @timeout
|
|
282
|
+
|
|
283
|
+
sleep 0.5
|
|
284
|
+
end
|
|
285
|
+
end
|
|
262
286
|
|
|
263
|
-
|
|
287
|
+
def socket_connected?
|
|
288
|
+
@socket_connected ||= socket.connected?
|
|
264
289
|
end
|
|
265
290
|
|
|
266
291
|
def poll_for_shutdown
|
|
@@ -1,54 +1,272 @@
|
|
|
1
|
-
function()
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function
|
|
1
|
+
(function findElements(target, root) {
|
|
2
|
+
const INVALID_SELECTOR = 'invalid selector';
|
|
3
|
+
const INVALID_ARGUMENT = 'invalid argument';
|
|
4
|
+
const NO_SUCH_ELEMENT = 'no such element';
|
|
5
|
+
function botError(code, message) {
|
|
6
|
+
const err = new Error(message);
|
|
7
|
+
err['code'] = code;
|
|
8
|
+
return err;
|
|
9
|
+
}
|
|
10
|
+
function cssEscapeId(s) {
|
|
11
|
+
return s.replace(/[\s'"\\#.:;,!?+<>=~*^$|%&@`{}\-/\[\]()]/g, (c) => '\\' + c);
|
|
12
|
+
}
|
|
13
|
+
function classNameMany(target, root) {
|
|
14
|
+
if (!target) {
|
|
15
|
+
throw botError(INVALID_SELECTOR, 'No class name specified');
|
|
16
|
+
}
|
|
17
|
+
target = target.trim();
|
|
18
|
+
if (target.indexOf(' ') !== -1) {
|
|
19
|
+
throw botError(INVALID_SELECTOR, 'Compound class names not permitted');
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return Array.from(root.querySelectorAll('.' + target.replace(/\\/g, '\\\\').replace(/\./g, '\\.')));
|
|
23
|
+
}
|
|
24
|
+
catch (_e) {
|
|
25
|
+
throw botError(INVALID_SELECTOR, 'An invalid or illegal class name was specified');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function cssMany(target, root) {
|
|
29
|
+
try {
|
|
30
|
+
return Array.from(root.querySelectorAll(target));
|
|
31
|
+
}
|
|
32
|
+
catch (_e) {
|
|
33
|
+
throw botError(INVALID_SELECTOR, 'An invalid or illegal CSS selector was specified: ' + target);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function idMany(target, root) {
|
|
37
|
+
if (!target) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
if (!/^\d/.test(target)) {
|
|
41
|
+
try {
|
|
42
|
+
return Array.from(root.querySelectorAll('#' + cssEscapeId(target)));
|
|
43
|
+
}
|
|
44
|
+
catch (_e) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return Array.from(root.querySelectorAll('*')).filter(el => el.getAttribute('id') === target);
|
|
49
|
+
}
|
|
50
|
+
function getLinkText(el) {
|
|
51
|
+
const text = el.innerText !== undefined ? el.innerText : el.textContent || '';
|
|
52
|
+
return text.replace(/^[\s]+|[\s]+$/g, '');
|
|
53
|
+
}
|
|
54
|
+
function linkTextMany(target, root, partial) {
|
|
55
|
+
return Array.from(root.querySelectorAll('a')).filter(el => {
|
|
56
|
+
const text = getLinkText(el);
|
|
57
|
+
return partial ? text.indexOf(target) !== -1 : text === target;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function nameMany(target, root) {
|
|
61
|
+
return Array.from(root.querySelectorAll('[name="' + target.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]'));
|
|
62
|
+
}
|
|
63
|
+
function tagNameMany(target, root) {
|
|
64
|
+
if (target === '') {
|
|
65
|
+
throw botError(INVALID_SELECTOR, 'Unable to locate an element with the tagName ""');
|
|
66
|
+
}
|
|
67
|
+
return Array.from(root.getElementsByTagName(target));
|
|
68
|
+
}
|
|
69
|
+
const DEFAULT_NS_RESOLVER = (function () {
|
|
70
|
+
const ns = { svg: 'http://www.w3.org/2000/svg' };
|
|
71
|
+
return (prefix) => ns[prefix] || null;
|
|
72
|
+
})();
|
|
73
|
+
const ORDERED_NODE_SNAPSHOT_TYPE = 7;
|
|
74
|
+
function xpathMany(target, root) {
|
|
75
|
+
const doc = root.documentElement ? root : root.ownerDocument;
|
|
76
|
+
if (!doc.documentElement) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
let resolver = null;
|
|
81
|
+
if (target.indexOf(':') !== -1) {
|
|
82
|
+
const reversedNs = {};
|
|
83
|
+
const allNodes = doc.getElementsByTagName('*');
|
|
84
|
+
for (let i = 0; i < allNodes.length; i++) {
|
|
85
|
+
const n = allNodes[i];
|
|
86
|
+
const ns = n.namespaceURI;
|
|
87
|
+
if (ns && !reversedNs[ns]) {
|
|
88
|
+
let prefix = n.lookupPrefix(ns);
|
|
89
|
+
if (!prefix) {
|
|
90
|
+
const m = ns.match('.*/(\\w+)/?$');
|
|
91
|
+
prefix = m ? m[1] : 'xhtml';
|
|
92
|
+
}
|
|
93
|
+
reversedNs[ns] = prefix;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const namespaces = {};
|
|
97
|
+
for (const key in reversedNs) {
|
|
98
|
+
namespaces[reversedNs[key]] = key;
|
|
99
|
+
}
|
|
100
|
+
resolver = (prefix) => namespaces[prefix || ''] || null;
|
|
101
|
+
}
|
|
102
|
+
let result = null;
|
|
103
|
+
try {
|
|
104
|
+
result = doc.evaluate(target, root, resolver, ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
105
|
+
}
|
|
106
|
+
catch (te) {
|
|
107
|
+
if (te.name === 'TypeError') {
|
|
108
|
+
const fallback = doc.createNSResolver ? doc.createNSResolver(doc.documentElement) : DEFAULT_NS_RESOLVER;
|
|
109
|
+
result = doc.evaluate(target, root, fallback, ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
throw te;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!result) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const results = [];
|
|
119
|
+
for (let i = 0; i < result.snapshotLength; i++) {
|
|
120
|
+
const node = result.snapshotItem(i);
|
|
121
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) {
|
|
122
|
+
throw botError(INVALID_SELECTOR, 'The result of the xpath expression "' + target + '" is: ' + node + '. It should be an element.');
|
|
123
|
+
}
|
|
124
|
+
results.push(node);
|
|
125
|
+
}
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
catch (ex) {
|
|
129
|
+
if (ex.name === 'NS_ERROR_ILLEGAL_VALUE') {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
if (ex.code === INVALID_SELECTOR) {
|
|
133
|
+
throw ex;
|
|
134
|
+
}
|
|
135
|
+
throw botError(INVALID_SELECTOR, 'Unable to locate an element with the xpath expression ' + target + ' because of the following error:\n' + ex);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function getClientRect(element) {
|
|
139
|
+
const r = element.getBoundingClientRect();
|
|
140
|
+
return { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
141
|
+
}
|
|
142
|
+
function resolveAnchor(selector) {
|
|
143
|
+
if (selector instanceof Element) {
|
|
144
|
+
return selector;
|
|
145
|
+
}
|
|
146
|
+
if (typeof selector === 'function') {
|
|
147
|
+
return resolveAnchor(selector());
|
|
148
|
+
}
|
|
149
|
+
if (selector && typeof selector === 'object') {
|
|
150
|
+
const found = findElements(selector);
|
|
151
|
+
if (!found.length) {
|
|
152
|
+
throw botError(NO_SUCH_ELEMENT, 'No element has been found by ' + JSON.stringify(selector));
|
|
153
|
+
}
|
|
154
|
+
return found[0];
|
|
155
|
+
}
|
|
156
|
+
throw botError(INVALID_ARGUMENT, 'Selector is of wrong type: ' + JSON.stringify(selector));
|
|
157
|
+
}
|
|
158
|
+
function makeProximityFilter(selector, test) {
|
|
159
|
+
return (candidate) => test(getClientRect(resolveAnchor(selector)), getClientRect(candidate));
|
|
160
|
+
}
|
|
161
|
+
const RELATIVE_STRATEGIES = {
|
|
162
|
+
above: (sel) => makeProximityFilter(sel, (a, c) => c.top + c.height <= a.top),
|
|
163
|
+
below: (sel) => makeProximityFilter(sel, (a, c) => c.top >= a.top + a.height),
|
|
164
|
+
left: (sel) => makeProximityFilter(sel, (a, c) => c.left + c.width <= a.left),
|
|
165
|
+
right: (sel) => makeProximityFilter(sel, (a, c) => c.left >= a.left + a.width),
|
|
166
|
+
near: (sel, distArg) => {
|
|
167
|
+
const distFromSelector = typeof sel['distance'] === 'number'
|
|
168
|
+
? sel['distance']
|
|
169
|
+
: 0;
|
|
170
|
+
const distance = typeof distArg === 'number' && distArg > 0 ? distArg : distFromSelector || 50;
|
|
171
|
+
return (candidate) => {
|
|
172
|
+
const anchor = resolveAnchor(sel);
|
|
173
|
+
if (anchor === candidate)
|
|
174
|
+
return false;
|
|
175
|
+
const a = getClientRect(anchor);
|
|
176
|
+
const c = getClientRect(candidate);
|
|
177
|
+
const expanded = { left: a.left - distance, top: a.top - distance, width: a.width + distance * 2, height: a.height + distance * 2 };
|
|
178
|
+
return (c.left < expanded.left + expanded.width &&
|
|
179
|
+
c.left + c.width > expanded.left &&
|
|
180
|
+
c.top < expanded.top + expanded.height &&
|
|
181
|
+
c.top + c.height > expanded.top);
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
straightAbove: (sel) => makeProximityFilter(sel, (a, c) => c.left < a.left + a.width && c.left + c.width > a.left && c.top + c.height <= a.top),
|
|
185
|
+
straightBelow: (sel) => makeProximityFilter(sel, (a, c) => c.left < a.left + a.width && c.left + c.width > a.left && c.top >= a.top + a.height),
|
|
186
|
+
straightLeft: (sel) => makeProximityFilter(sel, (a, c) => c.top < a.top + a.height && c.top + c.height > a.top && c.left + c.width <= a.left),
|
|
187
|
+
straightRight: (sel) => makeProximityFilter(sel, (a, c) => c.top < a.top + a.height && c.top + c.height > a.top && c.left >= a.left + a.width),
|
|
188
|
+
};
|
|
189
|
+
function sortByProximity(anchor, elements) {
|
|
190
|
+
const ar = getClientRect(anchor);
|
|
191
|
+
const acx = ar.left + Math.max(1, ar.width) / 2;
|
|
192
|
+
const acy = ar.top + Math.max(1, ar.height) / 2;
|
|
193
|
+
return elements.slice().sort((a, b) => {
|
|
194
|
+
const ra = getClientRect(a);
|
|
195
|
+
const rb = getClientRect(b);
|
|
196
|
+
const da = Math.sqrt(Math.pow(acx - (ra.left + Math.max(1, ra.width) / 2), 2) + Math.pow(acy - (ra.top + Math.max(1, ra.height) / 2), 2));
|
|
197
|
+
const db = Math.sqrt(Math.pow(acx - (rb.left + Math.max(1, rb.width) / 2), 2) + Math.pow(acy - (rb.top + Math.max(1, rb.height) / 2), 2));
|
|
198
|
+
return da - db;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function relativeMany(target, root) {
|
|
202
|
+
if (!Object.prototype.hasOwnProperty.call(target, 'root') || !Object.prototype.hasOwnProperty.call(target, 'filters')) {
|
|
203
|
+
throw botError(INVALID_ARGUMENT, 'Locator not suitable for relative locators: ' + JSON.stringify(target));
|
|
204
|
+
}
|
|
205
|
+
const filters = target['filters'];
|
|
206
|
+
if (!Array.isArray(filters)) {
|
|
207
|
+
throw botError(INVALID_ARGUMENT, 'Targets should be an array: ' + JSON.stringify(target));
|
|
208
|
+
}
|
|
209
|
+
let elements;
|
|
210
|
+
const rootTarget = target['root'];
|
|
211
|
+
if (rootTarget instanceof Element) {
|
|
212
|
+
elements = [rootTarget];
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
elements = findElements(rootTarget, root);
|
|
216
|
+
}
|
|
217
|
+
if (!elements.length) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
const matched = elements.filter(el => {
|
|
221
|
+
if (!el)
|
|
222
|
+
return false;
|
|
223
|
+
return filters.every(filter => {
|
|
224
|
+
const strategy = RELATIVE_STRATEGIES[filter.kind];
|
|
225
|
+
if (!strategy) {
|
|
226
|
+
throw botError(INVALID_ARGUMENT, 'Cannot find filter suitable for ' + filter.kind);
|
|
227
|
+
}
|
|
228
|
+
return strategy(...filter.args)(el);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
const finalFilter = filters[filters.length - 1];
|
|
232
|
+
if (!finalFilter || !RELATIVE_STRATEGIES[finalFilter.kind]) {
|
|
233
|
+
return matched;
|
|
234
|
+
}
|
|
235
|
+
const lastAnchor = resolveAnchor(finalFilter.args[0]);
|
|
236
|
+
return sortByProximity(lastAnchor, matched);
|
|
237
|
+
}
|
|
238
|
+
const actualRoot = root || document;
|
|
239
|
+
const keys = Object.keys(target).filter(k => Object.prototype.hasOwnProperty.call(target, k));
|
|
240
|
+
if (!keys.length) {
|
|
241
|
+
throw botError(INVALID_ARGUMENT, 'Unsupported locator strategy: (empty)');
|
|
242
|
+
}
|
|
243
|
+
const key = keys[0];
|
|
244
|
+
const value = target[key];
|
|
245
|
+
switch (key) {
|
|
246
|
+
case 'className':
|
|
247
|
+
case 'class name':
|
|
248
|
+
return classNameMany(value, actualRoot);
|
|
249
|
+
case 'css':
|
|
250
|
+
case 'css selector':
|
|
251
|
+
return cssMany(value, actualRoot);
|
|
252
|
+
case 'id':
|
|
253
|
+
return idMany(value, actualRoot);
|
|
254
|
+
case 'linkText':
|
|
255
|
+
case 'link text':
|
|
256
|
+
return linkTextMany(value, actualRoot, false);
|
|
257
|
+
case 'partialLinkText':
|
|
258
|
+
case 'partial link text':
|
|
259
|
+
return linkTextMany(value, actualRoot, true);
|
|
260
|
+
case 'name':
|
|
261
|
+
return nameMany(value, actualRoot);
|
|
262
|
+
case 'tagName':
|
|
263
|
+
case 'tag name':
|
|
264
|
+
return tagNameMany(value, actualRoot);
|
|
265
|
+
case 'xpath':
|
|
266
|
+
return xpathMany(value, actualRoot);
|
|
267
|
+
case 'relative':
|
|
268
|
+
return relativeMany(value, actualRoot);
|
|
269
|
+
default:
|
|
270
|
+
throw botError(INVALID_ARGUMENT, 'Unsupported locator strategy: ' + key);
|
|
271
|
+
}
|
|
272
|
+
})
|
|
@@ -1,6 +1,152 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
(function (element, attribute) {
|
|
2
|
+
const PROPERTY_ALIASES = {
|
|
3
|
+
'class': 'className',
|
|
4
|
+
'readonly': 'readOnly',
|
|
5
|
+
};
|
|
6
|
+
const BOOLEAN_PROPERTIES = new Set([
|
|
7
|
+
'allowfullscreen',
|
|
8
|
+
'allowpaymentrequest',
|
|
9
|
+
'allowusermedia',
|
|
10
|
+
'async',
|
|
11
|
+
'autofocus',
|
|
12
|
+
'autoplay',
|
|
13
|
+
'checked',
|
|
14
|
+
'compact',
|
|
15
|
+
'complete',
|
|
16
|
+
'controls',
|
|
17
|
+
'declare',
|
|
18
|
+
'default',
|
|
19
|
+
'defaultchecked',
|
|
20
|
+
'defaultselected',
|
|
21
|
+
'defer',
|
|
22
|
+
'disabled',
|
|
23
|
+
'ended',
|
|
24
|
+
'formnovalidate',
|
|
25
|
+
'hidden',
|
|
26
|
+
'indeterminate',
|
|
27
|
+
'iscontenteditable',
|
|
28
|
+
'ismap',
|
|
29
|
+
'itemscope',
|
|
30
|
+
'loop',
|
|
31
|
+
'multiple',
|
|
32
|
+
'muted',
|
|
33
|
+
'nohref',
|
|
34
|
+
'nomodule',
|
|
35
|
+
'noresize',
|
|
36
|
+
'noshade',
|
|
37
|
+
'novalidate',
|
|
38
|
+
'nowrap',
|
|
39
|
+
'open',
|
|
40
|
+
'paused',
|
|
41
|
+
'playsinline',
|
|
42
|
+
'pubdate',
|
|
43
|
+
'readonly',
|
|
44
|
+
'required',
|
|
45
|
+
'reversed',
|
|
46
|
+
'scoped',
|
|
47
|
+
'seamless',
|
|
48
|
+
'seeking',
|
|
49
|
+
'selected',
|
|
50
|
+
'truespeed',
|
|
51
|
+
'typemustmatch',
|
|
52
|
+
'willvalidate',
|
|
53
|
+
]);
|
|
54
|
+
function getAttribute(element, attributeName) {
|
|
55
|
+
return element.getAttribute(attributeName.toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
function getProperty(element, propertyName) {
|
|
58
|
+
return element[propertyName];
|
|
59
|
+
}
|
|
60
|
+
function isElement(node, tagName) {
|
|
61
|
+
const elem = node;
|
|
62
|
+
if (!elem || elem.nodeType !== 1) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (!tagName) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const normalizedTag = tagName.toUpperCase();
|
|
69
|
+
if (node instanceof HTMLFormElement) {
|
|
70
|
+
return normalizedTag === 'FORM';
|
|
71
|
+
}
|
|
72
|
+
return typeof elem.tagName === 'string' && elem.tagName.toUpperCase() === normalizedTag;
|
|
73
|
+
}
|
|
74
|
+
function isSelectable(element) {
|
|
75
|
+
if (isElement(element, 'OPTION')) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (isElement(element, 'INPUT')) {
|
|
79
|
+
const type = element.type.toLowerCase();
|
|
80
|
+
return type === 'checkbox' || type === 'radio';
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function isSelected(element) {
|
|
85
|
+
var _a;
|
|
86
|
+
if (isElement(element, 'OPTION')) {
|
|
87
|
+
return element.selected;
|
|
88
|
+
}
|
|
89
|
+
const type = (_a = element.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
90
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
91
|
+
return element.checked;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function isObject(value) {
|
|
96
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function');
|
|
97
|
+
}
|
|
98
|
+
const name = attribute.toLowerCase();
|
|
99
|
+
if (name === 'style') {
|
|
100
|
+
const style = element.style;
|
|
101
|
+
if (!style) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return typeof style === 'string' ? style : style.cssText;
|
|
105
|
+
}
|
|
106
|
+
if ((name === 'selected' || name === 'checked') && isSelectable(element)) {
|
|
107
|
+
return isSelected(element) ? 'true' : null;
|
|
108
|
+
}
|
|
109
|
+
const isLink = isElement(element, 'A');
|
|
110
|
+
const isImg = isElement(element, 'IMG');
|
|
111
|
+
if ((isImg && name === 'src') || (isLink && name === 'href')) {
|
|
112
|
+
const attrValue = getAttribute(element, name);
|
|
113
|
+
if (attrValue) {
|
|
114
|
+
return String(getProperty(element, name));
|
|
115
|
+
}
|
|
116
|
+
return attrValue;
|
|
117
|
+
}
|
|
118
|
+
if (name === 'spellcheck') {
|
|
119
|
+
const attrValue = getAttribute(element, name);
|
|
120
|
+
if (attrValue !== null) {
|
|
121
|
+
const lower = attrValue.toLowerCase();
|
|
122
|
+
if (lower === 'false') {
|
|
123
|
+
return 'false';
|
|
124
|
+
}
|
|
125
|
+
if (lower === 'true') {
|
|
126
|
+
return 'true';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return String(getProperty(element, name));
|
|
130
|
+
}
|
|
131
|
+
const propName = PROPERTY_ALIASES[name] || attribute;
|
|
132
|
+
if (BOOLEAN_PROPERTIES.has(name)) {
|
|
133
|
+
const hasAttr = element.getAttribute(name) !== null;
|
|
134
|
+
const propValue = getProperty(element, propName);
|
|
135
|
+
return hasAttr || !!propValue ? 'true' : null;
|
|
136
|
+
}
|
|
137
|
+
if (name === 'value' && isElement(element, 'LI')) {
|
|
138
|
+
const attrValue = element.getAttribute(name);
|
|
139
|
+
return attrValue != null ? attrValue : null;
|
|
140
|
+
}
|
|
141
|
+
let property;
|
|
142
|
+
try {
|
|
143
|
+
property = getProperty(element, propName);
|
|
144
|
+
}
|
|
145
|
+
catch (_e) {
|
|
146
|
+
}
|
|
147
|
+
if (property == null || isObject(property)) {
|
|
148
|
+
const attrValue = element.getAttribute(name);
|
|
149
|
+
return attrValue != null ? attrValue : null;
|
|
150
|
+
}
|
|
151
|
+
return property != null ? String(property) : null;
|
|
152
|
+
})
|