selenium-webdriver 4.43.0 → 4.45.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 +18 -0
- data/Gemfile +1 -4
- data/README.md +43 -23
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/webdriver/atoms/findElements.js +272 -68
- data/lib/selenium/webdriver/atoms/getAttribute.js +152 -17
- data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -39
- data/lib/selenium/webdriver/chromium/options.rb +3 -1
- data/lib/selenium/webdriver/chromium/profile.rb +6 -0
- data/lib/selenium/webdriver/common/driver_finder.rb +32 -23
- data/lib/selenium/webdriver/common/local_driver.rb +4 -8
- data/lib/selenium/webdriver/common/service.rb +1 -11
- data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +1 -1
- data/lib/selenium/webdriver/firefox/service.rb +22 -12
- data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
- data/lib/selenium/webdriver/safari/options.rb +8 -4
- data/lib/selenium/webdriver/safari.rb +1 -6
- data/lib/selenium/webdriver/support/guards/guard.rb +10 -11
- data/lib/selenium/webdriver/support/guards.rb +2 -1
- data/lib/selenium/webdriver/version.rb +1 -1
- data/selenium-webdriver.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b441a6e8d993250a3931dd136edc5749b16ab9b0b08bdb673d1e8b4f734418f0
|
|
4
|
+
data.tar.gz: 784b3693ca66a564c57d68cab9b132e84c44225fb5615fb6d4587d9abaf2643c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e317799f8e4cae55972263b3c866f08d48cf9029df08e3d1263cebc65da213e5e5758637d38edbf27fd36fe8f7c852b7358022e4ab8b860438fa41d1dbf465a2
|
|
7
|
+
data.tar.gz: 94213e5c29e70daf13fc950994531983e1e453ebe169f8e43888e76dfa5a85ea1ee854653b516456547f467ce3ca7bb1c7e6d5e9a41380e1de55ced708f79c54
|
data/CHANGES
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
4.45.0 (2026-06-15)
|
|
2
|
+
=========================
|
|
3
|
+
* Support CDP versions: v147, v148, v149
|
|
4
|
+
* deprecate curb http client support (#17443)
|
|
5
|
+
* move Ruby bindings to use typescript get attribute atom (#17524)
|
|
6
|
+
* Move atoms to use the typescript versions (#17532)
|
|
7
|
+
* deprecate Chromium Profile classes (#17557)
|
|
8
|
+
* update bazel test tags (#17558)
|
|
9
|
+
* separate concerns between Service, DriverFinder, and Options (#17564)
|
|
10
|
+
* fix using environment variables to set drivers (#17571)
|
|
11
|
+
* create more obvious test guard keywords as aliases (#17636)
|
|
12
|
+
|
|
13
|
+
4.44.0 (2026-05-12)
|
|
14
|
+
=========================
|
|
15
|
+
* Support CDP versions: v146, v147, v148
|
|
16
|
+
* Modern Firefox does not like both the `-v` and `--log` flags at the same time (#17412)
|
|
17
|
+
* Fix credential issue with private key (#17188)
|
|
18
|
+
|
|
1
19
|
4.43.0 (2026-04-09)
|
|
2
20
|
=========================
|
|
3
21
|
* Support CDP versions: v145, v146, v147
|
data/Gemfile
CHANGED
|
@@ -5,8 +5,5 @@ Dir["#{__dir__}/*.gemspec"].each do |spec|
|
|
|
5
5
|
gemspec name: File.basename(spec, '.gemspec')
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
# ActiveSupport 8.x requires Ruby 3.2+ (dependency of Steep)
|
|
9
|
-
gem 'activesupport', '~> 7.0', require: false, platforms: %i[mri mingw x64_mingw]
|
|
10
|
-
gem 'curb', '~> 1.0.5', require: false, platforms: %i[mri mingw x64_mingw]
|
|
11
8
|
gem 'debug', '~> 1.7', require: false, platforms: %i[mri mingw x64_mingw]
|
|
12
|
-
gem 'steep', '~>
|
|
9
|
+
gem 'steep', '~> 2.0', require: false, platforms: %i[mri mingw x64_mingw]
|
data/README.md
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
# selenium-webdriver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ruby language bindings for [Selenium WebDriver](https://www.selenium.dev).
|
|
4
|
+
Selenium automates browsers for testing and web-based task automation.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Supports MRI >= 3.3.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Installation
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
```bash
|
|
11
|
+
gem install selenium-webdriver
|
|
12
|
+
```
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
* https://www.selenium.dev/selenium/docs/api/rb/index.html
|
|
13
|
-
* https://www.selenium.dev/documentation/?tab=ruby
|
|
14
|
-
* https://github.com/SeleniumHQ/selenium/issues
|
|
14
|
+
## Quick Start
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
```ruby
|
|
17
|
+
require "selenium-webdriver"
|
|
18
|
+
|
|
19
|
+
driver = Selenium::WebDriver.for :chrome
|
|
20
|
+
begin
|
|
21
|
+
driver.get "https://www.selenium.dev"
|
|
22
|
+
puts driver.title
|
|
23
|
+
ensure
|
|
24
|
+
driver.quit
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Selenium Manager automatically handles browser driver installation — no manual driver setup required.
|
|
29
|
+
|
|
30
|
+
## Documentation
|
|
31
|
+
|
|
32
|
+
- [Getting Started](https://www.selenium.dev/documentation/webdriver/getting_started/)
|
|
33
|
+
- [Ruby API Docs](https://www.selenium.dev/selenium/docs/api/rb/index.html)
|
|
34
|
+
- [Selenium Manager](https://www.selenium.dev/documentation/selenium_manager/)
|
|
35
|
+
- [Selenium Grid](https://www.selenium.dev/documentation/grid/)
|
|
17
36
|
|
|
18
|
-
|
|
37
|
+
## Support
|
|
19
38
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
distributed with this work for additional information
|
|
23
|
-
regarding copyright ownership. The SFC licenses this file
|
|
24
|
-
to you under the Apache License, Version 2.0 (the
|
|
25
|
-
"License"); you may not use this file except in compliance
|
|
26
|
-
with the License. You may obtain a copy of the License at
|
|
39
|
+
- [Selenium Chat](https://www.selenium.dev/support/#ChatRoom)
|
|
40
|
+
- [GitHub Issues](https://github.com/SeleniumHQ/selenium/issues)
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
## Contributing
|
|
43
|
+
|
|
44
|
+
Contributions are welcome via [GitHub](https://github.com/SeleniumHQ/selenium/) pull requests.
|
|
45
|
+
See the [source code](https://github.com/SeleniumHQ/selenium/tree/trunk/rb) for this binding.
|
|
46
|
+
|
|
47
|
+
## Links
|
|
48
|
+
|
|
49
|
+
- [RubyGems](https://rubygems.org/gems/selenium-webdriver)
|
|
50
|
+
- [Documentation](https://www.selenium.dev/documentation/?tab=ruby)
|
|
51
|
+
|
|
52
|
+
## License
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
32
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
33
|
-
See the License for the specific language governing permissions and
|
|
34
|
-
limitations under the License.
|
|
54
|
+
Licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
data/bin/linux/selenium-manager
CHANGED
|
Binary file
|
data/bin/macos/selenium-manager
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -1,68 +1,272 @@
|
|
|
1
|
-
function()
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
})
|