puppeteer-ruby 0.0.19 → 0.0.25

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: 6e6a29713aa4dd143bdd6c4fd1cc84df1bd7037aa920b0d660a62267f6321515
4
- data.tar.gz: 46a433d484dfabd019cc0874b8dd6c17182e374fb53d6f6905fde7c1d120d5e6
3
+ metadata.gz: 73958c6fad7b569150344ee3cc43d20ca6eeaf82898bd1b20d8c9f40772e66f3
4
+ data.tar.gz: 3d39e319ed9e21aeff3868255ae956e16c9ef63f507a8bb591ea9693084110df
5
5
  SHA512:
6
- metadata.gz: 9c5796c2331a9218c1191e1c2869aa5b02c6affdd19f5af23324ee1b257e93d3f328688b19ac14b4060f050c3649a82f6074f0a2b5d66c4bb71f34f46d51557a
7
- data.tar.gz: 70bfa15d8c788ab11dbf8cf240a3967a906a15777294b1f8be2be22a64d5f2123a3c4bf44226c35c58bed0614a9b7703d6db498ce283309aa36bb30c5587cea6
6
+ metadata.gz: e15df5c9f1114cbc599c81d541a4abcb90c6470a37ebfbeeca5c18152b4f034b55ce5af99d7f00ddc717ddf6abdf854dd7a80828deb15ec306b7c2b3e336791e
7
+ data.tar.gz: 0e51a01f8e02c3bdfea8bd0c13f781a4c101d638aacf30bc7bcb21429820326ac1cd2ae186586f49116a6791d046416ef92b59e8ec5121e2bc61097155686c38
@@ -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
@@ -47,8 +47,46 @@ Puppeteer.launch(headless: false, slow_mo: 50, args: ['--guest', '--window-size=
47
47
  end
48
48
  ```
49
49
 
50
+ ### Evaluate JavaScript
51
+
52
+ ```ruby
53
+ Puppeteer.launch do |browser|
54
+ page = browser.pages.last || browser.new_page
55
+ page.goto 'https://github.com/YusukeIwaki'
56
+
57
+ # Get the "viewport" of the page, as reported by the page.
58
+ dimensions = page.evaluate(<<~JAVASCRIPT)
59
+ () => {
60
+ return {
61
+ width: document.documentElement.clientWidth,
62
+ height: document.documentElement.clientHeight,
63
+ deviceScaleFactor: window.devicePixelRatio
64
+ };
65
+ }
66
+ JAVASCRIPT
67
+
68
+ puts "dimensions: #{dimensions}"
69
+ # => dimensions: {"width"=>800, "height"=>600, "deviceScaleFactor"=>1}
70
+ end
71
+ ```
72
+
50
73
  More usage examples can be found [here](https://github.com/YusukeIwaki/puppeteer-ruby-example)
51
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
+
52
90
  ## :bulb: Collaboration with Selenium or Capybara
53
91
 
54
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
@@ -24,8 +26,10 @@ require 'puppeteer/cdp_session'
24
26
  require 'puppeteer/connection'
25
27
  require 'puppeteer/console_message'
26
28
  require 'puppeteer/devices'
29
+ require 'puppeteer/dialog'
27
30
  require 'puppeteer/dom_world'
28
31
  require 'puppeteer/emulation_manager'
32
+ require 'puppeteer/exception_details'
29
33
  require 'puppeteer/execution_context'
30
34
  require 'puppeteer/file_chooser'
31
35
  require 'puppeteer/frame'
@@ -60,7 +64,11 @@ class Puppeteer
60
64
  is_puppeteer_core: true,
61
65
  )
62
66
 
63
- @puppeteer.send(method, *args, **kwargs, &block)
67
+ if kwargs.empty? # for Ruby < 2.7
68
+ @puppeteer.public_send(method, *args, &block)
69
+ else
70
+ @puppeteer.public_send(method, *args, **kwargs, &block)
71
+ end
64
72
  end
65
73
 
66
74
  # @param project_root [String]
@@ -126,7 +134,7 @@ class Puppeteer
126
134
  ignore_https_errors: ignore_https_errors,
127
135
  default_viewport: default_viewport,
128
136
  slow_mo: slow_mo,
129
- }.compact
137
+ }
130
138
 
131
139
  @product_name ||= product
132
140
  browser = launcher.launch(options)
@@ -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,16 +14,24 @@ 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
21
20
 
22
21
  class BrowserProcess
23
22
  def initialize(env, executable_path, args)
23
+ @spawnargs =
24
+ if args && !args.empty?
25
+ [executable_path] + args
26
+ else
27
+ [executable_path]
28
+ end
29
+
24
30
  stdin, @stdout, @stderr, @thread = Open3.popen3(env, executable_path, *args)
25
31
  stdin.close
26
32
  @pid = @thread.pid
33
+ rescue Errno::ENOENT => err
34
+ raise LaunchError.new(err.message)
27
35
  end
28
36
 
29
37
  def kill
@@ -37,7 +45,13 @@ class Puppeteer::BrowserRunner
37
45
  @thread.join
38
46
  end
39
47
 
40
- attr_reader :stdout, :stderr
48
+ attr_reader :stdout, :stderr, :spawnargs
49
+ end
50
+
51
+ class LaunchError < StandardError
52
+ def initialize(reason)
53
+ super("Failed to launch browser! #{reason}")
54
+ end
41
55
  end
42
56
 
43
57
  # @param {!(Launcher.LaunchOptions)=} options
@@ -123,12 +137,12 @@ class Puppeteer::BrowserRunner
123
137
 
124
138
  # @return {Promise}
125
139
  def kill
126
- unless @closed
127
- @proc.kill
128
- end
129
140
  if @temp_directory
130
141
  FileUtils.rm_rf(@temp_directory)
131
142
  end
143
+ unless @closed
144
+ @proc.kill
145
+ end
132
146
  end
133
147
 
134
148
 
@@ -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,6 +83,16 @@ 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)
87
+ end
88
+
89
+ # @param event_name [String]
90
+ def on(event_name, &block)
91
+ add_event_listener(event_name, &block)
92
+ end
93
+
94
+ # @param event_name [String]
95
+ def once(event_name, &block)
96
+ observe_first(event_name, &block)
83
97
  end
84
98
  end