puppeteer-ruby 0.0.22 → 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 627b2272803511db582250101edac0b5f1f0191abdf86255c6f16d0f088cc82e
4
- data.tar.gz: 70458a316f8ecce1b784bd84595c3f0fa248c77e671e4458384c494a1471c196
3
+ metadata.gz: 133c14dccb0da46cc01bff747bb2e36c1de1f7619ee4a8a898e798b3eb3947af
4
+ data.tar.gz: a7cc95948ddef2b45cc3b4a8faa82497334daeb1e47d92c97a7cbcbf9dccd887
5
5
  SHA512:
6
- metadata.gz: 2baf0cb4769fc7100495b3dc590293085e3219c661bd164cdb28f0aad1975f18cba17890029156a08873f879ef370c196c3d995bfd040d073d42d0b49acbeef3
7
- data.tar.gz: 67a23e46e7d439b76f438c59d062fe5daea0d216469e79ea1b4af2b8d60c4f9070455de22e51b9523fde2a67692919420bce2b05becf948120b71d426c4c560f
6
+ metadata.gz: de40a672ecbf7089e43463497bd877ed671e366ae022d6224bef0068c9ecbc5a949d4649454348af8d9823abf0e0effe84255ce3e73f8bd6f13c2d4ce2dd514e
7
+ data.tar.gz: 96821b3ec521338f6c2a08c31a65113acb1aa808e1f50b9fece78bcdcc19cb6f4072b2dd6d778a5a413f47733b1efa7d3f9ded85fdd0e0dce6c1729f53be47ab
@@ -116,6 +116,43 @@ Style/DefWithParentheses:
116
116
  Style/MethodDefParentheses:
117
117
  Enabled: true
118
118
 
119
+ Style/MethodCallWithArgsParentheses:
120
+ Enabled: true
121
+ IgnoredMethods:
122
+ # Gemfile, gemspec
123
+ - source
124
+ - add_dependency
125
+ - add_development_dependency
126
+
127
+ # include/require
128
+ - require
129
+ - require_relative
130
+ - include
131
+
132
+ # fundamental methods
133
+ - raise
134
+ - sleep
135
+
136
+ # RSpec
137
+ - describe
138
+ - it
139
+ - to
140
+ - not_to
141
+ - be
142
+ - eq
143
+
144
+ # async/await
145
+ - define_async_method
146
+ - await
147
+ - future
148
+
149
+ # utils
150
+ - debug_print
151
+ - debug_puts
152
+
153
+ Style/MethodCallWithoutArgsParentheses:
154
+ Enabled: true
155
+
119
156
  Style/RedundantFreeze:
120
157
  Enabled: true
121
158
 
data/README.md CHANGED
@@ -72,6 +72,21 @@ end
72
72
 
