puppeteer-ruby 0.0.8 → 0.0.13

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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/docs/Puppeteer.html +415 -97
  4. data/docs/Puppeteer/AsyncAwaitBehavior.html +1 -1
  5. data/docs/Puppeteer/Browser.html +261 -153
  6. data/docs/Puppeteer/BrowserContext.html +2 -2
  7. data/docs/Puppeteer/BrowserFetcher.html +1 -1
  8. data/docs/Puppeteer/BrowserRunner.html +1 -1
  9. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +1 -1
  10. data/docs/Puppeteer/CDPSession.html +2 -2
  11. data/docs/Puppeteer/CDPSession/Error.html +1 -1
  12. data/docs/Puppeteer/ConcurrentRubyUtils.html +14 -6
  13. data/docs/Puppeteer/Connection.html +66 -62
  14. data/docs/Puppeteer/Connection/MessageCallback.html +1 -1
  15. data/docs/Puppeteer/Connection/ProtocolError.html +1 -1
  16. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +5 -5
  17. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +12 -12
  18. data/docs/Puppeteer/ConsoleMessage.html +1 -1
  19. data/docs/Puppeteer/ConsoleMessage/Location.html +1 -1
  20. data/docs/Puppeteer/DOMWorld.html +106 -32
  21. data/docs/Puppeteer/DOMWorld/DetachedError.html +1 -1
  22. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +1 -1
  23. data/docs/Puppeteer/DebugPrint.html +1 -1
  24. data/docs/Puppeteer/Device.html +1 -1
  25. data/docs/Puppeteer/Devices.html +1 -1
  26. data/docs/Puppeteer/ElementHandle.html +525 -207
  27. data/docs/Puppeteer/ElementHandle/BoundingBox.html +507 -0
  28. data/docs/Puppeteer/ElementHandle/BoxModel.html +404 -0
  29. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +5 -5
  30. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +5 -5
  31. data/docs/Puppeteer/ElementHandle/Point.html +40 -29
  32. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +1 -1
  33. data/docs/Puppeteer/EmulationManager.html +1 -1
  34. data/docs/Puppeteer/EventCallbackable.html +83 -17
  35. data/docs/Puppeteer/EventCallbackable/EventListeners.html +1 -1
  36. data/docs/Puppeteer/ExecutionContext.html +1 -1
  37. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +1 -1
  38. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +1 -1
  39. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +1 -1
  40. data/docs/Puppeteer/FileChooser.html +1 -1
  41. data/docs/Puppeteer/Frame.html +450 -292
  42. data/docs/Puppeteer/FrameManager.html +23 -27
  43. data/docs/Puppeteer/FrameManager/NavigationError.html +1 -1
  44. data/docs/Puppeteer/IfPresent.html +1 -1
  45. data/docs/Puppeteer/JSHandle.html +1 -1
  46. data/docs/Puppeteer/Keyboard.html +1 -1
  47. data/docs/Puppeteer/Keyboard/KeyDefinition.html +1 -1
  48. data/docs/Puppeteer/Keyboard/KeyDescription.html +1 -1
  49. data/docs/Puppeteer/Launcher.html +1 -1
  50. data/docs/Puppeteer/Launcher/Base.html +1 -1
  51. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +1 -1
  52. data/docs/Puppeteer/Launcher/BrowserOptions.html +1 -1
  53. data/docs/Puppeteer/Launcher/Chrome.html +36 -31
  54. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +1 -1
  55. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +1 -1
  56. data/docs/Puppeteer/Launcher/LaunchOptions.html +1 -1
  57. data/docs/Puppeteer/LifecycleWatcher.html +1 -1
  58. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +1 -1
  59. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +1 -1
  60. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +1 -1
  61. data/docs/Puppeteer/Mouse.html +31 -41
  62. data/docs/Puppeteer/Mouse/Button.html +1 -1
  63. data/docs/Puppeteer/NetworkManager.html +2 -2
  64. data/docs/Puppeteer/NetworkManager/Credentials.html +1 -1
  65. data/docs/Puppeteer/Page.html +502 -299
  66. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +5 -5
  67. data/docs/Puppeteer/Page/ScreenshotOptions.html +1 -1
  68. data/docs/Puppeteer/Page/ScriptTag.html +24 -24
  69. data/docs/Puppeteer/Page/StyleTag.html +19 -19
  70. data/docs/Puppeteer/Page/TargetCrashedError.html +1 -1
  71. data/docs/Puppeteer/RemoteObject.html +110 -39
  72. data/docs/Puppeteer/Target.html +150 -198
  73. data/docs/Puppeteer/Target/InitializeFailure.html +1 -1
  74. data/docs/Puppeteer/Target/TargetInfo.html +1 -1
  75. data/docs/Puppeteer/TimeoutError.html +2 -2
  76. data/docs/Puppeteer/TimeoutSettings.html +1 -1
  77. data/docs/Puppeteer/TouchScreen.html +1 -1
  78. data/docs/Puppeteer/Viewport.html +81 -1
  79. data/docs/Puppeteer/WaitTask.html +1 -1
  80. data/docs/Puppeteer/WaitTask/TerminatedError.html +1 -1
  81. data/docs/Puppeteer/WaitTask/TimeoutError.html +1 -1
  82. data/docs/Puppeteer/WebSocket.html +26 -26
  83. data/docs/Puppeteer/WebSocket/DriverImpl.html +1 -1
  84. data/docs/Puppeteer/WebSocket/TransportError.html +124 -0
  85. data/docs/Puppeteer/WebSocketTransport.html +9 -9
  86. data/docs/Puppeteer/WebSocktTransportError.html +1 -1
  87. data/docs/_index.html +28 -21
  88. data/docs/class_list.html +1 -1
  89. data/docs/file.README.html +3 -1
  90. data/docs/index.html +3 -1
  91. data/docs/method_list.html +659 -515
  92. data/docs/top-level-namespace.html +1 -1
  93. data/lib/puppeteer.rb +36 -12
  94. data/lib/puppeteer/browser.rb +26 -6
  95. data/lib/puppeteer/concurrent_ruby_utils.rb +6 -2
  96. data/lib/puppeteer/connection.rb +13 -1
  97. data/lib/puppeteer/dom_world.rb +11 -12
  98. data/lib/puppeteer/element_handle.rb +66 -108
  99. data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
  100. data/lib/puppeteer/element_handle/box_model.rb +19 -0
  101. data/lib/puppeteer/element_handle/point.rb +26 -0
  102. data/lib/puppeteer/errors.rb +1 -3
  103. data/lib/puppeteer/event_callbackable.rb +11 -0
  104. data/lib/puppeteer/frame.rb +20 -1
  105. data/lib/puppeteer/launcher.rb +6 -6
  106. data/lib/puppeteer/launcher/chrome.rb +10 -8
  107. data/lib/puppeteer/mouse.rb +3 -8
  108. data/lib/puppeteer/page.rb +43 -4
  109. data/lib/puppeteer/remote_object.rb +9 -0
  110. data/lib/puppeteer/target.rb +25 -25
  111. data/lib/puppeteer/version.rb +1 -1
  112. data/lib/puppeteer/viewport.rb +18 -0
  113. data/lib/puppeteer/web_socket.rb +3 -1
  114. data/lib/puppeteer/web_socket_transport.rb +8 -8
  115. data/puppeteer-ruby.png +0 -0
  116. metadata +9 -4
  117. data/Dockerfile +0 -6
  118. data/docker-compose.yml +0 -15
