apparition 0.0.6 → 0.1.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/README.md +1 -2
- data/lib/capybara/apparition/browser.rb +102 -293
- data/lib/capybara/apparition/browser/auth.rb +28 -0
- data/lib/capybara/apparition/browser/cookie.rb +50 -0
- data/lib/capybara/apparition/browser/frame.rb +18 -0
- data/lib/capybara/apparition/browser/header.rb +41 -0
- data/lib/capybara/apparition/browser/modal.rb +31 -0
- data/lib/capybara/apparition/browser/render.rb +52 -0
- data/lib/capybara/apparition/browser/window.rb +66 -0
- data/lib/capybara/apparition/dev_tools_protocol/target.rb +19 -7
- data/lib/capybara/apparition/dev_tools_protocol/target_manager.rb +13 -2
- data/lib/capybara/apparition/driver.rb +6 -1
- data/lib/capybara/apparition/driver/chrome_client.rb +1 -0
- data/lib/capybara/apparition/driver/response.rb +4 -0
- data/lib/capybara/apparition/node.rb +14 -10
- data/lib/capybara/apparition/page.rb +7 -3
- data/lib/capybara/apparition/page/keyboard.rb +11 -6
- data/lib/capybara/apparition/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b51cb57f961f9825c56ce38377bbf4fd5477a5cdad6d75a2dac1aab827b81561
|
4
|
+
data.tar.gz: 29e9b0c81cdcd3be2820a8d9a2719708750d3099f56a6c403c474a77bb254bce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95be5f025a29554e4909b5e4500d2694f580b20ee92b9cf555c7fcf2a9bc1962871d95c81730d9800a55d8cc38f99656713f88cd10f0f7bd8e880083819c0edc
|
7
|
+
data.tar.gz: 34c0d53702b1b0be001cd65ebf0bfcb17ab6d3e86505851a23bbe39bd9bd80d4c0cb6d3d95a990804bd3ee123090ebce155892ddadd2ab4484782219ad0535b3
|
data/README.md
CHANGED
@@ -158,8 +158,7 @@ end
|
|
158
158
|
* `:headless` (Boolean) - When false, run the browser visibly
|
159
159
|
* `:debug` (Boolean) - When true, debug output is logged to `STDERR`.
|
160
160
|
* `:logger` (Object responding to `puts`) - When present, debug output is written to this object
|
161
|
-
* `:browser_logger` (`IO` object) -
|
162
|
-
where your `console.log` statements will show up. Default: `STDOUT`
|
161
|
+
* `:browser_logger` (`IO` object) - This is where your `console.log` statements will show up. Default: `STDOUT`
|
163
162
|
* `:timeout` (Numeric) - The number of seconds we'll wait for a response
|
164
163
|
when communicating with Chrome. Default is 30.
|
165
164
|
* `:inspector` (Boolean, String) - See 'Remote Debugging', above.
|
@@ -4,6 +4,13 @@ require 'capybara/apparition/errors'
|
|
4
4
|
require 'capybara/apparition/dev_tools_protocol/target_manager'
|
5
5
|
require 'capybara/apparition/page'
|
6
6
|
require 'capybara/apparition/console'
|
7
|
+
require 'capybara/apparition/browser/header'
|
8
|
+
require 'capybara/apparition/browser/window'
|
9
|
+
require 'capybara/apparition/browser/render'
|
10
|
+
require 'capybara/apparition/browser/cookie'
|
11
|
+
require 'capybara/apparition/browser/modal'
|
12
|
+
require 'capybara/apparition/browser/frame'
|
13
|
+
require 'capybara/apparition/browser/auth'
|
7
14
|
require 'json'
|
8
15
|
require 'time'
|
9
16
|
|
@@ -23,7 +30,7 @@ module Capybara::Apparition
|
|
23
30
|
def initialize(client, logger = nil)
|
24
31
|
@client = client
|
25
32
|
@current_page_handle = nil
|
26
|
-
@targets = Capybara::Apparition::DevToolsProtocol::TargetManager.new
|
33
|
+
@targets = Capybara::Apparition::DevToolsProtocol::TargetManager.new(self)
|
27
34
|
@context_id = nil
|
28
35
|
@js_errors = true
|
29
36
|
@ignore_https_errors = false
|
@@ -38,6 +45,7 @@ module Capybara::Apparition
|
|
38
45
|
puts 'waiting for target...'
|
39
46
|
sleep 0.1
|
40
47
|
end
|
48
|
+
@context_id = current_target.context_id
|
41
49
|
end
|
42
50
|
|
43
51
|
def restart
|
@@ -64,101 +72,43 @@ module Capybara::Apparition
|
|
64
72
|
current_page.click_at(x, y)
|
65
73
|
end
|
66
74
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
current_page.pop_frame(top: true)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def window_handle
|
79
|
-
@current_page_handle
|
80
|
-
end
|
81
|
-
|
82
|
-
def window_handles
|
83
|
-
@targets.window_handles
|
84
|
-
end
|
85
|
-
|
86
|
-
def switch_to_window(handle)
|
87
|
-
target = @targets.get(handle)
|
88
|
-
raise NoSuchWindowError unless target&.page
|
75
|
+
include Header
|
76
|
+
include Window
|
77
|
+
include Render
|
78
|
+
include Cookie
|
79
|
+
include Modal
|
80
|
+
include Frame
|
81
|
+
include Auth
|
89
82
|
|
90
|
-
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
def open_new_window
|
95
|
-
context_id = @context_id || current_target.info['browserContextId']
|
96
|
-
info = command('Target.createTarget', url: 'about:blank', browserContextId: context_id)
|
97
|
-
target_id = info['targetId']
|
98
|
-
target = DevToolsProtocol::Target.new(self, info.merge('type' => 'page', 'inherit' => current_page))
|
99
|
-
target.page # Ensure page object construction happens
|
100
|
-
@targets.add(target_id, target)
|
101
|
-
target_id
|
102
|
-
end
|
83
|
+
def reset
|
84
|
+
current_page_targets = @targets.of_type('page').values
|
103
85
|
|
104
|
-
|
105
|
-
|
106
|
-
win_target = @targets.delete(handle)
|
107
|
-
warn 'Window was already closed unexpectedly' if win_target.nil?
|
108
|
-
win_target&.close
|
109
|
-
end
|
86
|
+
new_context_id = command('Target.createBrowserContext')['browserContextId']
|
87
|
+
new_target_response = client.send_cmd('Target.createTarget', url: 'about:blank', browserContextId: new_context_id)
|
110
88
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
def reset
|
121
|
-
command('Target.disposeBrowserContext', browserContextId: @context_id) if @context_id
|
89
|
+
current_page_targets.each do |target|
|
90
|
+
begin
|
91
|
+
client.send_cmd('Target.disposeBrowserContext', browserContextId: target.context_id).discard_result
|
92
|
+
rescue WrongWorld
|
93
|
+
puts 'Unknown browserContextId'
|
94
|
+
end
|
95
|
+
@targets.delete(target.id)
|
96
|
+
end
|
122
97
|
|
123
|
-
|
124
|
-
target_id = command('Target.createTarget', url: 'about:blank', browserContextId: @context_id)['targetId']
|
98
|
+
new_target_id = new_target_response['targetId']
|
125
99
|
|
126
|
-
timer = Capybara::Helpers.timer(expire_in:
|
127
|
-
until @targets.get(
|
100
|
+
timer = Capybara::Helpers.timer(expire_in: 10)
|
101
|
+
until @targets.get(new_target_id)&.page&.usable?
|
128
102
|
if timer.expired?
|
129
103
|
puts 'Timedout waiting for reset'
|
130
|
-
# byebug
|
131
104
|
raise TimeoutError.new('reset')
|
132
105
|
end
|
133
106
|
sleep 0.01
|
134
107
|
end
|
135
|
-
@current_page_handle =
|
108
|
+
@current_page_handle = new_target_id
|
136
109
|
true
|
137
110
|
end
|
138
111
|
|
139
|
-
def render(path, options = {})
|
140
|
-
check_render_options!(options, path)
|
141
|
-
img_data = current_page.render(options)
|
142
|
-
File.open(path, 'wb') { |f| f.write(Base64.decode64(img_data)) }
|
143
|
-
end
|
144
|
-
|
145
|
-
def render_base64(options = {})
|
146
|
-
check_render_options!(options)
|
147
|
-
current_page.render(options)
|
148
|
-
end
|
149
|
-
|
150
|
-
attr_writer :zoom_factor
|
151
|
-
|
152
|
-
def paper_size=(size)
|
153
|
-
@paper_size = if size.is_a? Hash
|
154
|
-
size
|
155
|
-
else
|
156
|
-
PAPER_SIZES.fetch(size) do
|
157
|
-
raise_errors ArgumentError, "Unknwon paper size: #{size}"
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
112
|
def resize(width, height, screen: nil)
|
163
113
|
current_page.set_viewport width: width, height: height, screen: screen
|
164
114
|
end
|
@@ -172,95 +122,6 @@ module Capybara::Apparition
|
|
172
122
|
end
|
173
123
|
end
|
174
124
|
|
175
|
-
def headers
|
176
|
-
current_page.extra_headers
|
177
|
-
end
|
178
|
-
|
179
|
-
def headers=(headers)
|
180
|
-
@targets.pages.each do |page|
|
181
|
-
page.perm_headers = headers.dup
|
182
|
-
page.temp_headers = {}
|
183
|
-
page.temp_no_redirect_headers = {}
|
184
|
-
page.update_headers
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def add_headers(headers)
|
189
|
-
current_page.perm_headers.merge! headers
|
190
|
-
current_page.update_headers
|
191
|
-
end
|
192
|
-
|
193
|
-
def add_header(header, permanent: true, **_options)
|
194
|
-
if permanent == true
|
195
|
-
@targets.pages.each do |page|
|
196
|
-
page.perm_headers.merge! header
|
197
|
-
page.update_headers
|
198
|
-
end
|
199
|
-
else
|
200
|
-
if permanent.to_s == 'no_redirect'
|
201
|
-
current_page.temp_no_redirect_headers.merge! header
|
202
|
-
else
|
203
|
-
current_page.temp_headers.merge! header
|
204
|
-
end
|
205
|
-
current_page.update_headers
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
def cookies
|
210
|
-
CookieJar.new(
|
211
|
-
# current_page.command('Network.getCookies')['cookies'].map { |c| Cookie.new(c) }
|
212
|
-
self
|
213
|
-
)
|
214
|
-
end
|
215
|
-
|
216
|
-
def all_cookies
|
217
|
-
CookieJar.new(
|
218
|
-
# current_page.command('Network.getAllCookies')['cookies'].map { |c| Cookie.new(c) }
|
219
|
-
self
|
220
|
-
)
|
221
|
-
end
|
222
|
-
|
223
|
-
def get_raw_cookies
|
224
|
-
current_page.command('Network.getAllCookies')['cookies'].map { |c| Cookie.new(c) }
|
225
|
-
end
|
226
|
-
|
227
|
-
def set_cookie(cookie)
|
228
|
-
if cookie[:expires]
|
229
|
-
# cookie[:expires] = cookie[:expires].to_i * 1000
|
230
|
-
cookie[:expires] = cookie[:expires].to_i
|
231
|
-
end
|
232
|
-
|
233
|
-
current_page.command('Network.setCookie', cookie)
|
234
|
-
end
|
235
|
-
|
236
|
-
def remove_cookie(name)
|
237
|
-
current_page.command('Network.deleteCookies', name: name, url: current_url)
|
238
|
-
end
|
239
|
-
|
240
|
-
def clear_cookies
|
241
|
-
current_page.command('Network.clearBrowserCookies')
|
242
|
-
end
|
243
|
-
|
244
|
-
def cookies_enabled=(flag)
|
245
|
-
current_page.command('Emulation.setDocumentCookieDisabled', disabled: !flag)
|
246
|
-
end
|
247
|
-
|
248
|
-
def set_proxy_auth(user, password)
|
249
|
-
@proxy_auth = if user.nil? && password.nil?
|
250
|
-
nil
|
251
|
-
else
|
252
|
-
{ username: user, password: password }
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
def set_http_auth(user = nil, password = nil)
|
257
|
-
current_page.credentials = if user.nil? && password.nil?
|
258
|
-
nil
|
259
|
-
else
|
260
|
-
{ username: user, password: password }
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
125
|
attr_accessor :js_errors, :ignore_https_errors
|
265
126
|
|
266
127
|
def extensions=(filenames)
|
@@ -305,30 +166,6 @@ module Capybara::Apparition
|
|
305
166
|
raise
|
306
167
|
end
|
307
168
|
|
308
|
-
def accept_alert
|
309
|
-
current_page.add_modal(alert: true)
|
310
|
-
end
|
311
|
-
|
312
|
-
def accept_confirm
|
313
|
-
current_page.add_modal(confirm: true)
|
314
|
-
end
|
315
|
-
|
316
|
-
def dismiss_confirm
|
317
|
-
current_page.add_modal(confirm: false)
|
318
|
-
end
|
319
|
-
|
320
|
-
def accept_prompt(response)
|
321
|
-
current_page.add_modal(prompt: response)
|
322
|
-
end
|
323
|
-
|
324
|
-
def dismiss_prompt
|
325
|
-
current_page.add_modal(prompt: false)
|
326
|
-
end
|
327
|
-
|
328
|
-
def modal_message
|
329
|
-
current_page.modal_messages.shift
|
330
|
-
end
|
331
|
-
|
332
169
|
def current_page
|
333
170
|
current_target.page
|
334
171
|
end
|
@@ -342,6 +179,7 @@ module Capybara::Apparition
|
|
342
179
|
def current_target
|
343
180
|
@targets.get(@current_page_handle) || begin
|
344
181
|
puts "No current page: #{@current_page_handle}"
|
182
|
+
puts caller
|
345
183
|
@current_page_handle = nil
|
346
184
|
raise NoSuchWindowError
|
347
185
|
end
|
@@ -351,98 +189,79 @@ module Capybara::Apparition
|
|
351
189
|
@logger&.puts message if ENV['DEBUG']
|
352
190
|
end
|
353
191
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
k
|
420
|
-
end.join(',')
|
421
|
-
else
|
422
|
-
''
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
def symbol_to_desc(symbol)
|
427
|
-
if symbol == :space
|
428
|
-
res = ' '
|
429
|
-
else
|
430
|
-
key = KEY_ALIASES.fetch(symbol.downcase, symbol)
|
431
|
-
if (match = key.to_s.match(/numpad(.)/))
|
432
|
-
res = { keys: match[1], modifier: 'keypad' }
|
433
|
-
elsif !/^[A-Z]/.match?(key)
|
434
|
-
key = key.to_s.split('_').map(&:capitalize).join
|
435
|
-
end
|
436
|
-
end
|
437
|
-
res || { key: key }
|
438
|
-
end
|
192
|
+
# KEY_ALIASES = {
|
193
|
+
# command: :Meta,
|
194
|
+
# equals: :Equal,
|
195
|
+
# control: :Control,
|
196
|
+
# ctrl: :Control,
|
197
|
+
# multiply: 'numpad*',
|
198
|
+
# add: 'numpad+',
|
199
|
+
# divide: 'numpad/',
|
200
|
+
# subtract: 'numpad-',
|
201
|
+
# decimal: 'numpad.',
|
202
|
+
# left: 'ArrowLeft',
|
203
|
+
# right: 'ArrowRight',
|
204
|
+
# down: 'ArrowDown',
|
205
|
+
# up: 'ArrowUp'
|
206
|
+
# }.freeze
|
207
|
+
#
|
208
|
+
# def normalize_keys(keys)
|
209
|
+
# keys.map do |key_desc|
|
210
|
+
# case key_desc
|
211
|
+
# when Array
|
212
|
+
# # [:Shift, "s"] => { modifier: "shift", keys: "S" }
|
213
|
+
# # [:Shift, "string"] => { modifier: "shift", keys: "STRING" }
|
214
|
+
# # [:Ctrl, :Left] => { modifier: "ctrl", key: 'Left' }
|
215
|
+
# # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: 'Left' }
|
216
|
+
# # [:Ctrl, :Left, :Left] => { modifier: "ctrl", key: [:Left, :Left] }
|
217
|
+
# keys_chunks = key_desc.chunk do |k|
|
218
|
+
# k.is_a?(Symbol) && %w[shift ctrl control alt meta command].include?(k.to_s.downcase)
|
219
|
+
# end
|
220
|
+
# modifiers = modifiers_from_chunks(keys_chunks)
|
221
|
+
# letters = normalize_keys(_keys.next[1].map { |k| k.is_a?(String) ? k.upcase : k })
|
222
|
+
# { modifier: modifiers, keys: letters }
|
223
|
+
# when Symbol
|
224
|
+
# symbol_to_desc(key_desc)
|
225
|
+
# when String
|
226
|
+
# key_desc # Plain string, nothing to do
|
227
|
+
# end
|
228
|
+
# end
|
229
|
+
# end
|
230
|
+
#
|
231
|
+
# def modifiers_from_chunks(chunks)
|
232
|
+
# if chunks.peek[0]
|
233
|
+
# chunks.next[1].map do |k|
|
234
|
+
# k = k.to_s.downcase
|
235
|
+
# k = 'control' if k == 'ctrl'
|
236
|
+
# k = 'meta' if k == 'command'
|
237
|
+
# k
|
238
|
+
# end.join(',')
|
239
|
+
# else
|
240
|
+
# ''
|
241
|
+
# end
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
# def symbol_to_desc(symbol)
|
245
|
+
# if symbol == :space
|
246
|
+
# res = ' '
|
247
|
+
# else
|
248
|
+
# key = KEY_ALIASES.fetch(symbol.downcase, symbol)
|
249
|
+
# if (match = key.to_s.match(/numpad(.)/))
|
250
|
+
# res = { keys: match[1], modifier: 'keypad' }
|
251
|
+
# elsif !/^[A-Z]/.match?(key)
|
252
|
+
# key = key.to_s.split('_').map(&:capitalize).join
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
# res || { key: key }
|
256
|
+
# end
|
439
257
|
|
440
258
|
def initialize_handlers
|
441
259
|
@client.on 'Target.targetCreated' do |info|
|
442
260
|
puts "Target Created Info: #{info}" if ENV['DEBUG']
|
443
261
|
target_info = info['targetInfo']
|
444
262
|
if !@targets.target?(target_info['targetId'])
|
445
|
-
@targets.add(target_info['targetId'], DevToolsProtocol::Target.new(self, target_info))
|
263
|
+
# @targets.add(target_info['targetId'], DevToolsProtocol::Target.new(self, target_info))
|
264
|
+
@targets.add(target_info['targetId'], target_info)
|
446
265
|
puts "**** Target Added #{info}" if ENV['DEBUG']
|
447
266
|
elsif ENV['DEBUG']
|
448
267
|
puts "Target already existed #{info}"
|
@@ -460,22 +279,12 @@ module Capybara::Apparition
|
|
460
279
|
target_info = info['targetInfo']
|
461
280
|
target = @targets.get(target_info['targetId'])
|
462
281
|
if target
|
463
|
-
target.
|
282
|
+
target.update(target_info)
|
464
283
|
else
|
465
284
|
puts '****No target for the info change- creating****' if ENV['DEBUG']
|
466
|
-
@targets.add(target_info['targetId'],
|
285
|
+
@targets.add(target_info['targetId'], target_info)
|
467
286
|
end
|
468
287
|
end
|
469
288
|
end
|
470
|
-
|
471
|
-
PAPER_SIZES = {
|
472
|
-
'A3' => { width: 11.69, height: 16.53 },
|
473
|
-
'A4' => { width: 8.27, height: 11.69 },
|
474
|
-
'A5' => { width: 5.83, height: 8.27 },
|
475
|
-
'Legal' => { width: 8.5, height: 14 },
|
476
|
-
'Letter' => { width: 8.5, height: 11 },
|
477
|
-
'Tabloid' => { width: 11, height: 17 },
|
478
|
-
'Ledger' => { width: 17, height: 11 }
|
479
|
-
}.freeze
|
480
289
|
end
|
481
290
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Auth
|
6
|
+
def set_proxy_auth(user, password)
|
7
|
+
set_auth(:proxy, user, password)
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_http_auth(user, password)
|
11
|
+
set_auth(:http, user, password)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def set_auth(type, user, password)
|
17
|
+
creds = user.nil? && password.nil? ? nil : { username: user, password: password }
|
18
|
+
|
19
|
+
case type
|
20
|
+
when :http
|
21
|
+
current_page.credentials = creds
|
22
|
+
when :proxy
|
23
|
+
@proxy_auth = creds
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/apparition/cookie_jar'
|
4
|
+
|
5
|
+
module Capybara::Apparition
|
6
|
+
class Browser
|
7
|
+
module Cookie
|
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
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_raw_cookies
|
23
|
+
current_page.command('Network.getAllCookies')['cookies'].map do |c|
|
24
|
+
Capybara::Apparition::Cookie.new(c)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
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
|
33
|
+
|
34
|
+
current_page.command('Network.setCookie', cookie)
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_cookie(name)
|
38
|
+
current_page.command('Network.deleteCookies', name: name, url: current_url)
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_cookies
|
42
|
+
current_page.command('Network.clearBrowserCookies')
|
43
|
+
end
|
44
|
+
|
45
|
+
def cookies_enabled=(flag)
|
46
|
+
current_page.command('Emulation.setDocumentCookieDisabled', disabled: !flag)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Frame
|
6
|
+
def switch_to_frame(frame)
|
7
|
+
case frame
|
8
|
+
when Capybara::Node::Base
|
9
|
+
current_page.push_frame(frame)
|
10
|
+
when :parent
|
11
|
+
current_page.pop_frame
|
12
|
+
when :top
|
13
|
+
current_page.pop_frame(top: true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Header
|
6
|
+
def headers
|
7
|
+
current_page.extra_headers
|
8
|
+
end
|
9
|
+
|
10
|
+
def headers=(headers)
|
11
|
+
@targets.pages.each do |page|
|
12
|
+
page.perm_headers = headers.dup
|
13
|
+
page.temp_headers = {}
|
14
|
+
page.temp_no_redirect_headers = {}
|
15
|
+
page.update_headers
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_headers(headers)
|
20
|
+
current_page.perm_headers.merge! headers
|
21
|
+
current_page.update_headers
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_header(header, permanent: true, **_options)
|
25
|
+
if permanent == true
|
26
|
+
@targets.pages.each do |page|
|
27
|
+
page.perm_headers.merge! header
|
28
|
+
page.update_headers
|
29
|
+
end
|
30
|
+
else
|
31
|
+
if permanent.to_s == 'no_redirect'
|
32
|
+
current_page.temp_no_redirect_headers.merge! header
|
33
|
+
else
|
34
|
+
current_page.temp_headers.merge! header
|
35
|
+
end
|
36
|
+
current_page.update_headers
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Modal
|
6
|
+
def accept_alert
|
7
|
+
current_page.add_modal(alert: true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def accept_confirm
|
11
|
+
current_page.add_modal(confirm: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def dismiss_confirm
|
15
|
+
current_page.add_modal(confirm: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def accept_prompt(response)
|
19
|
+
current_page.add_modal(prompt: response)
|
20
|
+
end
|
21
|
+
|
22
|
+
def dismiss_prompt
|
23
|
+
current_page.add_modal(prompt: false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def modal_message
|
27
|
+
current_page.modal_messages.shift
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Render
|
6
|
+
def render(path, options = {})
|
7
|
+
check_render_options!(options, path)
|
8
|
+
img_data = current_page.render(options)
|
9
|
+
File.open(path, 'wb') { |f| f.write(Base64.decode64(img_data)) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_base64(options = {})
|
13
|
+
check_render_options!(options)
|
14
|
+
current_page.render(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_writer :zoom_factor
|
18
|
+
|
19
|
+
def paper_size=(size)
|
20
|
+
@paper_size = if size.is_a? Hash
|
21
|
+
size
|
22
|
+
else
|
23
|
+
PAPER_SIZES.fetch(size) do
|
24
|
+
raise_errors ArgumentError, "Unknwon paper size: #{size}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check_render_options!(options, path = nil)
|
32
|
+
options[:format] ||= File.extname(path).downcase[1..-1] if path
|
33
|
+
options[:format] = :jpeg if options[:format].to_s == 'jpg'
|
34
|
+
options[:full] = !!options[:full]
|
35
|
+
return unless options[:full] && options.key?(:selector)
|
36
|
+
|
37
|
+
warn "Ignoring :selector in #render since :full => true was given at #{caller(1..1)}"
|
38
|
+
options.delete(:selector)
|
39
|
+
end
|
40
|
+
|
41
|
+
PAPER_SIZES = {
|
42
|
+
'A3' => { width: 11.69, height: 16.53 },
|
43
|
+
'A4' => { width: 8.27, height: 11.69 },
|
44
|
+
'A5' => { width: 5.83, height: 8.27 },
|
45
|
+
'Legal' => { width: 8.5, height: 14 },
|
46
|
+
'Letter' => { width: 8.5, height: 11 },
|
47
|
+
'Tabloid' => { width: 11, height: 17 },
|
48
|
+
'Ledger' => { width: 17, height: 11 }
|
49
|
+
}.freeze
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara::Apparition
|
4
|
+
class Browser
|
5
|
+
module Window
|
6
|
+
def window_handle
|
7
|
+
@current_page_handle
|
8
|
+
end
|
9
|
+
|
10
|
+
def window_handles
|
11
|
+
@targets.window_handles
|
12
|
+
end
|
13
|
+
|
14
|
+
def switch_to_window(handle)
|
15
|
+
target = @targets.get(handle)
|
16
|
+
raise NoSuchWindowError unless target&.page
|
17
|
+
|
18
|
+
target.page.wait_for_loaded
|
19
|
+
@current_page_handle = handle
|
20
|
+
end
|
21
|
+
|
22
|
+
def open_new_window
|
23
|
+
context_id = current_target.context_id
|
24
|
+
info = command('Target.createTarget', url: 'about:blank', browserContextId: context_id)
|
25
|
+
target_id = info['targetId']
|
26
|
+
target = DevToolsProtocol::Target.new(self, info.merge('type' => 'page', 'inherit' => current_page))
|
27
|
+
target.page # Ensure page object construction happens
|
28
|
+
begin
|
29
|
+
puts "Adding #{target_id} - #{target.info}" if ENV['DEBUG']
|
30
|
+
@targets.add(target_id, target)
|
31
|
+
rescue ArgumentError
|
32
|
+
puts 'Target already existed' if ENV['DEBUG']
|
33
|
+
end
|
34
|
+
target_id
|
35
|
+
end
|
36
|
+
|
37
|
+
def close_window(handle)
|
38
|
+
@current_page_handle = nil if @current_page_handle == handle
|
39
|
+
win_target = @targets.delete(handle)
|
40
|
+
warn 'Window was already closed unexpectedly' if win_target.nil?
|
41
|
+
win_target&.close
|
42
|
+
end
|
43
|
+
|
44
|
+
def within_window(locator)
|
45
|
+
original = window_handle
|
46
|
+
handle = find_window_handle(locator)
|
47
|
+
switch_to_window(handle)
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
switch_to_window(original)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def find_window_handle(locator)
|
57
|
+
return locator if window_handles.include? locator
|
58
|
+
|
59
|
+
window_handles.each do |handle|
|
60
|
+
switch_to_window(handle)
|
61
|
+
return handle if evaluate('window.name') == locator
|
62
|
+
end
|
63
|
+
raise NoSuchWindowError
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -5,24 +5,36 @@ require 'capybara/apparition/dev_tools_protocol/session'
|
|
5
5
|
module Capybara::Apparition
|
6
6
|
module DevToolsProtocol
|
7
7
|
class Target
|
8
|
-
attr_accessor :info
|
9
|
-
|
10
8
|
def initialize(browser, info)
|
11
9
|
@browser = browser
|
12
|
-
@info = info
|
10
|
+
@info = info.dup
|
13
11
|
@page = nil
|
14
12
|
end
|
15
13
|
|
14
|
+
def info
|
15
|
+
@info.dup.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(new_info)
|
19
|
+
@info ||= {}
|
20
|
+
@info.merge!(new_info)
|
21
|
+
info
|
22
|
+
end
|
23
|
+
|
16
24
|
def id
|
17
|
-
info['targetId']
|
25
|
+
@info['targetId']
|
26
|
+
end
|
27
|
+
|
28
|
+
def context_id
|
29
|
+
@info['browserContextId']
|
18
30
|
end
|
19
31
|
|
20
32
|
def title
|
21
|
-
info['title']
|
33
|
+
@info['title']
|
22
34
|
end
|
23
35
|
|
24
36
|
def url
|
25
|
-
info['url']
|
37
|
+
@info['url']
|
26
38
|
end
|
27
39
|
|
28
40
|
def page
|
@@ -30,7 +42,7 @@ module Capybara::Apparition
|
|
30
42
|
if info['type'] == 'page'
|
31
43
|
Page.create(@browser, create_session, id,
|
32
44
|
ignore_https_errors: @browser.ignore_https_errors,
|
33
|
-
js_errors: @browser.js_errors).inherit(info.delete('inherit'))
|
45
|
+
js_errors: @browser.js_errors).inherit(@info.delete('inherit'))
|
34
46
|
else
|
35
47
|
nil
|
36
48
|
end
|
@@ -5,7 +5,8 @@ require 'capybara/apparition/dev_tools_protocol/target'
|
|
5
5
|
module Capybara::Apparition
|
6
6
|
module DevToolsProtocol
|
7
7
|
class TargetManager
|
8
|
-
def initialize
|
8
|
+
def initialize(browser)
|
9
|
+
@browser = browser
|
9
10
|
@targets = {}
|
10
11
|
end
|
11
12
|
|
@@ -13,8 +14,18 @@ module Capybara::Apparition
|
|
13
14
|
@targets[id]
|
14
15
|
end
|
15
16
|
|
17
|
+
def of_type(type)
|
18
|
+
@targets.select { |_id, target| target.info['type'] == type }
|
19
|
+
end
|
20
|
+
|
16
21
|
def add(id, target)
|
17
|
-
@targets
|
22
|
+
raise ArgumentError, 'Target already exists' if @targets.key?(id)
|
23
|
+
|
24
|
+
@targets[id] = if target.is_a? DevToolsProtocol::Target
|
25
|
+
target
|
26
|
+
else
|
27
|
+
DevToolsProtocol::Target.new(@browser, target)
|
28
|
+
end
|
18
29
|
end
|
19
30
|
|
20
31
|
def delete(id)
|
@@ -135,7 +135,12 @@ module Capybara::Apparition
|
|
135
135
|
end
|
136
136
|
|
137
137
|
def reset!
|
138
|
-
|
138
|
+
begin
|
139
|
+
browser.reset
|
140
|
+
rescue TimeoutError
|
141
|
+
puts 'Reset timed out - retrying'
|
142
|
+
browser.reset
|
143
|
+
end
|
139
144
|
browser.url_blacklist = options[:url_blacklist] || []
|
140
145
|
browser.url_whitelist = options[:url_whitelist] || []
|
141
146
|
@started = false
|
@@ -101,7 +101,7 @@ module Capybara::Apparition
|
|
101
101
|
evaluate_on GET_STYLES_JS, value: styles
|
102
102
|
end
|
103
103
|
|
104
|
-
def set(value, **
|
104
|
+
def set(value, **options)
|
105
105
|
if tag_name == 'input'
|
106
106
|
case self[:type]
|
107
107
|
when 'radio'
|
@@ -118,13 +118,13 @@ module Capybara::Apparition
|
|
118
118
|
when 'datetime-local'
|
119
119
|
set_datetime_local(value)
|
120
120
|
else
|
121
|
-
set_text(value.to_s)
|
121
|
+
set_text(value.to_s, delay: options.fetch(:delay, 0))
|
122
122
|
end
|
123
123
|
elsif tag_name == 'textarea'
|
124
124
|
set_text(value.to_s)
|
125
125
|
elsif self[:isContentEditable]
|
126
126
|
delete_text
|
127
|
-
send_keys(value.to_s)
|
127
|
+
send_keys(value.to_s, delay: options.fetch(:delay, 0))
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
@@ -233,9 +233,9 @@ module Capybara::Apparition
|
|
233
233
|
false
|
234
234
|
end
|
235
235
|
|
236
|
-
def send_keys(*keys)
|
236
|
+
def send_keys(*keys, delay: 0, **_opts)
|
237
237
|
click unless evaluate_on CURRENT_NODE_SELECTED_JS
|
238
|
-
@page.keyboard.type(keys)
|
238
|
+
@page.keyboard.type(keys, delay: delay)
|
239
239
|
end
|
240
240
|
alias_method :send_key, :send_keys
|
241
241
|
|
@@ -410,16 +410,16 @@ module Capybara::Apparition
|
|
410
410
|
DevToolsProtocol::RemoteObject.new(@page, response['result'] || response['object']).value
|
411
411
|
end
|
412
412
|
|
413
|
-
def set_text(value, clear: nil, **_unused)
|
413
|
+
def set_text(value, clear: nil, delay: 0, **_unused)
|
414
414
|
value = value.to_s
|
415
415
|
if value.empty? && clear.nil?
|
416
416
|
evaluate_on CLEAR_ELEMENT_JS
|
417
417
|
elsif clear == :backspace
|
418
418
|
# Clear field by sending the correct number of backspace keys.
|
419
419
|
backspaces = [:backspace] * self.value.to_s.length
|
420
|
-
send_keys(*([:end] + backspaces + [value]))
|
420
|
+
send_keys(*([:end] + backspaces + [value]), delay: delay)
|
421
421
|
elsif clear.is_a? Array
|
422
|
-
send_keys(*clear, value)
|
422
|
+
send_keys(*clear, value, delay: delay)
|
423
423
|
else
|
424
424
|
# Clear field by JavaScript assignment of the value property.
|
425
425
|
# Script can change a readonly element which user input cannot, so
|
@@ -429,7 +429,7 @@ module Capybara::Apparition
|
|
429
429
|
arguments[0].value = ''
|
430
430
|
}
|
431
431
|
JS
|
432
|
-
send_keys(value)
|
432
|
+
send_keys(value, delay: delay)
|
433
433
|
end
|
434
434
|
end
|
435
435
|
|
@@ -439,7 +439,11 @@ module Capybara::Apparition
|
|
439
439
|
|
440
440
|
def set_date(value)
|
441
441
|
value = SettableValue.new(value)
|
442
|
-
|
442
|
+
unless value.dateable?
|
443
|
+
# click(x:5, y:10)
|
444
|
+
# debug
|
445
|
+
return set_text(value)
|
446
|
+
end
|
443
447
|
|
444
448
|
# TODO: this would be better if locale can be detected and correct keystrokes sent
|
445
449
|
update_value_js(value.to_date_str)
|
@@ -133,11 +133,13 @@ module Capybara::Apparition
|
|
133
133
|
|
134
134
|
def render(options)
|
135
135
|
wait_for_loaded
|
136
|
+
pixel_ratio = evaluate('window.devicePixelRatio')
|
137
|
+
scale = (@browser.zoom_factor || 1).to_f / pixel_ratio
|
136
138
|
if options[:format].to_s == 'pdf'
|
137
139
|
params = {}
|
138
140
|
params[:paperWidth] = @browser.paper_size[:width].to_f if @browser.paper_size
|
139
141
|
params[:paperHeight] = @browser.paper_size[:height].to_f if @browser.paper_size
|
140
|
-
params[:scale] =
|
142
|
+
params[:scale] = scale
|
141
143
|
command('Page.printToPDF', params)
|
142
144
|
else
|
143
145
|
clip_options = if options[:selector]
|
@@ -152,8 +154,7 @@ module Capybara::Apparition
|
|
152
154
|
{ width: window.innerWidth, height: window.innerHeight }
|
153
155
|
JS
|
154
156
|
end
|
155
|
-
options[:clip] = { x: 0, y: 0, scale:
|
156
|
-
options[:clip][:scale] = @browser.zoom_factor || 1
|
157
|
+
options[:clip] = { x: 0, y: 0, scale: scale }.merge(clip_options)
|
157
158
|
command('Page.captureScreenshot', options)
|
158
159
|
end['data']
|
159
160
|
end
|
@@ -595,6 +596,9 @@ module Capybara::Apparition
|
|
595
596
|
unless @temp_no_redirect_headers.empty? || !navigation
|
596
597
|
headers.delete_if { |name, value| @temp_no_redirect_headers[name] == value }
|
597
598
|
end
|
599
|
+
if (accept = perm_headers.keys.find { |k| k =~ /accept/i })
|
600
|
+
headers[accept] = perm_headers[accept]
|
601
|
+
end
|
598
602
|
|
599
603
|
if @url_blacklist.any? { |r| url.match Regexp.escape(r).gsub('\*', '.*?') }
|
600
604
|
block_request(interception_id, 'Failed')
|
@@ -10,8 +10,8 @@ module Capybara::Apparition
|
|
10
10
|
@pressed_keys = {}
|
11
11
|
end
|
12
12
|
|
13
|
-
def type(keys)
|
14
|
-
type_with_modifiers(Array(keys))
|
13
|
+
def type(keys, delay:)
|
14
|
+
type_with_modifiers(Array(keys), delay: delay)
|
15
15
|
end
|
16
16
|
|
17
17
|
def press(key)
|
@@ -66,14 +66,19 @@ module Capybara::Apparition
|
|
66
66
|
|
67
67
|
private
|
68
68
|
|
69
|
-
def type_with_modifiers(keys)
|
69
|
+
def type_with_modifiers(keys, delay:)
|
70
70
|
old_pressed_keys, @pressed_keys = @pressed_keys, {}
|
71
71
|
|
72
72
|
Array(keys).each do |sequence|
|
73
73
|
case sequence
|
74
|
-
when Array then type_with_modifiers(sequence)
|
75
|
-
when String then sequence.each_char
|
76
|
-
|
74
|
+
when Array then type_with_modifiers(sequence, delay: delay)
|
75
|
+
when String then sequence.each_char do |char|
|
76
|
+
press char
|
77
|
+
sleep delay
|
78
|
+
end
|
79
|
+
else
|
80
|
+
press sequence
|
81
|
+
sleep delay
|
77
82
|
end
|
78
83
|
end
|
79
84
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apparition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.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-
|
11
|
+
date: 2019-02-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: backports
|
@@ -224,6 +224,13 @@ files:
|
|
224
224
|
- README.md
|
225
225
|
- lib/capybara/apparition.rb
|
226
226
|
- lib/capybara/apparition/browser.rb
|
227
|
+
- lib/capybara/apparition/browser/auth.rb
|
228
|
+
- lib/capybara/apparition/browser/cookie.rb
|
229
|
+
- lib/capybara/apparition/browser/frame.rb
|
230
|
+
- lib/capybara/apparition/browser/header.rb
|
231
|
+
- lib/capybara/apparition/browser/modal.rb
|
232
|
+
- lib/capybara/apparition/browser/render.rb
|
233
|
+
- lib/capybara/apparition/browser/window.rb
|
227
234
|
- lib/capybara/apparition/console.rb
|
228
235
|
- lib/capybara/apparition/cookie.rb
|
229
236
|
- lib/capybara/apparition/cookie_jar.rb
|