73
73
  More usage examples can be found [here](https://github.com/YusukeIwaki/puppeteer-ruby-example)
74
74
 
75
+ ## :whale: Running in Docker
76
+
77
+ Following packages are required.
78
+
79
+ * Google Chrome or Chromium
80
+ * In Debian-based images, `google-chrome-stable`
81
+ * In Alpine-based images, `chromium`
82
+
83
+ Also, CJK font will be required for Chinese, Japanese, Korean sites.
84
+
85
+ ### References
86
+
87
+ * Puppeteer official README: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker
88
+ * puppeteer-ruby example: https://github.com/YusukeIwaki/puppeteer-ruby-example/tree/master/docker_chromium
89
+
75
90
  ## :bulb: Collaboration with Selenium or Capybara
76
91
 
77
92
  It is really remarkable that we can use puppeteer functions in existing Selenium or Capybara codes, with a few configuration in advance.
@@ -6,7 +6,9 @@ require 'puppeteer/env'
6
6
 
7
7
  # Custom data types.
8
8
  require 'puppeteer/device'
9
+ require 'puppeteer/events'
9
10
  require 'puppeteer/errors'
11
+ require 'puppeteer/geolocation'
10
12
  require 'puppeteer/viewport'
11
13
 
12
14
  # Modules
@@ -27,6 +29,7 @@ require 'puppeteer/devices'
27
29
  require 'puppeteer/dialog'
28
30
  require 'puppeteer/dom_world'
29
31
  require 'puppeteer/emulation_manager'
32
+ require 'puppeteer/exception_details'
30
33
  require 'puppeteer/execution_context'
31
34
  require 'puppeteer/file_chooser'
32
35
  require 'puppeteer/frame'
@@ -46,37 +46,30 @@ 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.Connection.Disconnected' do
50
- emit_event 'Events.Browser.Disconnected'
49
+ @connection.on_event(ConnectionEmittedEvents::Disconnected) do
50
+ emit_event(BrowserEmittedEvents::Disconnected)
51
51
  end
52
- @connection.on_event 'Target.targetCreated', &method(:handle_target_created)
53
- @connection.on_event 'Target.targetDestroyed', &method(:handle_target_destroyed)
54
- @connection.on_event 'Target.targetInfoChanged', &method(:handle_target_info_changed)
52
+ @connection.on_event('Target.targetCreated', &method(:handle_target_created))
53
+ @connection.on_event('Target.targetDestroyed', &method(:handle_target_destroyed))
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
57
  # @param event_name [Symbol] either of :disconnected, :targetcreated, :targetchanged, :targetdestroyed
65
58
  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(", ")}")
59
+ unless BrowserEmittedEvents.values.include?(event_name.to_s)
60
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{BrowserEmittedEvents.values.to_a.join(", ")}")
68
61
  end
69
62
 
70
- add_event_listener(EVENT_MAPPINGS[event_name.to_sym], &block)
63
+ super(event_name.to_s, &block)
71
64
  end
72
65
 
73
66
  # @param event_name [Symbol]
74
67
  def once(event_name, &block)
75
- unless EVENT_MAPPINGS.has_key?(event_name.to_sym)
76
- raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{EVENT_MAPPINGS.keys.join(", ")}")
68
+ unless BrowserEmittedEvents.values.include?(event_name.to_s)
69
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{BrowserEmittedEvents.values.to_a.join(", ")}")
77
70
  end
78
71
 
79
- observe_first(EVENT_MAPPINGS[event_name.to_sym], &block)
72
+ super(event_name.to_s, &block)
80
73
  end
81
74
 
82
75
  # @return [Puppeteer::BrowserRunner::BrowserProcess]
@@ -137,8 +130,8 @@ class Puppeteer::Browser
137
130
  # assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
138
131
  @targets[target_info.target_id] = target
139
132
  if await target.initialized_promise
140
- emit_event 'Events.Browser.TargetCreated', target
141
- context.emit_event 'Events.BrowserContext.TargetCreated', target
133
+ emit_event(BrowserEmittedEvents::TargetCreated, target)
134
+ context.emit_event(BrowserContextEmittedEvents::TargetCreated, target)
142
135
  end
143
136
  end
144
137
 
@@ -150,8 +143,8 @@ class Puppeteer::Browser
150
143
  @targets.delete(target_id)
151
144
  target.closed_callback
152
145
  if await target.initialized_promise
153
- emit_event 'Events.Browser.TargetDestroyed', target
154
- target.browser_context.emit_event 'Events.BrowserContext.TargetDestroyed', target
146
+ emit_event(BrowserEmittedEvents::TargetDestroyed, target)
147
+ target.browser_context.emit_event(BrowserContextEmittedEvents::TargetDestroyed, target)
155
148
  end
156
149
  end
157
150
 
@@ -169,8 +162,8 @@ class Puppeteer::Browser
169
162
  was_initialized = target.initialized?
170
163
  target.handle_target_info_changed(target_info)
171
164
  if was_initialized && previous_url != target.url
172
- emit_event 'Events.Browser.TargetChanged', target
173
- target.browser_context.emit_event 'Events.BrowserContext.TargetChanged', target
165
+ emit_event(BrowserEmittedEvents::TargetChanged, target)
166
+ target.browser_context.emit_event(BrowserContextEmittedEvents::TargetChanged, target)
174
167
  end
