apparition 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/capybara/apparition.rb +0 -2
- data/lib/capybara/apparition/browser.rb +18 -129
- data/lib/capybara/apparition/browser/cookie.rb +3 -15
- data/lib/capybara/apparition/browser/header.rb +2 -2
- data/lib/capybara/apparition/browser/page_manager.rb +84 -0
- data/lib/capybara/apparition/browser/window.rb +7 -7
- data/lib/capybara/apparition/driver.rb +5 -2
- data/lib/capybara/apparition/node.rb +64 -49
- data/lib/capybara/apparition/node/drag.rb +4 -4
- data/lib/capybara/apparition/page.rb +33 -19
- data/lib/capybara/apparition/page/frame_manager.rb +1 -0
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +47 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5675a80f435049166cf933b540e00b5569b0f516099597cdc071cc4c7767ff0
|
4
|
+
data.tar.gz: 8a63bf45f5376a4eb0a65954bd3863062f1aea67b030cb3ee2b96c7cfe1d108d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d0b64f71ec2e0a026bc5f0cea02588e550a5e27768b5368041bfbbd206e758654900cf22c35620d88f0618a76a12b874120513ae3d606e8ee481a5021bf3297
|
7
|
+
data.tar.gz: b279c8871baedd04acf1e717fddbf2898ae9796e0c1152fd17c0ccc160deaa10cb0cc86243037448883e10da3a90844351c60e097e0b6b5aebb8e87b0e796f65
|
data/lib/capybara/apparition.rb
CHANGED
@@ -9,6 +9,7 @@ require 'capybara/apparition/browser/window'
|
|
9
9
|
require 'capybara/apparition/browser/render'
|
10
10
|
require 'capybara/apparition/browser/cookie'
|
11
11
|
require 'capybara/apparition/browser/modal'
|
12
|
+
require 'capybara/apparition/browser/page_manager'
|
12
13
|
require 'capybara/apparition/browser/frame'
|
13
14
|
require 'capybara/apparition/browser/auth'
|
14
15
|
require 'json'
|
@@ -30,7 +31,7 @@ module Capybara::Apparition
|
|
30
31
|
def initialize(client, logger = nil)
|
31
32
|
@client = client
|
32
33
|
@current_page_handle = nil
|
33
|
-
@pages =
|
34
|
+
@pages = PageManager.new(self)
|
34
35
|
@context_id = nil
|
35
36
|
@js_errors = true
|
36
37
|
@ignore_https_errors = false
|
@@ -46,7 +47,7 @@ module Capybara::Apparition
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def restart
|
49
|
-
puts 'handle client restart'
|
50
|
+
# puts 'handle client restart'
|
50
51
|
# client.restart
|
51
52
|
|
52
53
|
self.debug = @debug if defined?(@debug)
|
@@ -79,35 +80,26 @@ module Capybara::Apparition
|
|
79
80
|
|
80
81
|
def reset
|
81
82
|
new_context_id = command('Target.createBrowserContext')['browserContextId']
|
82
|
-
# current_pages = @pages.keys
|
83
|
-
|
84
83
|
new_target_response = client.send_cmd('Target.createTarget', url: 'about:blank', browserContextId: new_context_id)
|
85
|
-
|
86
|
-
|
87
|
-
client.send_cmd('Target.disposeBrowserContext', browserContextId: page.browser_context_id).discard_result
|
88
|
-
rescue WrongWorld
|
89
|
-
puts 'Unknown browserContextId'
|
90
|
-
end
|
91
|
-
@pages.delete(id)
|
92
|
-
end
|
84
|
+
|
85
|
+
@pages.reset
|
93
86
|
|
94
87
|
new_target_id = new_target_response['targetId']
|
95
88
|
|
96
89
|
session_id = command('Target.attachToTarget', targetId: new_target_id)['sessionId']
|
97
90
|
session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
|
98
91
|
|
99
|
-
@pages
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
@pages[new_target_id].send(:main_frame).loaded!
|
92
|
+
@pages.create(new_target_id, session, new_context_id,
|
93
|
+
ignore_https_errors: ignore_https_errors,
|
94
|
+
js_errors: js_errors, extensions: @extensions,
|
95
|
+
url_blacklist: @url_blacklist,
|
96
|
+
url_whitelist: @url_whitelist) .send(:main_frame).loaded!
|
105
97
|
|
106
98
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
107
99
|
until @pages[new_target_id].usable?
|
108
100
|
if timer.expired?
|
109
101
|
puts 'Timedout waiting for reset'
|
110
|
-
raise TimeoutError
|
102
|
+
raise TimeoutError, 'reset'
|
111
103
|
end
|
112
104
|
sleep 0.01
|
113
105
|
end
|
@@ -117,42 +109,11 @@ module Capybara::Apparition
|
|
117
109
|
end
|
118
110
|
|
119
111
|
def refresh_pages(opener:)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
session_result = client.send_cmd('Target.attachToTarget', targetId: target_id)
|
126
|
-
[target_id, session_result]
|
127
|
-
end
|
128
|
-
|
129
|
-
sessions = sessions.map do |(target_id, session_result)|
|
130
|
-
session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_result.result['sessionId'])
|
131
|
-
[target_id, session]
|
132
|
-
end
|
133
|
-
|
134
|
-
sessions.each do |(_target_id, session)|
|
135
|
-
session.async_commands 'Page.enable', 'Network.enable', 'Runtime.enable', 'Security.enable', 'DOM.enable'
|
136
|
-
end
|
137
|
-
|
138
|
-
# sessions.each do |(target_id, session_result)|
|
139
|
-
# session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_result.result['sessionId'])
|
140
|
-
sessions.each do |(target_id, session)|
|
141
|
-
page_options = { ignore_https_errors: ignore_https_errors, js_errors: js_errors,
|
142
|
-
url_blacklist: @url_blacklist, url_whitelist: @url_whitelist }
|
143
|
-
new_page = Page.create(self, session, target_id, opener.browser_context_id, page_options).inherit(opener)
|
144
|
-
@pages[target_id] = new_page
|
145
|
-
end
|
146
|
-
|
147
|
-
# new_pages.each do |page|
|
148
|
-
# target_id = page['targetId']
|
149
|
-
# session_id = command('Target.attachToTarget', targetId: target_id)['sessionId']
|
150
|
-
# session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
|
151
|
-
# page_options = { ignore_https_errors: ignore_https_errors, js_errors: js_errors,
|
152
|
-
# url_blacklist: @url_blacklist, url_whitelist: @url_whitelist }
|
153
|
-
# new_page = Page.create(self, session, page['targetId'], opener.browser_context_id, page_options).inherit(opener)
|
154
|
-
# @pages[target_id] = new_page
|
155
|
-
# end
|
112
|
+
@pages.refresh(opener: opener,
|
113
|
+
ignore_https_errors: ignore_https_errors,
|
114
|
+
js_errors: js_errors,
|
115
|
+
url_blacklist: @url_blacklist,
|
116
|
+
url_whitelist: @url_whitelist)
|
156
117
|
end
|
157
118
|
|
158
119
|
def resize(width, height, screen: nil)
|
@@ -178,17 +139,11 @@ module Capybara::Apparition
|
|
178
139
|
end
|
179
140
|
|
180
141
|
def url_whitelist=(whitelist)
|
181
|
-
@url_whitelist = whitelist
|
182
|
-
@pages.each do |_id, page|
|
183
|
-
page.url_whitelist = whitelist
|
184
|
-
end
|
142
|
+
@url_whitelist = @pages.whitelist = whitelist
|
185
143
|
end
|
186
144
|
|
187
145
|
def url_blacklist=(blacklist)
|
188
|
-
@url_blacklist = blacklist
|
189
|
-
@pages.each do |_id, page|
|
190
|
-
page.url_blacklist = blacklist
|
191
|
-
end
|
146
|
+
@url_blacklist = @pages.blacklist = blacklist
|
192
147
|
end
|
193
148
|
|
194
149
|
attr_writer :debug
|
@@ -234,72 +189,6 @@ module Capybara::Apparition
|
|
234
189
|
@logger&.puts message if ENV['DEBUG']
|
235
190
|
end
|
236
191
|
|
237
|
-
# KEY_ALIASES = {
|
238
|
-
# command: :Meta,
|
239
|
-
# equals: :Equal,
|
240
|
-
# control: :Control,
|
241
|
-
# ctrl: :Control,
|
242
|
-
# multiply: 'numpad*',
|
243
|
-
# add: 'numpad+',
|
244
|
-
# divide: 'numpad/',
|
245
|
-
# subtract: 'numpad-',
|
246
|
-
# decimal: 'numpad.',
|
247
|
-
# left: 'ArrowLeft',
|
248
|
-
# right: 'ArrowRight',
|
249
|
-
# down: 'ArrowDown',
|
250
|
-
# up: 'ArrowUp'
|
251
|
-
# }.freeze
|
252
|
-
#
|
253
|
-
# def normalize_keys(keys)
|
254
|
-
# keys.map do |key_desc|
|
255
|
-
# case key_desc
|
256
|
-
# when Array
|
257
|
-
# # [:Shift, "s"] => { modifier: "shift", keys: "S" }
|
258
|
-
# # [:Shift, "string"] => { modifier: "shift", keys: "STRING" }
|
259
|
-
# # [:Ctrl, :Left] => { modifier: "ctrl", key: 'Left' }
|
260
|
-
# # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: 'Left' }
|
261
|
-
# # [:Ctrl, :Left, :Left] => { modifier: "ctrl", key: [:Left, :Left] }
|
262
|
-
# keys_chunks = key_desc.chunk do |k|
|
263
|
-
# k.is_a?(Symbol) && %w[shift ctrl control alt meta command].include?(k.to_s.downcase)
|
264
|
-
# end
|
265
|
-
# modifiers = modifiers_from_chunks(keys_chunks)
|
266
|
-
# letters = normalize_keys(_keys.next[1].map { |k| k.is_a?(String) ? k.upcase : k })
|
267
|
-
# { modifier: modifiers, keys: letters }
|
268
|
-
# when Symbol
|
269
|
-
# symbol_to_desc(key_desc)
|
270
|
-
# when String
|
271
|
-
# key_desc # Plain string, nothing to do
|
272
|
-
# end
|
273
|
-
# end
|
274
|
-
# end
|
275
|
-
#
|
276
|
-
# def modifiers_from_chunks(chunks)
|
277
|
-
# if chunks.peek[0]
|
278
|
-
# chunks.next[1].map do |k|
|
279
|
-
# k = k.to_s.downcase
|
280
|
-
# k = 'control' if k == 'ctrl'
|
281
|
-
# k = 'meta' if k == 'command'
|
282
|
-
# k
|
283
|
-
# end.join(',')
|
284
|
-
# else
|
285
|
-
# ''
|
286
|
-
# end
|
287
|
-
# end
|
288
|
-
#
|
289
|
-
# def symbol_to_desc(symbol)
|
290
|
-
# if symbol == :space
|
291
|
-
# res = ' '
|
292
|
-
# else
|
293
|
-
# key = KEY_ALIASES.fetch(symbol.downcase, symbol)
|
294
|
-
# if (match = key.to_s.match(/numpad(.)/))
|
295
|
-
# res = { keys: match[1], modifier: 'keypad' }
|
296
|
-
# elsif !/^[A-Z]/.match?(key)
|
297
|
-
# key = key.to_s.split('_').map(&:capitalize).join
|
298
|
-
# end
|
299
|
-
# end
|
300
|
-
# res || { key: key }
|
301
|
-
# end
|
302
|
-
|
303
192
|
def initialize_handlers
|
304
193
|
# @client.on 'Target.targetCreated' do |info|
|
305
194
|
# byebug
|
@@ -6,18 +6,9 @@ module Capybara::Apparition
|
|
6
6
|
class Browser
|
7
7
|
module Cookie
|
8
8
|
def cookies
|
9
|
-
CookieJar.new(
|
10
|
-
# current_page.command('Network.getCookies')['cookies'].map { |c| Cookie.new(c) }
|
11
|
-
self
|
12
|
-
)
|
13
|
-
end
|
14
|
-
|
15
|
-
def all_cookies
|
16
|
-
CookieJar.new(
|
17
|
-
# current_page.command('Network.getAllCookies')['cookies'].map { |c| Cookie.new(c) }
|
18
|
-
self
|
19
|
-
)
|
9
|
+
CookieJar.new(self)
|
20
10
|
end
|
11
|
+
alias :all_cookies :cookies
|
21
12
|
|
22
13
|
def get_raw_cookies
|
23
14
|
current_page.command('Network.getAllCookies')['cookies'].map do |c|
|
@@ -26,10 +17,7 @@ module Capybara::Apparition
|
|
26
17
|
end
|
27
18
|
|
28
19
|
def set_cookie(cookie)
|
29
|
-
if cookie[:expires]
|
30
|
-
# cookie[:expires] = cookie[:expires].to_i * 1000
|
31
|
-
cookie[:expires] = cookie[:expires].to_i
|
32
|
-
end
|
20
|
+
cookie[:expires] = cookie[:expires].to_i if cookie[:expires]
|
33
21
|
|
34
22
|
current_page.command('Network.setCookie', cookie)
|
35
23
|
end
|
@@ -8,7 +8,7 @@ module Capybara::Apparition
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def headers=(headers)
|
11
|
-
@pages.each do |
|
11
|
+
@pages.each do |page|
|
12
12
|
page.perm_headers = headers.dup
|
13
13
|
page.temp_headers = {}
|
14
14
|
page.temp_no_redirect_headers = {}
|
@@ -23,7 +23,7 @@ module Capybara::Apparition
|
|
23
23
|
|
24
24
|
def add_header(header, permanent: true, **_options)
|
25
25
|
if permanent == true
|
26
|
-
@pages.each do |
|
26
|
+
@pages.each do |page|
|
27
27
|
page.perm_headers.merge! header
|
28
28
|
page.update_headers
|
29
29
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
class PageManager
|
6
|
+
def initialize(browser)
|
7
|
+
@browser = browser
|
8
|
+
@pages = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def ids
|
12
|
+
@pages.keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](id)
|
16
|
+
@pages[id]
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
@pages.each_value(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset
|
24
|
+
@pages.each do |id, page|
|
25
|
+
begin
|
26
|
+
@browser.client.send_cmd(
|
27
|
+
'Target.disposeBrowserContext',
|
28
|
+
browserContextId: page.browser_context_id
|
29
|
+
).discard_result
|
30
|
+
rescue WrongWorld
|
31
|
+
puts 'Unknown browserContextId'
|
32
|
+
end
|
33
|
+
@pages.delete(id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create(id, session, ctx_id, **options)
|
38
|
+
@pages[id] = Page.create(@browser, session, id, ctx_id, **options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(id)
|
42
|
+
@pages.delete(id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def refresh(opener:, **page_options)
|
46
|
+
new_pages = @browser.command('Target.getTargets')['targetInfos'].select do |ti|
|
47
|
+
(ti['openerId'] == opener.target_id) && (ti['type'] == 'page') && (ti['attached'] == false)
|
48
|
+
end
|
49
|
+
|
50
|
+
sessions = new_pages.map do |page|
|
51
|
+
target_id = page['targetId']
|
52
|
+
session_result = @browser.client.send_cmd('Target.attachToTarget', targetId: target_id)
|
53
|
+
[target_id, session_result]
|
54
|
+
end
|
55
|
+
|
56
|
+
sessions = sessions.map do |(target_id, session_result)|
|
57
|
+
session = Capybara::Apparition::DevToolsProtocol::Session.new(
|
58
|
+
@browser,
|
59
|
+
@browser.client,
|
60
|
+
session_result.result['sessionId']
|
61
|
+
)
|
62
|
+
[target_id, session]
|
63
|
+
end
|
64
|
+
|
65
|
+
sessions.each do |(_id, session)|
|
66
|
+
session.async_commands 'Page.enable', 'Network.enable', 'Runtime.enable', 'Security.enable', 'DOM.enable'
|
67
|
+
end
|
68
|
+
|
69
|
+
sessions.each do |(target_id, session)|
|
70
|
+
new_page = Page.create(@browser, session, target_id, opener.browser_context_id, page_options).inherit(opener)
|
71
|
+
@pages[target_id] = new_page
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def whitelist=(list)
|
76
|
+
@pages.each_value { |page| page.url_whitelist = list }
|
77
|
+
end
|
78
|
+
|
79
|
+
def blacklist=(list)
|
80
|
+
@pages.each_value { |page| page.url_blacklist = list }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -8,7 +8,7 @@ module Capybara::Apparition
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def window_handles
|
11
|
-
@pages.
|
11
|
+
@pages.ids
|
12
12
|
end
|
13
13
|
|
14
14
|
def switch_to_window(handle)
|
@@ -29,12 +29,12 @@ module Capybara::Apparition
|
|
29
29
|
target_id = command('Target.createTarget', url: 'about:blank', browserContextId: context_id)['targetId']
|
30
30
|
session_id = command('Target.attachToTarget', targetId: target_id)['sessionId']
|
31
31
|
session = Capybara::Apparition::DevToolsProtocol::Session.new(self, client, session_id)
|
32
|
-
@pages
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
@pages.create(target_id, session, context_id,
|
33
|
+
ignore_https_errors: ignore_https_errors,
|
34
|
+
js_errors: js_errors,
|
35
|
+
url_whitelist: @url_whitelist,
|
36
|
+
extensions: @extensions,
|
37
|
+
url_blacklist: @url_blacklist).inherit(current_page(allow_nil: true))
|
38
38
|
@pages[target_id].send(:main_frame).loaded!
|
39
39
|
target_id
|
40
40
|
end
|
@@ -100,7 +100,9 @@ module Capybara::Apparition
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def find(method, selector)
|
103
|
-
browser.find(method, selector).map
|
103
|
+
browser.find(method, selector).map do |page_id, id, attrs|
|
104
|
+
Capybara::Apparition::Node.new(self, page_id, id, attrs)
|
105
|
+
end
|
104
106
|
end
|
105
107
|
|
106
108
|
def find_xpath(selector)
|
@@ -521,7 +523,8 @@ module Capybara::Apparition
|
|
521
523
|
object_cache[arg]
|
522
524
|
when Hash
|
523
525
|
if (arg['subtype'] == 'node') && arg['objectId']
|
524
|
-
|
526
|
+
tag_name = arg['description'].split(/[\.#]/, 2)[0]
|
527
|
+
Capybara::Apparition::Node.new(self, browser.current_page, arg['objectId'], tag_name: tag_name)
|
525
528
|
else
|
526
529
|
object_cache[arg] = {}
|
527
530
|
arg.each { |k, v| object_cache[arg][k] = unwrap_script_result(v, object_cache) }
|
@@ -11,8 +11,8 @@ module Capybara::Apparition
|
|
11
11
|
|
12
12
|
attr_reader :page_id
|
13
13
|
|
14
|
-
def initialize(driver, page, remote_object)
|
15
|
-
super(driver, self)
|
14
|
+
def initialize(driver, page, remote_object, initial_cache)
|
15
|
+
super(driver, self, initial_cache)
|
16
16
|
@page = page
|
17
17
|
@remote_object = remote_object
|
18
18
|
end
|
@@ -30,7 +30,8 @@ module Capybara::Apparition
|
|
30
30
|
def find(method, selector)
|
31
31
|
js = method == :css ? FIND_CSS_JS : FIND_XPATH_JS
|
32
32
|
evaluate_on(js, value: selector).map do |r_o|
|
33
|
-
|
33
|
+
tag_name = r_o['description'].split(/[\.#]/, 2)[0]
|
34
|
+
Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
|
34
35
|
end
|
35
36
|
rescue ::Capybara::Apparition::BrowserError => e
|
36
37
|
raise unless /is not a valid (XPath expression|selector)/.match? e.name
|
@@ -165,8 +166,19 @@ module Capybara::Apparition
|
|
165
166
|
evaluate_on VISIBLE_JS
|
166
167
|
end
|
167
168
|
|
168
|
-
def obscured?(
|
169
|
-
|
169
|
+
def obscured?(**)
|
170
|
+
pos = visible_center(allow_scroll: false)
|
171
|
+
return true if pos.nil?
|
172
|
+
|
173
|
+
hit_node = @page.element_from_point(pos)
|
174
|
+
return true if hit_node.nil?
|
175
|
+
|
176
|
+
begin
|
177
|
+
return evaluate_on('el => !this.contains(el)', objectId: hit_node['objectId'])
|
178
|
+
rescue WrongWorld # rubocop:disable Lint/HandleExceptions
|
179
|
+
end
|
180
|
+
|
181
|
+
true
|
170
182
|
end
|
171
183
|
|
172
184
|
def checked?
|
@@ -256,9 +268,9 @@ module Capybara::Apparition
|
|
256
268
|
false
|
257
269
|
end
|
258
270
|
|
259
|
-
def send_keys(*keys, delay: 0, **
|
271
|
+
def send_keys(*keys, delay: 0, **opts)
|
260
272
|
click unless evaluate_on CURRENT_NODE_SELECTED_JS
|
261
|
-
|
273
|
+
_send_keys(*keys, delay: delay, **opts)
|
262
274
|
end
|
263
275
|
alias_method :send_key, :send_keys
|
264
276
|
|
@@ -311,8 +323,8 @@ module Capybara::Apparition
|
|
311
323
|
end
|
312
324
|
end
|
313
325
|
|
314
|
-
def visible_center
|
315
|
-
rect = in_view_bounding_rect
|
326
|
+
def visible_center(allow_scroll: true)
|
327
|
+
rect = in_view_bounding_rect(allow_scroll: allow_scroll)
|
316
328
|
return nil if rect.nil?
|
317
329
|
|
318
330
|
frame_offset = @page.current_frame_offset
|
@@ -405,8 +417,38 @@ module Capybara::Apparition
|
|
405
417
|
|
406
418
|
private
|
407
419
|
|
408
|
-
def
|
409
|
-
|
420
|
+
def focus
|
421
|
+
@page.command('DOM.focus', objectId: id)
|
422
|
+
end
|
423
|
+
|
424
|
+
def keys_to_send(value, clear)
|
425
|
+
case clear
|
426
|
+
when :backspace
|
427
|
+
# Clear field by sending the correct number of backspace keys.
|
428
|
+
[:end] + ([:backspace] * self.value.to_s.length) + [value]
|
429
|
+
when :none
|
430
|
+
[value]
|
431
|
+
when Array
|
432
|
+
clear << value
|
433
|
+
else
|
434
|
+
# Clear field by JavaScript assignment of the value property.
|
435
|
+
# Script can change a readonly element which user input cannot, so
|
436
|
+
# don't execute if readonly.
|
437
|
+
driver.execute_script <<~JS, self
|
438
|
+
if (!arguments[0].readOnly) {
|
439
|
+
arguments[0].value = ''
|
440
|
+
}
|
441
|
+
JS
|
442
|
+
[value]
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def _send_keys(*keys, delay: 0, **_opts)
|
447
|
+
@page.keyboard.type(keys, delay: delay)
|
448
|
+
end
|
449
|
+
|
450
|
+
def in_view_bounding_rect(allow_scroll: true)
|
451
|
+
evaluate_on('() => this.scrollIntoViewIfNeeded()') if allow_scroll
|
410
452
|
result = evaluate_on GET_BOUNDING_CLIENT_RECT_JS
|
411
453
|
result = result['model'] if result && result['model']
|
412
454
|
result
|
@@ -437,22 +479,9 @@ module Capybara::Apparition
|
|
437
479
|
value = value.to_s
|
438
480
|
if value.empty? && clear.nil?
|
439
481
|
evaluate_on CLEAR_ELEMENT_JS
|
440
|
-
elsif clear == :backspace
|
441
|
-
# Clear field by sending the correct number of backspace keys.
|
442
|
-
backspaces = [:backspace] * self.value.to_s.length
|
443
|
-
send_keys(*([:end] + backspaces + [value]), delay: delay)
|
444
|
-
elsif clear.is_a? Array
|
445
|
-
send_keys(*clear, value, delay: delay)
|
446
482
|
else
|
447
|
-
|
448
|
-
|
449
|
-
# don't execute if readonly.
|
450
|
-
driver.execute_script <<~JS, self unless clear == :none
|
451
|
-
if (!arguments[0].readOnly) {
|
452
|
-
arguments[0].value = ''
|
453
|
-
}
|
454
|
-
JS
|
455
|
-
send_keys(value, delay: delay)
|
483
|
+
focus
|
484
|
+
_send_keys(*keys_to_send(value, clear), delay: delay)
|
456
485
|
end
|
457
486
|
end
|
458
487
|
|
@@ -511,7 +540,8 @@ module Capybara::Apparition
|
|
511
540
|
r_o = @page.element_from_point(x: x, y: y)
|
512
541
|
return nil unless r_o && r_o['objectId']
|
513
542
|
|
514
|
-
|
543
|
+
tag_name = r_o['description'].split(/[\.#]/, 2)[0]
|
544
|
+
hit_node = Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
|
515
545
|
result = begin
|
516
546
|
evaluate_on(<<~JS, objectId: hit_node.id)
|
517
547
|
(hit_node) => {
|
@@ -681,11 +711,14 @@ module Capybara::Apparition
|
|
681
711
|
sel = sel.parentNode;
|
682
712
|
}
|
683
713
|
let event_options = { bubbles: true, cancelable: true };
|
714
|
+
sel.dispatchEvent(new MouseEvent('mousedown', event_options));
|
684
715
|
sel.dispatchEvent(new FocusEvent('focus', event_options));
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
716
|
+
if (this.selected == false){
|
717
|
+
this.selected = true;
|
718
|
+
sel.dispatchEvent(new Event('change', event_options));
|
719
|
+
}
|
720
|
+
sel.dispatchEvent(new MouseEvent('mouseup', event_options));
|
721
|
+
sel.dispatchEvent(new MouseEvent('click', event_options));
|
689
722
|
sel.dispatchEvent(new FocusEvent('blur', event_options));
|
690
723
|
}
|
691
724
|
JS
|
@@ -733,24 +766,6 @@ module Capybara::Apparition
|
|
733
766
|
}
|
734
767
|
JS
|
735
768
|
|
736
|
-
OBSCURED_JS = <<~JS
|
737
|
-
function(x, y) {
|
738
|
-
var box = this.getBoundingClientRect();
|
739
|
-
if (x == null) x = box.width/2;
|
740
|
-
if (y == null) y = box.height/2 ;
|
741
|
-
|
742
|
-
var px = box.left + x,
|
743
|
-
py = box.top + y,
|
744
|
-
e = document.elementFromPoint(px, py);
|
745
|
-
|
746
|
-
if (!this.contains(e))
|
747
|
-
return true;
|
748
|
-
|
749
|
-
return { x: px, y: py };
|
750
|
-
}
|
751
|
-
JS
|
752
|
-
|
753
|
-
|
754
769
|
DELETE_TEXT_JS = <<~JS
|
755
770
|
function(){
|
756
771
|
range = document.createRange();
|
@@ -42,16 +42,17 @@ module Capybara::Apparition
|
|
42
42
|
def drop(*args)
|
43
43
|
if args[0].is_a? String
|
44
44
|
input = evaluate_on ATTACH_FILE
|
45
|
-
|
45
|
+
tag_name = input['description'].split(/[\.#]/, 2)[0]
|
46
|
+
input = Capybara::Apparition::Node.new(driver, @page, input['objectId'], tag_name: tag_name)
|
46
47
|
input.set(args)
|
47
|
-
evaluate_on DROP_FILE,
|
48
|
+
evaluate_on DROP_FILE, objectId: input.id
|
48
49
|
else
|
49
50
|
items = args.each_with_object([]) do |arg, arr|
|
50
51
|
arg.each_with_object(arr) do |(type, data), arr_|
|
51
52
|
arr_ << { type: type, data: data }
|
52
53
|
end
|
53
54
|
end
|
54
|
-
evaluate_on DROP_STRING,
|
55
|
+
evaluate_on DROP_STRING, value: items
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
@@ -103,7 +104,6 @@ module Capybara::Apparition
|
|
103
104
|
}
|
104
105
|
JS
|
105
106
|
|
106
|
-
|
107
107
|
private
|
108
108
|
|
109
109
|
def html5_drag_to(element)
|
@@ -145,10 +145,11 @@ module Capybara::Apparition
|
|
145
145
|
pixel_ratio = evaluate('window.devicePixelRatio')
|
146
146
|
scale = (@browser.zoom_factor || 1).to_f / pixel_ratio
|
147
147
|
if options[:format].to_s == 'pdf'
|
148
|
-
params = {}
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
params = { scale: scale }
|
149
|
+
if @browser.paper_size
|
150
|
+
params[:paperWidth] = @browser.paper_size[:width].to_f
|
151
|
+
params[:paperHeight] = @browser.paper_size[:height].to_f
|
152
|
+
end
|
152
153
|
command('Page.printToPDF', params)
|
153
154
|
else
|
154
155
|
clip_options = if options[:selector]
|
@@ -173,15 +174,14 @@ module Capybara::Apparition
|
|
173
174
|
frame_id = node['node']['frameId']
|
174
175
|
|
175
176
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
176
|
-
while (frame = @frames
|
177
|
+
while (frame = @frames[frame_id]).nil? || frame.loading?
|
177
178
|
# Wait for the frame creation messages to be processed
|
178
179
|
if timer.expired?
|
179
|
-
puts 'Timed out waiting
|
180
|
+
puts 'Timed out waiting for frame to be ready'
|
180
181
|
raise TimeoutError.new('push_frame')
|
181
182
|
end
|
182
183
|
sleep 0.1
|
183
184
|
end
|
184
|
-
return unless frame
|
185
185
|
|
186
186
|
frame.element_id = frame_el.base.id
|
187
187
|
@frames.push_frame(frame.id)
|
@@ -196,8 +196,8 @@ module Capybara::Apparition
|
|
196
196
|
wait_for_loaded
|
197
197
|
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
198
198
|
query = method == :css ? CSS_FIND_JS : XPATH_FIND_JS
|
199
|
-
result = _raw_evaluate(query
|
200
|
-
(result || []).map { |r_o| [self, r_o['objectId']] }
|
199
|
+
result = _raw_evaluate(format(query, selector: js_escaped_selector))
|
200
|
+
(result || []).map { |r_o| [self, r_o['objectId'], tag_name: r_o['description'].split(/[\.#]/, 2)[0]] }
|
201
201
|
rescue ::Capybara::Apparition::BrowserError => e
|
202
202
|
raise unless /is not a valid (XPath expression|selector)/.match? e.name
|
203
203
|
|
@@ -241,15 +241,29 @@ module Capybara::Apparition
|
|
241
241
|
attr_reader :status_code
|
242
242
|
|
243
243
|
def wait_for_loaded(allow_obsolete: false)
|
244
|
+
# We can't reliably detect if the page is loaded, so just ensure the context
|
245
|
+
# is usable
|
244
246
|
timer = Capybara::Helpers.timer(expire_in: 30)
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
247
|
+
page_function = '(function(){ return 1 == 1; })()'
|
248
|
+
begin
|
249
|
+
response = command('Runtime.evaluate',
|
250
|
+
expression: page_function,
|
251
|
+
contextId: current_frame.context_id,
|
252
|
+
returnByValue: false,
|
253
|
+
awaitPromise: true)
|
254
|
+
process_response(response)
|
255
|
+
current_frame.loaded!
|
256
|
+
rescue # rubocop:disable Style/RescueStandardError
|
257
|
+
return if allow_obsolete && current_frame.obsolete?
|
258
|
+
|
259
|
+
unless timer.expired?
|
260
|
+
sleep 0.05
|
261
|
+
retry
|
250
262
|
end
|
251
|
-
|
263
|
+
puts 'Timedout waiting for page to be loaded' if ENV['DEBUG']
|
264
|
+
raise TimeoutError.new('wait_for_loaded')
|
252
265
|
end
|
266
|
+
|
253
267
|
raise JavascriptError.new(js_error) if @js_error
|
254
268
|
end
|
255
269
|
|
@@ -702,8 +716,8 @@ module Capybara::Apparition
|
|
702
716
|
def process_response(response)
|
703
717
|
return nil if response.nil?
|
704
718
|
|
705
|
-
|
706
|
-
if
|
719
|
+
exception = response['exceptionDetails']&.dig('exception')
|
720
|
+
if exception
|
707
721
|
case exception['className']
|
708
722
|
when 'DOMException'
|
709
723
|
raise ::Capybara::Apparition::BrowserError.new('name' => exception['description'], 'args' => nil)
|
@@ -798,12 +812,12 @@ module Capybara::Apparition
|
|
798
812
|
JS
|
799
813
|
|
800
814
|
CSS_FIND_JS = <<~JS
|
801
|
-
Array.from(document.querySelectorAll("
|
815
|
+
Array.from(document.querySelectorAll("%<selector>s"));
|
802
816
|
JS
|
803
817
|
|
804
818
|
XPATH_FIND_JS = <<~JS
|
805
819
|
(function(){
|
806
|
-
const xpath = document.evaluate("
|
820
|
+
const xpath = document.evaluate("%<selector>s", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
807
821
|
let results = [];
|
808
822
|
for (let i=0; i < xpath.snapshotLength; i++){
|
809
823
|
results.push(xpath.snapshotItem(i))
|
metadata
CHANGED
@@ -1,36 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apparition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Walpole
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: backports
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: capybara
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '3.
|
19
|
+
version: '3.13'
|
34
20
|
- - "<"
|
35
21
|
- !ruby/object:Gem::Version
|
36
22
|
version: '4'
|
@@ -40,7 +26,7 @@ dependencies:
|
|
40
26
|
requirements:
|
41
27
|
- - "~>"
|
42
28
|
- !ruby/object:Gem::Version
|
43
|
-
version: '3.
|
29
|
+
version: '3.13'
|
44
30
|
- - "<"
|
45
31
|
- !ruby/object:Gem::Version
|
46
32
|
version: '4'
|
@@ -184,6 +170,48 @@ dependencies:
|
|
184
170
|
- - "~>"
|
185
171
|
- !ruby/object:Gem::Version
|
186
172
|
version: '3.6'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rubocop
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
- !ruby/object:Gem::Dependency
|
188
|
+
name: rubocop-performance
|
189
|
+
requirement: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
type: :development
|
195
|
+
prerelease: false
|
196
|
+
version_requirements: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: rubocop-rspec
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '0'
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '0'
|
187
215
|
- !ruby/object:Gem::Dependency
|
188
216
|
name: selenium-webdriver
|
189
217
|
requirement: !ruby/object:Gem::Requirement
|
@@ -229,6 +257,7 @@ files:
|
|
229
257
|
- lib/capybara/apparition/browser/frame.rb
|
230
258
|
- lib/capybara/apparition/browser/header.rb
|
231
259
|
- lib/capybara/apparition/browser/modal.rb
|
260
|
+
- lib/capybara/apparition/browser/page_manager.rb
|
232
261
|
- lib/capybara/apparition/browser/render.rb
|
233
262
|
- lib/capybara/apparition/browser/window.rb
|
234
263
|
- lib/capybara/apparition/configuration.rb
|