puppeteer-ruby 0.0.22 → 0.0.23

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: 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)