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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 594a3f464bdb932d89f32f9bc0c3ce132ae5322c167e96016b0e66e292a8d71f
4
- data.tar.gz: 4e2bf086855cb7aad5d38727efb96c9945f325e6c4b53f4ab17b4caa5a4caa35
3
+ metadata.gz: b51cb57f961f9825c56ce38377bbf4fd5477a5cdad6d75a2dac1aab827b81561
4
+ data.tar.gz: 29e9b0c81cdcd3be2820a8d9a2719708750d3099f56a6c403c474a77bb254bce
5
5
  SHA512:
6
- metadata.gz: d4111cb452f2f30f69619bbdfe499b1a44aa00208e255264797440dfed1dd5c98c4045a31ebd6572b902bf16ed62c4873dbfc5229d5ec273296c7ba688cb4c70
7
- data.tar.gz: 1c160f3115ec245d2ddb834053913627f1ed34fe681fd8cb892d0a2b9bcd1af7d61f673b392582c84d248d5341239b4b2343ee5eee14c357fd018396f6e919d8
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) - Where the `STDOUT` from Chromium is written to. This is
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
- def switch_to_frame(frame)
68
- case frame
69
- when Capybara::Node::Base
70
- current_page.push_frame(frame)
71
- when :parent
72
- current_page.pop_frame
73
- when :top
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
- target.page.wait_for_loaded
91
- @current_page_handle = handle
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
- def close_window(handle)
105
- @current_page_handle = nil if @current_page_handle == handle
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
- def within_window(locator)
112
- original = window_handle
113
- handle = find_window_handle(locator)
114
- switch_to_window(handle)
115
- yield
116
- ensure
117
- switch_to_window(original)
118
- end
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
- @context_id = command('Target.createBrowserContext')['browserContextId']
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: 5)
127
- until @targets.get(target_id)&.page&.usable?
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 = target_id
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
- def check_render_options!(options, path = nil)
355
- options[:format] ||= File.extname(path).downcase[1..-1] if path
356
- options[:format] = :jpeg if options[:format].to_s == 'jpg'
357
- options[:full] = !!options[:full]
358
- return unless options[:full] && options.key?(:selector)
359
-
360
- warn "Ignoring :selector in #render since :full => true was given at #{caller(1..1)}"
361
- options.delete(:selector)
362
- end
363
-
364
- def find_window_handle(locator)
365
- return locator if window_handles.include? locator
366
-
367
- window_handles.each do |handle|
368
- switch_to_window(handle)
369
- return handle if evaluate('window.name') == locator
370
- end
371
- raise NoSuchWindowError
372
- end
373
-
374
- KEY_ALIASES = {
375
- command: :Meta,
376
- equals: :Equal,
377
- control: :Control,
378
- ctrl: :Control,
379
- multiply: 'numpad*',
380
- add: 'numpad+',
381
- divide: 'numpad/',
382
- subtract: 'numpad-',
383
- decimal: 'numpad.',
384
- left: 'ArrowLeft',
385
- right: 'ArrowRight',
386
- down: 'ArrowDown',
387
- up: 'ArrowUp'
388
- }.freeze
389
-
390
- def normalize_keys(keys)
391
- keys.map do |key_desc|
392
- case key_desc
393
- when Array
394
- # [:Shift, "s"] => { modifier: "shift", keys: "S" }
395
- # [:Shift, "string"] => { modifier: "shift", keys: "STRING" }
396
- # [:Ctrl, :Left] => { modifier: "ctrl", key: 'Left' }
397
- # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: 'Left' }
398
- # [:Ctrl, :Left, :Left] => { modifier: "ctrl", key: [:Left, :Left] }
399
- keys_chunks = key_desc.chunk do |k|
400
- k.is_a?(Symbol) && %w[shift ctrl control alt meta command].include?(k.to_s.downcase)
401
- end
402
- modifiers = modifiers_from_chunks(keys_chunks)
403
- letters = normalize_keys(_keys.next[1].map { |k| k.is_a?(String) ? k.upcase : k })
404
- { modifier: modifiers, keys: letters }
405
- when Symbol
406
- symbol_to_desc(key_desc)
407
- when String
408
- key_desc # Plain string, nothing to do
409
- end
410
- end
411
- end
412
-
413
- def modifiers_from_chunks(chunks)
414
- if chunks.peek[0]
415
- chunks.next[1].map do |k|
416
- k = k.to_s.downcase
417
- k = 'control' if k == 'ctrl'
418
- k = 'meta' if k == 'command'
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.info.merge!(target_info)
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'], DevToolsProtocol::Target.new(self, target_info))
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[id] = target
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
- browser.reset
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
@@ -227,6 +227,7 @@ module Capybara::Apparition
227
227
  rescue EOFError # rubocop:disable Lint/HandleExceptions
228
228
  end
229
229
  end
230
+ # @listener.abort_on_exception = true
230
231
  end
231
232
  end
232
233
  end
@@ -26,6 +26,10 @@ module Capybara::Apparition
26
26
  nil
27
27
  end
28
28
 
29
+ def [](key)
30
+ result[key]
31
+ end
32
+
29
33
  private
30
34
 
31
35
  def handle_error(error)
@@ -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, **_options)
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
- return set_text(value) unless value.dateable?
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] = @browser.zoom_factor if @browser.zoom_factor
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: 1 }.merge(clip_options)
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 { |char| press char }
76
- else press sequence
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Apparition
5
- VERSION = '0.0.6'
5
+ VERSION = '0.1.0'
6
6
  end
7
7
  end
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.6
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-01-31 00:00:00.000000000 Z
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