@@ -116,7 +116,7 @@
116
116
  </div>
117
117
 
118
118
  <div id="footer">
119
- Generated on Thu Jun 4 23:54:40 2020 by
119
+ Generated on Wed Jun 24 03:07:58 2020 by
120
120
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
121
121
  0.9.24 (ruby-2.6.3).
122
122
  </div>
@@ -63,17 +63,33 @@ class Puppeteer
63
63
  end
64
64
  end
65
65
 
66
- # @param {string} projectRoot
67
- # @param {string} preferredRevision
68
- # @param {boolean} isPuppeteerCore
66
+ # @param project_root [String]
67
+ # @param prefereed_revision [String]
68
+ # @param is_puppeteer_core [String]
69
69
  def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
70
70
  @project_root = project_root
71
71
  @preferred_revision = preferred_revision
72
72
  @is_puppeteer_core = is_puppeteer_core
73
73
  end
74
74
 
75
- # @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions & {product?: string, extraPrefsFirefox?: !object})=} options
76
- # @return {!Promise<!Puppeteer.Browser>}
75
+ # @param product [String]
76
+ # @param executable_path [String]
77
+ # @param ignore_default_args [Array<String>|nil]
78
+ # @param handle_SIGINT [Boolean]
79
+ # @param handle_SIGTERM [Boolean]
80
+ # @param handle_SIGHUP [Boolean]
81
+ # @param timeout [Integer]
82
+ # @param dumpio [Boolean]
83
+ # @param env [Hash]
84
+ # @param pipe [Boolean]
85
+ # @param args [Array<String>]
86
+ # @param user_data_dir [String]
87
+ # @param devtools [Boolean]
88
+ # @param headless [Boolean]
89
+ # @param ignore_https_errors [Boolean]
90
+ # @param default_viewport [Puppeteer::Viewport|nil]
91
+ # @param slow_mo [Integer]
92
+ # @return [Puppeteer::Browser]
77
93
  def launch(
78
94
  product: nil,
79
95
  executable_path: nil,
@@ -125,8 +141,13 @@ class Puppeteer
125
141
  end
126
142
  end
127
143
 
128
- # @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
129
- # @return {!Promise<!Puppeteer.Browser>}
144
+ # @param browser_ws_endpoint [String]
145
+ # @param browser_url [String]
146
+ # @param transport [Puppeteer::WebSocketTransport]
147
+ # @param ignore_https_errors [Boolean]
148
+ # @param default_viewport [Puppeteer::Viewport|nil]
149
+ # @param slow_mo [Integer]
150
+ # @return [Puppeteer::Browser]
130
151
  def connect(
131
152
  browser_ws_endpoint: nil,
132
153
  browser_url: nil,
@@ -151,7 +172,7 @@ class Puppeteer
151
172
  end
152
173
  end
153
174
 
154
- # @return {string}
175
+ # @return [String]
155
176
  def executable_path
156
177
  launcher.executable_path
157
178
  end
@@ -165,12 +186,12 @@ class Puppeteer
165
186
  )
166
187
  end
167
188
 
168
- # @return {string}
189
+ # @return [String]
169
190
  def product
170
191
  launcher.product
171
192
  end
172
193
 
173
- # @return {Puppeteer::Devices}
194
+ # @return [Puppeteer::Devices]
174
195
  def devices
175
196
  Puppeteer::Devices
176
197
  end
@@ -180,8 +201,11 @@ class Puppeteer
180
201
  # # ???
181
202
  # end
182
203
 
183
- # @param {!Launcher.ChromeArgOptions=} options
184
- # @return {!Array<string>}
204
+ # @param args [Array<String>]
205
+ # @param user_data_dir [String]
206
+ # @param devtools [Boolean]
207
+ # @param headless [Boolean]
208
+ # @return [Array<String>]
185
209
  def default_args(args: nil, user_data_dir: nil, devtools: nil, headless: nil)
186
210
  options = {
187
211
  args: args,
@@ -46,7 +46,7 @@ class Puppeteer::Browser
46
46
  @contexts[context_id] = Puppeteer::BrowserContext.new(@connection, self. context_id)
47
47
  end
48
48
  @targets = {}
49
- @connection.on_event 'Events.CDPSession.Disconnected' do
49
+ @connection.on_event 'Events.Connection.Disconnected' do
50
50
  emit_event 'Events.Browser.Disconnected'
51
51
  end
52
52
  @connection.on_event 'Target.targetCreated', &method(:handle_target_created)
@@ -54,6 +54,22 @@ class Puppeteer::Browser
54
54
  @connection.on_event 'Target.targetInfoChanged', &method(:handle_target_info_changed)
55
55
  end
56
56
 
57
+ EVENT_MAPPINGS = {
58
+ disconnected: 'Events.Browser.Disconnected',
59
+ targetcreated: 'Events.Browser.TargetCreated',
60
+ targetchanged: 'Events.Browser.TargetChanged',
61
+ targetdestroyed: 'Events.Browser.TargetDestroyed',
62
+ }
63
+
64
+ # @param event_name [Symbol] either of :disconnected, :targetcreated, :targetchanged, :targetdestroyed
65
+ def on(event_name, &block)
66
+ unless EVENT_MAPPINGS.has_key?(event_name.to_sym)
67
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{EVENT_MAPPINGS.keys.join(", ")}")
68
+ end
69
+
70
+ add_event_listener(EVENT_MAPPINGS[event_name.to_sym], &block)
71
+ end
72
+
57
73
  # @return [Puppeteer::BrowserRunner::BrowserProcess]
58
74
  def process
59
75
  @process
@@ -102,8 +118,7 @@ class Puppeteer::Browser
102
118
  )
103
119
  # assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
104
120
  @targets[target_info.target_id] = target
105
-
106
- target.on_initialize_succeeded do
121
+ if await target.initialized_promise
107
122
  emit_event 'Events.Browser.TargetCreated', target
108
123
  context.emit_event 'Events.BrowserContext.TargetCreated', target
109
124
  end
@@ -118,10 +133,10 @@ class Puppeteer::Browser
118
133
  def handle_target_destroyed(event)
119
134
  target_id = event['targetId']
120
135
  target = @targets[target_id]
121
- target.handle_initialized(false)
136
+ target.ignore_initialize_callback_promise
122
137
  @targets.delete(target_id)
123
- target.handle_closed
124
- target.on_initialize_succeeded do
138
+ target.closed_callback
139
+ if await target.initialized_promise
125
140
  emit_event 'Events.Browser.TargetDestroyed', target
126
141
  target.browser_context.emit_event 'Events.BrowserContext.TargetDestroyed', target
127
142
  end
@@ -192,6 +207,11 @@ class Puppeteer::Browser
192
207
  targets.first { |target| target.type == 'browser' }
193
208
  end
194
209
 
210
+ # used only in Target#opener
211
+ private def find_target_by_id(target_id)
212
+ @targets[target_id]
213
+ end
214
+
195
215
  # @param {function(!Target):boolean} predicate
196
216
  # @param {{timeout?: number}=} options
197
217
  # @return {!Promise<!Target>}
@@ -29,8 +29,12 @@ module Puppeteer::ConcurrentRubyUtils
29
29
  Concurrent::Promises.future(&block)
30
30
  end
31
31
 
32
- def resolvable_future
33
- Concurrent::Promises.resolvable_future
32
+ def resolvable_future(&block)
33
+ future = Concurrent::Promises.resolvable_future
34
+ if block
35
+ block.call(future)
36
+ end
37
+ future
34
38
  end
35
39
  end
36
40
 
@@ -44,7 +44,9 @@ class Puppeteer::Connection
44
44
 
45
45
  @transport = transport
46
46
  @transport.on_message do |data|
47
- async_handle_message(JSON.parse(data))
47
+ message = JSON.parse(data)
48
+ sleep_before_handling_message(message)
49
+ async_handle_message(message)
48
50
  end
49
51
  @transport.on_close do |reason, code|
50
52
  handle_close(reason, code)
@@ -54,6 +56,16 @@ class Puppeteer::Connection
54
56
  @closed = false
55
57
  end
56
58
 
59
+ private def sleep_before_handling_message(message)
60
+ # Puppeteer doesn't handle any Network monitoring responses.
61
+ # So we don't have to sleep.
62
+ return if message['method']&.start_with?('Network.')
63
+
64
+ # For some reasons, sleeping a bit reduces trivial errors...
65
+ # 4ms is an interval of internal shared timer of WebKit.
66
+ sleep 0.004
67
+ end
68
+
57
69
  def self.from_session(session)
58
70
  session.connection
59
71
  end
@@ -97,17 +97,18 @@ class Puppeteer::DOMWorld
97
97
  document.S(selector)
98
98
  end
99
99
 
100
- class DocumentEvaluationError < StandardError; end
101
-
102
100
  private def evaluate_document
103
101
  # sometimes execution_context.evaluate_handle('document') returns null object.
104
102
  # D, [2020-04-24T02:17:51.023631 #220] DEBUG -- : RECV << {"id"=>20, "result"=>{"result"=>{"type"=>"object", "subtype"=>"null", "value"=>nil}}, "sessionId"=>"78E9CF1E14D81294E320E7C20E5CDE06"}
105
103
  # retry if so.
106
- 5.times do
107
- handle = execution_context.evaluate_handle('document')
108
- return handle if handle.is_a?(Puppeteer::ElementHandle)
104
+ Timeout.timeout(3) do
105
+ loop do
106
+ handle = execution_context.evaluate_handle('document')
107
+ return handle if handle.is_a?(Puppeteer::ElementHandle)
108
+ end
109
109
  end
110
- raise DocumentEvaluationError.new("'document' object cannot be evaluated as an Element")
110
+ rescue Timeout::Error
111
+ raise 'Bug of puppeteer-ruby...'
111
112
  end
112
113
 
113
114
  private def document
@@ -410,12 +411,10 @@ class Puppeteer::DOMWorld
410
411
  # return new WaitTask(this, pageFunction, 'function', polling, timeout, ...args).promise;
411
412
  # }
412
413
 
413
- # /**
414
- # * @return {!Promise<string>}
415
- # */
416
- # async title() {
417
- # return this.evaluate(() => document.title);
418
- # }
414
+ # @return [String]
415
+ def title
416
+ evaluate('() => document.title')
417
+ end
419
418
 
420
419
  # @param selector_or_xpath [String]
421
420
  # @param is_xpath [Boolean]
@@ -1,4 +1,6 @@
1
- require 'mime/types'
1
+ require_relative './element_handle/bounding_box'
2
+ require_relative './element_handle/box_model'
3
+ require_relative './element_handle/point'
2
4
 
3
5
  class Puppeteer::ElementHandle < Puppeteer::JSHandle
4
6
  include Puppeteer::IfPresent
@@ -53,29 +55,6 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
53
55
  sleep 0.16
54
56
  end
55
57
 
56
- class Point
57
- def initialize(x:, y:)
58
- @x = x
59
- @y = y
60
- end
61
-
62
- def +(other)
63
- Point.new(
64
- x: @x + other.x,
65
- y: @y + other.y,
66
- )
67
- end
68
-
69
- def /(num)
70
- Point.new(
71
- x: @x / num,
72
- y: @y / num,
73
- )
74
- end
75
-
76
- attr_reader :x, :y
77
- end
78
-
79
58
  class ElementNotVisibleError < StandardError
80
59
  def initialize
81
60
  super("Node is either not visible or not an HTMLElement")
@@ -105,15 +84,6 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
105
84
  quads.first.reduce(:+) / 4
106
85
  end
107
86
 
108
- # /**
109
- # * @return {!Promise<void|Protocol.DOM.getBoxModelReturnValue>}
110
- # */
111
- # _getBoxModel() {
112
- # return this._client.send('DOM.getBoxModel', {
113
- # objectId: this._remoteObject.objectId
114
- # }).catch(error => debugError(error));
115
- # }
116
-
117
87
  # @param quad [Array<number>]
118
88
  # @return [Array<Point>]
119
89
  private def from_protocol_quad(quad)
@@ -265,89 +235,77 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
265
235
  press(key, delay: delay)
266
236
  end
267
237
 
268
- # /**
269
- # * @return {!Promise<?{x: number, y: number, width: number, height: number}>}
270
- # */
271
- # async boundingBox() {
272
- # const result = await this._getBoxModel();
273
-
274
- # if (!result)
275
- # return null;
276
-
277
- # const quad = result.model.border;
278
- # const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
279
- # const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
280
- # const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
281
- # const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
282
-
283
- # return {x, y, width, height};
284
- # }
285
-
286
- # /**
287
- # * @return {!Promise<?BoxModel>}
288
- # */
289
- # async boxModel() {
290
- # const result = await this._getBoxModel();
291
-
292
- # if (!result)
293
- # return null;
294
-
295
- # const {content, padding, border, margin, width, height} = result.model;
296
- # return {
297
- # content: this._fromProtocolQuad(content),
298
- # padding: this._fromProtocolQuad(padding),
299
- # border: this._fromProtocolQuad(border),
300
- # margin: this._fromProtocolQuad(margin),
301
- # width,
302
- # height
303
- # };
304
- # }
305
-
306
- # /**
307
- # *
308
- # * @param {!Object=} options
309
- # * @returns {!Promise<string|!Buffer>}
310
- # */
311
- # async screenshot(options = {}) {
312
- # let needsViewportReset = false;
313
-
314
- # let boundingBox = await this.boundingBox();
315
- # assert(boundingBox, 'Node is either not visible or not an HTMLElement');
316
-
317
- # const viewport = this._page.viewport();
318
-
319
- # if (viewport && (boundingBox.width > viewport.width || boundingBox.height > viewport.height)) {
320
- # const newViewport = {
321
- # width: Math.max(viewport.width, Math.ceil(boundingBox.width)),
322
- # height: Math.max(viewport.height, Math.ceil(boundingBox.height)),
323
- # };
324
- # await this._page.setViewport(Object.assign({}, viewport, newViewport));
238
+ # @return [BoundingBox|nil]
239
+ def bounding_box
240
+ if_present(box_model) do |result_model|
241
+ quads = result_model.border
242
+
243
+ x = quads.map(&:x).min
244
+ y = quads.map(&:y).min
245
+ BoundingBox.new(
246
+ x: x,
247
+ y: y,
248
+ width: quads.map(&:x).max - x,
249
+ height: quads.map(&:y).max - y,
250
+ )
251
+ end
252
+ end
325
253
 
326
- # needsViewportReset = true;
327
- # }
254
+ # @return [BoxModel|nil]
255
+ def box_model
256
+ if_present(@remote_object.box_model(@client)) do |result|
257
+ BoxModel.new(result['model'])
258
+ end
259
+ end
328
260
 
329
- # await this._scrollIntoViewIfNeeded();
261
+ def screenshot(options = {})
262
+ needs_viewport_reset = false
330
263
 
331
- # boundingBox = await this.boundingBox();
332
- # assert(boundingBox, 'Node is either not visible or not an HTMLElement');
333
- # assert(boundingBox.width !== 0, 'Node has 0 width.');
334
- # assert(boundingBox.height !== 0, 'Node has 0 height.');
264
+ box = bounding_box
265
+ unless box
266
+ raise ElementNotVisibleError.new
267
+ end
335
268
 
336
- # const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics');
269
+ viewport = @page.viewport
270
+ if viewport && (box.width > viewport.width || box.height > viewport.height)
271
+ new_viewport = viewport.merge(
272
+ width: [viewport.width, box.width.to_i].min,
273
+ height: [viewport.height, box.height.to_i].min,
274
+ )
275
+ @page.viewport = new_viewport
337
276
 
338
- # const clip = Object.assign({}, boundingBox);
339
- # clip.x += pageX;
340
- # clip.y += pageY;
277
+ needs_viewport_reset = true
278
+ end
279
+ scroll_into_view_if_needed
341
280
 
342
- # const imageData = await this._page.screenshot(Object.assign({}, {
343
- # clip
344
- # }, options));
281
+ box = bounding_box
282
+ unless box
283
+ raise ElementNotVisibleError.new
284
+ end
285
+ if box.width == 0
286
+ raise 'Node has 0 width.'
287
+ end
288
+ if box.height == 0
289
+ raise 'Node has 0 height.'
290
+ end
345
291
 
346
- # if (needsViewportReset)
347
- # await this._page.setViewport(viewport);
292
+ layout_metrics = @client.send_message('Page.getLayoutMetrics')
293
+ page_x = layout_metrics["layoutViewport"]["pageX"]
294
+ page_y = layout_metrics["layoutViewport"]["pageY"]
295
+
296
+ clip = {
297
+ x: page_x + box.x,
298
+ y: page_y + box.y,
299
+ width: box.width,
300
+ height: box.height,
301
+ }
348
302
 
349
- # return imageData;
350
- # }
303
+ @page.screenshot({ clip: clip }.merge(options))
304
+ ensure
305
+ if needs_viewport_reset
306
+ @page.viewport = viewport
307
+ end
308
+ end
351
309
 
352
310
  # `$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
353
311
  # @param selector [String]