175
168
  end
176
169
 
@@ -222,12 +215,12 @@ class Puppeteer::Browser
222
215
 
223
216
  event_listening_ids = []
224
217
  target_promise = resolvable_future
225
- event_listening_ids << add_event_listener('Events.Browser.TargetCreated') do |target|
218
+ event_listening_ids << add_event_listener(BrowserEmittedEvents::TargetCreated) do |target|
226
219
  if predicate.call(target)
227
220
  target_promise.fulfill(target)
228
221
  end
229
222
  end
230
- event_listening_ids << add_event_listener('Events.Browser.TargetChanged') do |target|
223
+ event_listening_ids << add_event_listener(BrowserEmittedEvents::TargetChanged) do |target|
231
224
  if predicate.call(target)
232
225
  target_promise.fulfill(target)
233
226
  end
@@ -11,29 +11,22 @@ class Puppeteer::BrowserContext
11
11
  @id = context_id
12
12
  end
13
13
 
14
- EVENT_MAPPINGS = {
15
- disconnected: 'Events.BrowserContext.Disconnected',
16
- targetcreated: 'Events.BrowserContext.TargetCreated',
17
- targetchanged: 'Events.BrowserContext.TargetChanged',
18
- targetdestroyed: 'Events.BrowserContext.TargetDestroyed',
19
- }
20
-
21
14
  # @param event_name [Symbol] either of :disconnected, :targetcreated, :targetchanged, :targetdestroyed
22
15
  def on(event_name, &block)
23
- unless EVENT_MAPPINGS.has_key?(event_name.to_sym)
24
- raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{EVENT_MAPPINGS.keys.join(", ")}")
16
+ unless BrowserContextEmittedEvents.values.include?(event_name.to_s)
17
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{BrowserContextEmittedEvents.values.to_a.join(", ")}")
25
18
  end
26
19
 
27
- add_event_listener(EVENT_MAPPINGS[event_name.to_sym], &block)
20
+ super(event_name.to_s, &block)
28
21
  end
29
22
 
30
23
  # @param event_name [Symbol]
31
24
  def once(event_name, &block)
32
- unless EVENT_MAPPINGS.has_key?(event_name.to_sym)
33
- raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{EVENT_MAPPINGS.keys.join(", ")}")
25
+ unless BrowserContextEmittedEvents.values.include?(event_name.to_s)
26
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{BrowserContextEmittedEvents.values.to_a.join(", ")}")
34
27
  end
35
28
 
36
- observe_first(EVENT_MAPPINGS[event_name.to_sym], &block)
29
+ super(event_name.to_s, &block)
37
30
  end
38
31
 
39
32
  # @return {!Array<!Target>} target
@@ -64,42 +57,48 @@ class Puppeteer::BrowserContext
64
57
  !!@id
65
58
  end
66
59
 
