puppeteer-ruby 0.0.8 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
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]