puppeteer-ruby 0.0.19 → 0.0.25

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