67
- # /**
68
- # * @param {string} origin
69
- # * @param {!Array<string>} permissions
70
- # */
71
- # async overridePermissions(origin, permissions) {
72
- # const webPermissionToProtocol = new Map([
73
- # ['geolocation', 'geolocation'],
74
- # ['midi', 'midi'],
75
- # ['notifications', 'notifications'],
76
- # ['push', 'push'],
77
- # ['camera', 'videoCapture'],
78
- # ['microphone', 'audioCapture'],
79
- # ['background-sync', 'backgroundSync'],
80
- # ['ambient-light-sensor', 'sensors'],
81
- # ['accelerometer', 'sensors'],
82
- # ['gyroscope', 'sensors'],
83
- # ['magnetometer', 'sensors'],
84
- # ['accessibility-events', 'accessibilityEvents'],
85
- # ['clipboard-read', 'clipboardRead'],
86
- # ['clipboard-write', 'clipboardWrite'],
87
- # ['payment-handler', 'paymentHandler'],
88
- # // chrome-specific permissions we have.
89
- # ['midi-sysex', 'midiSysex'],
90
- # ]);
91
- # permissions = permissions.map(permission => {
92
- # const protocolPermission = webPermissionToProtocol.get(permission);
93
- # if (!protocolPermission)
94
- # throw new Error('Unknown permission: ' + permission);
95
- # return protocolPermission;
96
- # });
97
- # await this._connection.send('Browser.grantPermissions', {origin, browserContextId: this._id || undefined, permissions});
98
- # }
99
-
100
- # async clearPermissionOverrides() {
101
- # await this._connection.send('Browser.resetPermissions', {browserContextId: this._id || undefined});
102
- # }
60
+ WEB_PERMISSION_TO_PROTOCOL = {
61
+ 'geolocation' => 'geolocation',
62
+ 'midi' => 'midi',
63
+ 'notifications' => 'notifications',
64
+ # TODO: push isn't a valid type?
65
+ # 'push' => 'push',
66
+ 'camera' => 'videoCapture',
67
+ 'microphone' => 'audioCapture',
68
+ 'background-sync' => 'backgroundSync',
69
+ 'ambient-light-sensor' => 'sensors',
70
+ 'accelerometer' => 'sensors',
71
+ 'gyroscope' => 'sensors',
72
+ 'magnetometer' => 'sensors',
73
+ 'accessibility-events' => 'accessibilityEvents',
74
+ 'clipboard-read' => 'clipboardReadWrite',
75
+ 'clipboard-write' => 'clipboardReadWrite',
76
+ 'payment-handler' => 'paymentHandler',
77
+ 'idle-detection' => 'idleDetection',
78
+ # chrome-specific permissions we have.
79
+ 'midi-sysex' => 'midiSysex',
80
+ }.freeze
81
+
82
+ # @param origin [String]
83
+ # @param permissions [Array<String>]
84
+ def override_permissions(origin, permissions)
85
+ protocol_permissions = permissions.map do |permission|
86
+ WEB_PERMISSION_TO_PROTOCOL[permission] or raise ArgumentError.new("Unknown permission: #{permission}")
87
+ end
88
+ @connection.send_message('Browser.grantPermissions', {
89
+ origin: origin,
90
+ browserContextId: @id,
91
+ permissions: protocol_permissions,
92
+ }.compact)
93
+ end
94
+
95
+ def clear_permission_overrides
96
+ if @id
97
+ @connection.send_message('Browser.resetPermissions', browserContextId: @id)
98
+ else
99
+ @connection.send_message('Browser.resetPermissions')
100
+ end
101
+ end
103
102
 
104
103
  # @return [Future<Puppeteer::Page>]
105
104
  def new_page
@@ -14,7 +14,6 @@ class Puppeteer::BrowserRunner
14
14
  @proc = nil
15
15
  @connection = nil
16
16
  @closed = true
17
- @listeners = []
18
17
  end
19
18
 
20
19
  attr_reader :proc, :connection
@@ -9,7 +9,7 @@ class Puppeteer::CDPSession
9
9
  # @param {string} targetType
10
10
  # @param {string} sessionId
11
11
  def initialize(connection, target_type, session_id)
12
- @callbacks = {}
12
+ @callbacks = Concurrent::Hash.new
13
13
  @connection = connection
14
14
  @target_type = target_type
15
15
  @session_id = session_id
@@ -31,10 +31,14 @@ class Puppeteer::CDPSession
31
31
  if !@connection
32
32
  raise Error.new("Protocol error (#{method}): Session closed. Most likely the #{@target_type} has been closed.")
33
33
  end
34
- id = @connection.raw_send(message: { sessionId: @session_id, method: method, params: params })
34
+
35
35
  promise = resolvable_future
36
- callback = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
37
- @callbacks[id] = callback
36
+
37
+ @connection.generate_id do |id|
38
+ @callbacks[id] = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
39
+ @connection.raw_send(id: id, message: { sessionId: @session_id, method: method, params: params })
40
+ end
41
+
38
42
  promise
