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 +4 -4
- data/.rubocop.yml +37 -0
- data/README.md +38 -0
- data/lib/puppeteer.rb +10 -2
- data/lib/puppeteer/browser.rb +19 -26
- data/lib/puppeteer/browser_context.rb +48 -49
- data/lib/puppeteer/browser_runner.rb +19 -5
- data/lib/puppeteer/cdp_session.rb +21 -7
- data/lib/puppeteer/concurrent_ruby_utils.rb +18 -5
- data/lib/puppeteer/connection.rb +32 -13
- data/lib/puppeteer/devices.rb +998 -849
- data/lib/puppeteer/dialog.rb +34 -0
- data/lib/puppeteer/dom_world.rb +2 -2
- data/lib/puppeteer/event_callbackable.rb +4 -0
- data/lib/puppeteer/events.rb +184 -0
- data/lib/puppeteer/exception_details.rb +38 -0
- data/lib/puppeteer/frame.rb +1 -3
- data/lib/puppeteer/frame_manager.rb +20 -16
- data/lib/puppeteer/geolocation.rb +24 -0
- data/lib/puppeteer/keyboard/us_keyboard_layout.rb +2 -2
- data/lib/puppeteer/launcher.rb +0 -1
- data/lib/puppeteer/launcher/browser_options.rb +2 -1
- data/lib/puppeteer/launcher/chrome.rb +4 -8
- data/lib/puppeteer/launcher/firefox.rb +8 -15
- data/lib/puppeteer/lifecycle_watcher.rb +6 -6
- data/lib/puppeteer/network_manager.rb +6 -6
- data/lib/puppeteer/page.rb +106 -141
- data/lib/puppeteer/target.rb +2 -2
- data/lib/puppeteer/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73958c6fad7b569150344ee3cc43d20ca6eeaf82898bd1b20d8c9f40772e66f3
|
4
|
+
data.tar.gz: 3d39e319ed9e21aeff3868255ae956e16c9ef63f507a8bb591ea9693084110df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e15df5c9f1114cbc599c81d541a4abcb90c6470a37ebfbeeca5c18152b4f034b55ce5af99d7f00ddc717ddf6abdf854dd7a80828deb15ec306b7c2b3e336791e
|
7
|
+
data.tar.gz: 0e51a01f8e02c3bdfea8bd0c13f781a4c101d638aacf30bc7bcb21429820326ac1cd2ae186586f49116a6791d046416ef92b59e8ec5121e2bc61097155686c38
|
data/.rubocop.yml
CHANGED
@@ -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.
|
data/lib/puppeteer.rb
CHANGED
@@ -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
|
-
|
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
|
-
}
|
137
|
+
}
|
130
138
|
|
131
139
|
@product_name ||= product
|
132
140
|
browser = launcher.launch(options)
|
data/lib/puppeteer/browser.rb
CHANGED
@@ -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
|
50
|
-
emit_event
|
49
|
+
@connection.on_event(ConnectionEmittedEvents::Disconnected) do
|
50
|
+
emit_event(BrowserEmittedEvents::Disconnected)
|
51
51
|
end
|
52
|
-
@connection.on_event
|
53
|
-
@connection.on_event
|
54
|
-
@connection.on_event
|
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
|
67
|
-
raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{
|
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
|
-
|
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
|
76
|
-
raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{
|
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
|
-
|
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
|
141
|
-
context.emit_event
|
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
|
154
|
-
target.browser_context.emit_event
|
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
|
173
|
-
target.browser_context.emit_event
|
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(
|
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(
|
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
|
24
|
-
raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{
|
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
|
-
|
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
|
33
|
-
raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{
|
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
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
#
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
34
|
+
|
35
35
|
promise = resolvable_future
|
36
|
-
|
37
|
-
@
|
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
|
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
|
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
|