puppeteer-ruby 0.0.14 → 0.0.19
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/.circleci/config.yml +25 -2
- data/.github/workflows/reviewdog.yml +1 -1
- data/.rubocop.yml +49 -3
- data/Dockerfile +9 -0
- data/README.md +3 -1
- data/docker-compose.yml +34 -0
- data/lib/puppeteer.rb +16 -12
- data/lib/puppeteer/browser.rb +36 -9
- data/lib/puppeteer/browser_context.rb +35 -5
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/connection.rb +6 -1
- data/lib/puppeteer/debug_print.rb +2 -2
- data/lib/puppeteer/define_async_method.rb +1 -1
- data/lib/puppeteer/dom_world.rb +78 -52
- data/lib/puppeteer/element_handle.rb +49 -20
- data/lib/puppeteer/env.rb +23 -0
- data/lib/puppeteer/frame.rb +17 -31
- data/lib/puppeteer/js_handle.rb +37 -27
- data/lib/puppeteer/keyboard.rb +3 -2
- data/lib/puppeteer/launcher.rb +11 -1
- data/lib/puppeteer/launcher/base.rb +14 -4
- data/lib/puppeteer/launcher/chrome.rb +1 -1
- data/lib/puppeteer/launcher/firefox.rb +392 -0
- data/lib/puppeteer/mouse.rb +16 -0
- data/lib/puppeteer/network_manager.rb +163 -5
- data/lib/puppeteer/page.rb +195 -125
- data/lib/puppeteer/page/pdf_options.rb +166 -0
- data/lib/puppeteer/remote_object.rb +28 -1
- data/lib/puppeteer/request.rb +330 -0
- data/lib/puppeteer/response.rb +113 -0
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +1 -1
- data/lib/puppeteer/web_socket.rb +7 -0
- data/puppeteer-ruby.gemspec +2 -1
- metadata +25 -4
data/lib/puppeteer/mouse.rb
CHANGED
@@ -94,5 +94,21 @@ class Puppeteer::Mouse
|
|
94
94
|
)
|
95
95
|
end
|
96
96
|
|
97
|
+
# Dispatches a `mousewheel` event.
|
98
|
+
#
|
99
|
+
# @param delta_x [Integer]
|
100
|
+
# @param delta_y [Integer]
|
101
|
+
def wheel(delta_x: 0, delta_y: 0)
|
102
|
+
@client.send_message('Input.dispatchMouseEvent',
|
103
|
+
type: 'mouseWheel',
|
104
|
+
x: @x,
|
105
|
+
y: @y,
|
106
|
+
deltaX: delta_x,
|
107
|
+
deltaY: delta_y,
|
108
|
+
modifiers: @keyboard.modifiers,
|
109
|
+
pointerType: 'mouse',
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
97
113
|
define_async_method :async_up
|
98
114
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
class Puppeteer::NetworkManager
|
2
|
+
include Puppeteer::DebugPrint
|
2
3
|
include Puppeteer::EventCallbackable
|
4
|
+
include Puppeteer::IfPresent
|
3
5
|
|
4
6
|
class Credentials
|
5
7
|
# @param username [String|NilClass]
|
@@ -23,19 +25,39 @@ class Puppeteer::NetworkManager
|
|
23
25
|
@request_id_to_request = {}
|
24
26
|
|
25
27
|
# @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>}
|
26
|
-
@request_id_to_request_with_be_sent_event
|
28
|
+
@request_id_to_request_with_be_sent_event = {}
|
27
29
|
|
28
30
|
@extra_http_headers = {}
|
29
31
|
|
30
32
|
@offline = false
|
31
33
|
|
32
|
-
|
33
|
-
# this._attemptedAuthentications = new Set();
|
34
|
+
@attempted_authentications = Set.new
|
34
35
|
@user_request_interception_enabled = false
|
35
36
|
@protocol_request_interception_enabled = false
|
36
37
|
@user_cache_disabled = false
|
37
|
-
|
38
|
-
|
38
|
+
@request_id_to_interception_id = {}
|
39
|
+
|
40
|
+
@client.on_event('Fetch.requestPaused') do |event|
|
41
|
+
handle_request_paused(event)
|
42
|
+
end
|
43
|
+
@client.on_event('Fetch.authRequired') do |event|
|
44
|
+
handle_auth_required(event)
|
45
|
+
end
|
46
|
+
@client.on_event('Network.requestWillBeSent') do |event|
|
47
|
+
handle_request_will_be_sent(event)
|
48
|
+
end
|
49
|
+
@client.on_event('Network.requestServedFromCache') do |event|
|
50
|
+
handle_request_served_from_cache(event)
|
51
|
+
end
|
52
|
+
@client.on_event('Network.responseReceived') do |event|
|
53
|
+
handle_response_received(event)
|
54
|
+
end
|
55
|
+
@client.on_event('Network.loadingFinished') do |event|
|
56
|
+
handle_loading_finished(event)
|
57
|
+
end
|
58
|
+
@client.on_event('Network.loadingFailed') do |event|
|
59
|
+
handle_loading_failed(event)
|
60
|
+
end
|
39
61
|
end
|
40
62
|
|
41
63
|
def init
|
@@ -119,4 +141,140 @@ class Puppeteer::NetworkManager
|
|
119
141
|
cache_disabled = @user_cache_disabled || @protocol_request_interception_enabled
|
120
142
|
@client.send_message('Network.setCacheDisabled', cacheDisabled: cache_disabled)
|
121
143
|
end
|
144
|
+
|
145
|
+
private def handle_request_will_be_sent(event)
|
146
|
+
# Request interception doesn't happen for data URLs with Network Service.
|
147
|
+
if @protocol_request_interception_enabled && !event['request']['url'].start_with?('data:')
|
148
|
+
request_id = event['requestId']
|
149
|
+
interception_id = @request_id_to_interception_id.delete(request_id)
|
150
|
+
if interception_id
|
151
|
+
handle_request(event, interception_id)
|
152
|
+
else
|
153
|
+
@request_id_to_request_with_be_sent_event[request_id] = event
|
154
|
+
end
|
155
|
+
return
|
156
|
+
end
|
157
|
+
handle_request(event, nil)
|
158
|
+
end
|
159
|
+
|
160
|
+
private def handle_auth_required(event)
|
161
|
+
response = 'Default'
|
162
|
+
if @attempted_authentications.include?(event['requestId'])
|
163
|
+
response = 'CancelAuth'
|
164
|
+
elsif @credentials
|
165
|
+
response = 'ProvideCredentials'
|
166
|
+
@attempted_authentications << event['requestId']
|
167
|
+
end
|
168
|
+
|
169
|
+
username = @credentials&.username
|
170
|
+
password = @credentials&.password
|
171
|
+
|
172
|
+
begin
|
173
|
+
@client.send_message('Fetch.continueWithAuth',
|
174
|
+
requestId: event['requestId'],
|
175
|
+
authChallengeResponse: {
|
176
|
+
response: response,
|
177
|
+
username: username,
|
178
|
+
password: password,
|
179
|
+
},
|
180
|
+
)
|
181
|
+
rescue => err
|
182
|
+
debug_puts(err)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
private def handle_request_paused(event)
|
187
|
+
if !@user_request_interception_enabled && @protocol_request_interception_enabled
|
188
|
+
begin
|
189
|
+
@client.send_message('Fetch.continueRequest', requestId: event['requestId'])
|
190
|
+
rescue => err
|
191
|
+
debug_puts(err)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
request_id = event['networkId']
|
196
|
+
interception_id = event['requestId']
|
197
|
+
if request_id && (request_will_be_sent_event = @request_id_to_request_with_be_sent_event.delete(request_id))
|
198
|
+
handle_request(request_will_be_sent_event, interception_id)
|
199
|
+
else
|
200
|
+
@request_id_to_interception_id[request_id] = interception_id
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private def handle_request(event, interception_id)
|
205
|
+
redirect_chain = []
|
206
|
+
if event['redirectResponse']
|
207
|
+
if_present(@request_id_to_request[event['requestId']]) do |request|
|
208
|
+
handle_request_redirect(request, event['redirectResponse'])
|
209
|
+
redirect_chain = request.internal.redirect_chain
|
210
|
+
end
|
211
|
+
end
|
212
|
+
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
213
|
+
request = Puppeteer::Request.new(@client, frame, interception_id, @user_request_interception_enabled, event, redirect_chain)
|
214
|
+
@request_id_to_request[event['requestId']] = request
|
215
|
+
emit_event 'Events.NetworkManager.Request', request
|
216
|
+
end
|
217
|
+
|
218
|
+
private def handle_request_served_from_cache(event)
|
219
|
+
if_present(@request_id_to_request[event['requestId']]) do |request|
|
220
|
+
request.internal.from_memory_cache = true
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# @param request [Puppeteer::Request]
|
225
|
+
# @param response_payload [Hash]
|
226
|
+
private def handle_request_redirect(request, response_payload)
|
227
|
+
response = Puppeteer::Response.new(@client, request, response_payload)
|
228
|
+
request.internal.response = response
|
229
|
+
request.internal.redirect_chain << request
|
230
|
+
response.internal.body_loaded_promise.reject(Puppeteer::Response::Redirected.new)
|
231
|
+
@request_id_to_request.delete(request.internal.request_id)
|
232
|
+
@attempted_authentications.delete(request.internal.interception_id)
|
233
|
+
emit_event 'Events.NetworkManager.Response', response
|
234
|
+
emit_event 'Events.NetworkManager.RequestFinished', request
|
235
|
+
end
|
236
|
+
|
237
|
+
# @param event [Hash]
|
238
|
+
private def handle_response_received(event)
|
239
|
+
request = @request_id_to_request[event['requestId']]
|
240
|
+
# FileUpload sends a response without a matching request.
|
241
|
+
return unless request
|
242
|
+
|
243
|
+
response = Puppeteer::Response.new(@client, request, event['response'])
|
244
|
+
request.internal.response = response
|
245
|
+
emit_event 'Events.NetworkManager.Response', response
|
246
|
+
end
|
247
|
+
|
248
|
+
private def handle_loading_finished(event)
|
249
|
+
request = @request_id_to_request[event['requestId']]
|
250
|
+
# For certain requestIds we never receive requestWillBeSent event.
|
251
|
+
# @see https://crbug.com/750469
|
252
|
+
return unless request
|
253
|
+
|
254
|
+
|
255
|
+
# Under certain conditions we never get the Network.responseReceived
|
256
|
+
# event from protocol. @see https://crbug.com/883475
|
257
|
+
if_present(request.response) do |response|
|
258
|
+
response.internal.body_loaded_promise.fulfill(nil)
|
259
|
+
end
|
260
|
+
|
261
|
+
@request_id_to_request.delete(request.internal.request_id)
|
262
|
+
@attempted_authentications.delete(request.internal.interception_id)
|
263
|
+
emit_event 'Events.NetworkManager.RequestFinished', request
|
264
|
+
end
|
265
|
+
|
266
|
+
private def handle_loading_failed(event)
|
267
|
+
request = @request_id_to_request[event['requestId']]
|
268
|
+
# For certain requestIds we never receive requestWillBeSent event.
|
269
|
+
# @see https://crbug.com/750469
|
270
|
+
return unless request
|
271
|
+
|
272
|
+
request.internal.failure_text = event['errorText']
|
273
|
+
if_present(request.response) do |response|
|
274
|
+
response.internal.body_loaded_promise.fulfill(nil)
|
275
|
+
end
|
276
|
+
@request_id_to_request.delete(request.internal.request_id)
|
277
|
+
@attempted_authentications.delete(request.internal.interception_id)
|
278
|
+
emit_event 'Events.NetworkManager.RequestFailed', request
|
279
|
+
end
|
122
280
|
end
|
data/lib/puppeteer/page.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'base64'
|
2
|
+
require "stringio"
|
2
3
|
|
4
|
+
require_relative './page/pdf_options'
|
3
5
|
require_relative './page/screenshot_options'
|
4
6
|
|
5
7
|
class Puppeteer::Page
|
@@ -264,7 +266,13 @@ class Puppeteer::Page
|
|
264
266
|
@frame_manager.main_frame
|
265
267
|
end
|
266
268
|
|
267
|
-
attr_reader :
|
269
|
+
attr_reader :touch_screen, :coverage, :accessibility
|
270
|
+
|
271
|
+
def keyboard(&block)
|
272
|
+
@keyboard.instance_eval(&block) unless block.nil?
|
273
|
+
|
274
|
+
@keyboard
|
275
|
+
end
|
268
276
|
|
269
277
|
def frames
|
270
278
|
@frame_manager.frames
|
@@ -649,13 +657,14 @@ class Puppeteer::Page
|
|
649
657
|
main_frame.content
|
650
658
|
end
|
651
659
|
|
652
|
-
# @param
|
653
|
-
# @param
|
660
|
+
# @param html [String]
|
661
|
+
# @param timeout [Integer]
|
662
|
+
# @param wait_until [String|Array<String>]
|
654
663
|
def set_content(html, timeout: nil, wait_until: nil)
|
655
664
|
main_frame.set_content(html, timeout: timeout, wait_until: wait_until)
|
656
665
|
end
|
657
666
|
|
658
|
-
# @param
|
667
|
+
# @param html [String]
|
659
668
|
def content=(html)
|
660
669
|
main_frame.set_content(html)
|
661
670
|
end
|
@@ -678,49 +687,111 @@ class Puppeteer::Page
|
|
678
687
|
).first
|
679
688
|
end
|
680
689
|
|
681
|
-
# @param timeout [number|nil]
|
682
|
-
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
683
690
|
private def wait_for_navigation(timeout: nil, wait_until: nil)
|
684
691
|
main_frame.send(:wait_for_navigation, timeout: timeout, wait_until: wait_until)
|
685
692
|
end
|
686
693
|
|
694
|
+
# @!method async_wait_for_navigation(timeout: nil, wait_until: nil)
|
695
|
+
#
|
696
|
+
# @param timeout [number|nil]
|
697
|
+
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
687
698
|
define_async_method :async_wait_for_navigation
|
688
699
|
|
689
|
-
|
690
|
-
|
691
|
-
# * @param {!{timeout?: number}=} options
|
692
|
-
# * @return {!Promise<!Puppeteer.Request>}
|
693
|
-
# */
|
694
|
-
# async waitForRequest(urlOrPredicate, options = {}) {
|
695
|
-
# const {
|
696
|
-
# timeout = this._timeoutSettings.timeout(),
|
697
|
-
# } = options;
|
698
|
-
# return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => {
|
699
|
-
# if (helper.isString(urlOrPredicate))
|
700
|
-
# return (urlOrPredicate === request.url());
|
701
|
-
# if (typeof urlOrPredicate === 'function')
|
702
|
-
# return !!(urlOrPredicate(request));
|
703
|
-
# return false;
|
704
|
-
# }, timeout, this._sessionClosePromise());
|
705
|
-
# }
|
700
|
+
private def wait_for_network_manager_event(event_name, predicate:, timeout:)
|
701
|
+
option_timeout = timeout || @timeout_settings.timeout
|
706
702
|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
703
|
+
@wait_for_network_manager_event_listener_ids ||= {}
|
704
|
+
if_present(@wait_for_network_manager_event_listener_ids[event_name]) do |listener_id|
|
705
|
+
@frame_manager.network_manager.remove_event_listener(listener_id)
|
706
|
+
end
|
707
|
+
|
708
|
+
promise = resolvable_future
|
709
|
+
|
710
|
+
@wait_for_network_manager_event_listener_ids[event_name] =
|
711
|
+
@frame_manager.network_manager.add_event_listener(event_name) do |event_target|
|
712
|
+
if predicate.call(event_target)
|
713
|
+
promise.fulfill(nil)
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
begin
|
718
|
+
Timeout.timeout(option_timeout / 1000.0) do
|
719
|
+
await_any(promise, session_close_promise)
|
720
|
+
end
|
721
|
+
rescue Timeout::Error
|
722
|
+
raise Puppeteer::TimeoutError.new("waiting for #{event_name} failed: timeout #{timeout}ms exceeded")
|
723
|
+
ensure
|
724
|
+
@frame_manager.network_manager.remove_event_listener(@wait_for_network_manager_event_listener_ids[event_name])
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
private def session_close_promise
|
729
|
+
@disconnect_promise ||= resolvable_future do |future|
|
730
|
+
@client.observe_first('Events.CDPSession.Disconnected') do
|
731
|
+
future.reject(Puppeteer::CDPSession::Error.new('Target Closed'))
|
732
|
+
end
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
private def wait_for_request(url: nil, predicate: nil, timeout: nil)
|
737
|
+
if !url && !predicate
|
738
|
+
raise ArgumentError.new('url or predicate must be specified')
|
739
|
+
end
|
740
|
+
if predicate && !predicate.is_a?(Proc)
|
741
|
+
raise ArgumentError.new('predicate must be a proc.')
|
742
|
+
end
|
743
|
+
request_predicate =
|
744
|
+
if url
|
745
|
+
-> (request) { request.url == url }
|
746
|
+
else
|
747
|
+
-> (request) { predicate.call(request) }
|
748
|
+
end
|
749
|
+
|
750
|
+
wait_for_network_manager_event('Events.NetworkManager.Request',
|
751
|
+
predicate: request_predicate,
|
752
|
+
timeout: timeout,
|
753
|
+
)
|
754
|
+
end
|
755
|
+
|
756
|
+
# @!method async_wait_for_request(url: nil, predicate: nil, timeout: nil)
|
757
|
+
#
|
758
|
+
# Waits until request URL matches or request matches the given predicate.
|
759
|
+
#
|
760
|
+
# Waits until request URL matches
|
761
|
+
# wait_for_request(url: 'https://example.com/awesome')
|
762
|
+
#
|
763
|
+
# Waits until request matches the given predicate
|
764
|
+
# wait_for_request(predicate: -> (req){ req.url.start_with?('https://example.com/search') })
|
765
|
+
#
|
766
|
+
# @param url [String]
|
767
|
+
# @param predicate [Proc(Puppeteer::Request -> Boolean)]
|
768
|
+
define_async_method :async_wait_for_request
|
769
|
+
|
770
|
+
private def wait_for_response(url: nil, predicate: nil, timeout: nil)
|
771
|
+
if !url && !predicate
|
772
|
+
raise ArgumentError.new('url or predicate must be specified')
|
773
|
+
end
|
774
|
+
if predicate && !predicate.is_a?(Proc)
|
775
|
+
raise ArgumentError.new('predicate must be a proc.')
|
776
|
+
end
|
777
|
+
response_predicate =
|
778
|
+
if url
|
779
|
+
-> (response) { response.url == url }
|
780
|
+
else
|
781
|
+
-> (response) { predicate.call(response) }
|
782
|
+
end
|
783
|
+
|
784
|
+
wait_for_network_manager_event('Events.NetworkManager.Response',
|
785
|
+
predicate: response_predicate,
|
786
|
+
timeout: timeout,
|
787
|
+
)
|
788
|
+
end
|
789
|
+
|
790
|
+
# @!method async_wait_for_response(url: nil, predicate: nil, timeout: nil)
|
791
|
+
#
|
792
|
+
# @param url [String]
|
793
|
+
# @param predicate [Proc(Puppeteer::Request -> Boolean)]
|
794
|
+
define_async_method :async_wait_for_response
|
724
795
|
|
725
796
|
# @param timeout [number|nil]
|
726
797
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
@@ -759,20 +830,19 @@ class Puppeteer::Page
|
|
759
830
|
@client.send_message('Emulation.setScriptExecutionDisabled', value: !enabled)
|
760
831
|
end
|
761
832
|
|
762
|
-
#
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
# await this._client.send('Page.setBypassCSP', { enabled });
|
767
|
-
# }
|
833
|
+
# @param enabled [Boolean]
|
834
|
+
def bypass_csp=(enabled)
|
835
|
+
@client.send_message('Page.setBypassCSP', enabled: enabled)
|
836
|
+
end
|
768
837
|
|
769
|
-
#
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
838
|
+
# @param media_type [String|Symbol|nil] either of (media, print, nil)
|
839
|
+
def emulate_media_type(media_type)
|
840
|
+
media_type_str = media_type.to_s
|
841
|
+
unless ['screen', 'print', ''].include?(media_type_str)
|
842
|
+
raise ArgumentError.new("Unsupported media type: #{media_type}")
|
843
|
+
end
|
844
|
+
@client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
|
845
|
+
end
|
776
846
|
|
777
847
|
# /**
|
778
848
|
# * @param {?Array<MediaFeature>} features
|
@@ -792,7 +862,7 @@ class Puppeteer::Page
|
|
792
862
|
|
793
863
|
# @param timezone_id [String?]
|
794
864
|
def emulate_timezone(timezone_id)
|
795
|
-
@client.send_message('Emulation.setTimezoneOverride', timezoneId:
|
865
|
+
@client.send_message('Emulation.setTimezoneOverride', timezoneId: timezone_id || '')
|
796
866
|
rescue => err
|
797
867
|
if err.message.include?('Invalid timezone')
|
798
868
|
raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
|
@@ -915,60 +985,53 @@ class Puppeteer::Page
|
|
915
985
|
buffer
|
916
986
|
end
|
917
987
|
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
# scale = 1,
|
925
|
-
# displayHeaderFooter = false,
|
926
|
-
# headerTemplate = '',
|
927
|
-
# footerTemplate = '',
|
928
|
-
# printBackground = false,
|
929
|
-
# landscape = false,
|
930
|
-
# pageRanges = '',
|
931
|
-
# preferCSSPageSize = false,
|
932
|
-
# margin = {},
|
933
|
-
# path = null
|
934
|
-
# } = options;
|
935
|
-
|
936
|
-
# let paperWidth = 8.5;
|
937
|
-
# let paperHeight = 11;
|
938
|
-
# if (options.format) {
|
939
|
-
# const format = Page.PaperFormats[options.format.toLowerCase()];
|
940
|
-
# assert(format, 'Unknown paper format: ' + options.format);
|
941
|
-
# paperWidth = format.width;
|
942
|
-
# paperHeight = format.height;
|
943
|
-
# } else {
|
944
|
-
# paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
|
945
|
-
# paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
|
946
|
-
# }
|
988
|
+
class ProtocolStreamReader
|
989
|
+
def initialize(client:, handle:, path:)
|
990
|
+
@client = client
|
991
|
+
@handle = handle
|
992
|
+
@path = path
|
993
|
+
end
|
947
994
|
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
995
|
+
def read
|
996
|
+
out = StringIO.new
|
997
|
+
File.open(@path, 'w') do |file|
|
998
|
+
eof = false
|
999
|
+
until eof
|
1000
|
+
response = @client.send_message('IO.read', handle: @handle)
|
1001
|
+
eof = response['eof']
|
1002
|
+
data =
|
1003
|
+
if response['base64Encoded']
|
1004
|
+
Base64.decode64(response['data'])
|
1005
|
+
else
|
1006
|
+
response['data']
|
1007
|
+
end
|
1008
|
+
out.write(data)
|
1009
|
+
file.write(data)
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
@client.send_message('IO.close', handle: @handle)
|
1013
|
+
out.read
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
class PrintToPdfIsNotImplementedError < StandardError
|
1018
|
+
def initialize
|
1019
|
+
super('pdf() is only available in headless mode. See https://github.com/puppeteer/puppeteer/issues/1829')
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
# @return [String]
|
1024
|
+
def pdf(options = {})
|
1025
|
+
pdf_options = PDFOptions.new(options)
|
1026
|
+
result = @client.send_message('Page.printToPDF', pdf_options.page_print_args)
|
1027
|
+
ProtocolStreamReader.new(client: @client, handle: result['stream'], path: pdf_options.path).read
|
1028
|
+
rescue => err
|
1029
|
+
if err.message.include?('PrintToPDF is not implemented')
|
1030
|
+
raise PrintToPdfIsNotImplementedError.new
|
1031
|
+
else
|
1032
|
+
raise
|
1033
|
+
end
|
1034
|
+
end
|
972
1035
|
|
973
1036
|
# @param run_before_unload [Boolean]
|
974
1037
|
def close(run_before_unload: false)
|
@@ -1023,8 +1086,19 @@ class Puppeteer::Page
|
|
1023
1086
|
define_async_method :async_select
|
1024
1087
|
|
1025
1088
|
# @param selector [String]
|
1026
|
-
def tap(selector)
|
1027
|
-
|
1089
|
+
def tap(selector: nil, &block)
|
1090
|
+
# resolves double meaning of tap.
|
1091
|
+
if selector.nil? && block
|
1092
|
+
# Original usage of Object#tap.
|
1093
|
+
#
|
1094
|
+
# browser.new_page.tap do |page|
|
1095
|
+
# ...
|
1096
|
+
# end
|
1097
|
+
super(&block)
|
1098
|
+
else
|
1099
|
+
# Puppeteer's Page#tap.
|
1100
|
+
main_frame.tap(selector)
|
1101
|
+
end
|
1028
1102
|
end
|
1029
1103
|
|
1030
1104
|
define_async_method :async_tap
|
@@ -1038,16 +1112,6 @@ class Puppeteer::Page
|
|
1038
1112
|
|
1039
1113
|
define_async_method :async_type_text
|
1040
1114
|
|
1041
|
-
# /**
|
1042
|
-
# * @param {(string|number|Function)} selectorOrFunctionOrTimeout
|
1043
|
-
# * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
|
1044
|
-
# * @param {!Array<*>} args
|
1045
|
-
# * @return {!Promise<!Puppeteer.JSHandle>}
|
1046
|
-
# */
|
1047
|
-
# waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
|
1048
|
-
# return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
|
1049
|
-
# }
|
1050
|
-
|
1051
1115
|
# @param selector [String]
|
1052
1116
|
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1053
1117
|
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
@@ -1058,6 +1122,11 @@ class Puppeteer::Page
|
|
1058
1122
|
|
1059
1123
|
define_async_method :async_wait_for_selector
|
1060
1124
|
|
1125
|
+
# @param milliseconds [Integer] the number of milliseconds to wait.
|
1126
|
+
def wait_for_timeout(milliseconds)
|
1127
|
+
main_frame.wait_for_timeout(milliseconds)
|
1128
|
+
end
|
1129
|
+
|
1061
1130
|
# @param xpath [String]
|
1062
1131
|
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
1063
1132
|
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
@@ -1068,12 +1137,13 @@ class Puppeteer::Page
|
|
1068
1137
|
|
1069
1138
|
define_async_method :async_wait_for_xpath
|
1070
1139
|
|
1071
|
-
# @param
|
1072
|
-
# @param
|
1073
|
-
# @param
|
1074
|
-
# @
|
1075
|
-
|
1076
|
-
|
1140
|
+
# @param page_function [String]
|
1141
|
+
# @param args [Integer|Array]
|
1142
|
+
# @param polling [String]
|
1143
|
+
# @param timeout [Integer]
|
1144
|
+
# @return [Puppeteer::JSHandle]
|
1145
|
+
def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
|
1146
|
+
main_frame.wait_for_function(page_function, args: args, polling: polling, timeout: timeout)
|
1077
1147
|
end
|
1078
1148
|
|
1079
1149
|
define_async_method :async_wait_for_function
|