39
43
  end
40
44
 
@@ -44,10 +48,10 @@ class Puppeteer::CDPSession
44
48
  if callback = @callbacks.delete(message['id'])
45
49
  callback_with_message(callback, message)
46
50
  else
47
- raise Error.new("unknown id: #{id}")
51
+ raise Error.new("unknown id: #{message['id']}")
48
52
  end
49
53
  else
50
- emit_event message['method'], message['params']
54
+ emit_event(message['method'], message['params'])
51
55
  end
52
56
  end
53
57
 
@@ -79,7 +83,7 @@ class Puppeteer::CDPSession
79
83
  end
80
84
  @callbacks.clear
81
85
  @connection = nil
82
- emit_event 'Events.CDPSession.Disconnected'
86
+ emit_event(CDPSessionEmittedEvents::Disconnected)
83
87
  end
84
88
 
85
89
  # @param event_name [String]
@@ -39,7 +39,7 @@ class Puppeteer::Connection
39
39
  def initialize(url, transport, delay = 0)
40
40
  @url = url
41
41
  @last_id = 0
42
- @callbacks = {}
42
+ @callbacks = Concurrent::Hash.new
43
43
  @delay = delay
44
44
 
45
45
  @transport = transport
@@ -52,7 +52,7 @@ class Puppeteer::Connection
52
52
  handle_close
53
53
  end
54
54
 
55
- @sessions = {}
55
+ @sessions = Concurrent::Hash.new
56
56
  @closed = false
57
57
  end
58
58
 
@@ -92,22 +92,41 @@ class Puppeteer::Connection
92
92
  end
93
93
 
94
94
  def async_send_message(method, params = {})
95
- id = raw_send(message: { method: method, params: params })
96
95
  promise = resolvable_future
97
- @callbacks[id] = MessageCallback.new(method: method, promise: promise)
96
+
97
+ generate_id do |id|
98
+ @callbacks[id] = MessageCallback.new(method: method, promise: promise)
99
+ raw_send(id: id, message: { method: method, params: params })
100
+ end
101
+
98
102
  promise
99
103
  end
100
104
 
101
- private def generate_id
102
- @last_id += 1
105
+ # package private. not intended to use externally.
106
+ #
107
+ # ```usage
108
+ # connection.generate_id do |generated_id|
109
+ # # play with generated_id
110
+ # end
111
+ # ````
112
+ #
113
+ def generate_id(&block)
114
+ block.call(@last_id += 1)
103
115
  end
104
116
 
105
- def raw_send(message:)
106
- id = generate_id
117
+ # package private. not intended to use externally.
118
+ def raw_send(id:, message:)
119
+ # In original puppeteer (JS) implementation,
120
+ # id is generated here using #generate_id and the id argument is not passed to #raw_send.
121
+ #
122
+ # However with concurrent-ruby, '#handle_message' is sometimes called
123
+ # just soon after @transport.send_text and **before returning the id.**
124
+ #
125
+ # So we have to know the message id in advance before send_text.
126
+ #
107
127
  payload = JSON.fast_generate(message.compact.merge(id: id))
108
128
  @transport.send_text(payload)
109
129
  request_debug_printer.handle_payload(payload)
110
- id
111
130
  end
112
131
 
113
132
  # Just for effective debugging :)
@@ -211,7 +230,7 @@ class Puppeteer::Connection
211
230
  end
212
231
  end
213
232
  else
214
- emit_event message['method'], message['params']
233
+ emit_event(message['method'], message['params'])
215
234
  end
216
235
  end
217
236
 
@@ -233,7 +252,7 @@ class Puppeteer::Connection
233
252
  session.handle_closed
234
253
  end
235
254
  @sessions.clear
236
- emit_event 'Events.Connection.Disconnected'
255
+ emit_event(ConnectionEmittedEvents::Disconnected)
237
256
  end
238
257
 
239
258
  def on_close(&block)