puppeteer-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +36 -0
  5. data/.travis.yml +7 -0
  6. data/Dockerfile +6 -0
  7. data/Gemfile +6 -0
  8. data/README.md +41 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +11 -0
  11. data/bin/setup +8 -0
  12. data/docker-compose.yml +15 -0
  13. data/example.rb +7 -0
  14. data/lib/puppeteer.rb +192 -0
  15. data/lib/puppeteer/async_await_behavior.rb +34 -0
  16. data/lib/puppeteer/browser.rb +240 -0
  17. data/lib/puppeteer/browser_context.rb +90 -0
  18. data/lib/puppeteer/browser_fetcher.rb +6 -0
  19. data/lib/puppeteer/browser_runner.rb +142 -0
  20. data/lib/puppeteer/cdp_session.rb +78 -0
  21. data/lib/puppeteer/concurrent_ruby_utils.rb +37 -0
  22. data/lib/puppeteer/connection.rb +254 -0
  23. data/lib/puppeteer/console_message.rb +24 -0
  24. data/lib/puppeteer/debug_print.rb +20 -0
  25. data/lib/puppeteer/device.rb +12 -0
  26. data/lib/puppeteer/devices.rb +885 -0
  27. data/lib/puppeteer/dom_world.rb +447 -0
  28. data/lib/puppeteer/element_handle.rb +433 -0
  29. data/lib/puppeteer/emulation_manager.rb +46 -0
  30. data/lib/puppeteer/errors.rb +4 -0
  31. data/lib/puppeteer/event_callbackable.rb +88 -0
  32. data/lib/puppeteer/execution_context.rb +230 -0
  33. data/lib/puppeteer/frame.rb +278 -0
  34. data/lib/puppeteer/frame_manager.rb +380 -0
  35. data/lib/puppeteer/if_present.rb +18 -0
  36. data/lib/puppeteer/js_handle.rb +142 -0
  37. data/lib/puppeteer/keyboard.rb +183 -0
  38. data/lib/puppeteer/keyboard/key_description.rb +19 -0
  39. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +283 -0
  40. data/lib/puppeteer/launcher.rb +26 -0
  41. data/lib/puppeteer/launcher/base.rb +48 -0
  42. data/lib/puppeteer/launcher/browser_options.rb +41 -0
  43. data/lib/puppeteer/launcher/chrome.rb +165 -0
  44. data/lib/puppeteer/launcher/chrome_arg_options.rb +49 -0
  45. data/lib/puppeteer/launcher/launch_options.rb +68 -0
  46. data/lib/puppeteer/lifecycle_watcher.rb +168 -0
  47. data/lib/puppeteer/mouse.rb +120 -0
  48. data/lib/puppeteer/network_manager.rb +122 -0
  49. data/lib/puppeteer/page.rb +1001 -0
  50. data/lib/puppeteer/page/screenshot_options.rb +78 -0
  51. data/lib/puppeteer/remote_object.rb +124 -0
  52. data/lib/puppeteer/target.rb +150 -0
  53. data/lib/puppeteer/timeout_settings.rb +15 -0
  54. data/lib/puppeteer/touch_screen.rb +43 -0
  55. data/lib/puppeteer/version.rb +3 -0
  56. data/lib/puppeteer/viewport.rb +36 -0
  57. data/lib/puppeteer/wait_task.rb +6 -0
  58. data/lib/puppeteer/web_socket.rb +117 -0
  59. data/lib/puppeteer/web_socket_transport.rb +49 -0
  60. data/puppeteer-ruby.gemspec +29 -0
  61. metadata +213 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4f42c72b65d27578da2dbdbbc4dac1544bb0a0038ba3c35ffd8f298dafc47f14
4
+ data.tar.gz: c1fe854ee2566bb9c8993e42cf71b881ff9c87a709150bb7cddd09a6d952901d
5
+ SHA512:
6
+ metadata.gz: ef51d01afe77f2c1bf31ed70a7bfe3a629377b32b69ceb265413876802639ef5c44de08856b0bfaa69bbddf7bf41a6aaef01bd7e4a2c4a516ec21b645b7caede
7
+ data.tar.gz: 7a876a89710c9fd956377b01ce41d463a6fd547805991f8778bd89086182a4800459bb6cf5d6009ea8a363629993f4bdd333263475c1e24cfb3a99f9b886e53e
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ /.vscode
11
+ /Gemfile.lock
12
+ /vendor/bundle
13
+
14
+ # rspec failure tracking
15
+ .rspec_status
16
+
17
+ # RubyMine
18
+ /.idea/
19
+ /.rakeTasks
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ Layout/LineLength:
2
+ Max: 120
3
+
4
+ Style/AccessModifierDeclarations:
5
+ EnforcedStyle: inline
6
+
7
+ Style/ClassAndModuleChildren:
8
+ EnforcedStyle: compact
9
+
10
+ Style/FrozenStringLiteralComment:
11
+ Enabled: false
12
+
13
+ Style/HashEachMethods:
14
+ Enabled: true
15
+
16
+ Style/HashTransformKeys:
17
+ Enabled: true
18
+
19
+ Style/HashTransformValues:
20
+ Enabled: true
21
+
22
+ Style/RaiseArgs:
23
+ Enabled: true
24
+ EnforcedStyle: compact
25
+
26
+ Style/TrailingCommaInArguments:
27
+ Enabled: true
28
+ EnforcedStyleForMultiline: comma
29
+
30
+ Style/TrailingCommaInArrayLiteral:
31
+ Enabled: true
32
+ EnforcedStyleForMultiline: comma
33
+
34
+ Style/TrailingCommaInHashLiteral:
35
+ Enabled: true
36
+ EnforcedStyleForMultiline: comma
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.3
7
+ before_install: gem install bundler -v 1.17.2
data/Dockerfile ADDED
@@ -0,0 +1,6 @@
1
+ FROM ruby:2.6.5
2
+
3
+ RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
4
+ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
5
+ && apt-get update && apt-get install -y google-chrome-stable --no-install-recommends \
6
+ && rm -rf /var/lib/apt/lists/*
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in puppeteer-ruby.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Puppeteer in Ruby [UNDER HEAVY DEVELOPMENT]
2
+
3
+ A Ruby port of [puppeteer](https://pptr.dev/).
4
+
5
+ REMARK: This Gem is NOT production-ready!!
6
+
7
+ ## Getting Started
8
+
9
+ Simple usage:
10
+
11
+ ```ruby
12
+ require 'puppeteer'
13
+
14
+ Puppeteer.launch(headless: false, slow_mo: 50, args: ['--guest', '--window-size=1280,800']) do |browser|
15
+ page = browser.pages.first || browser.new_page
16
+ page.viewport = Puppeteer::Viewport.new(width: 1280, height: 800)
17
+ page.goto("https://github.com/", wait_until: 'domcontentloaded')
18
+
19
+ form = page.S("form.js-site-search-form")
20
+ searchInput = form.S("input.header-search-input")
21
+ searchInput.type_text("puppeteer")
22
+ await_all(
23
+ page.async_wait_for_navigation,
24
+ searchInput.async_press("Enter"),
25
+ )
26
+
27
+ list = page.S("ul.repo-list")
28
+ items = list.SS("div.f4")
29
+ items.each do |item|
30
+ title = item.Seval("a", "a => a.innerText")
31
+ puts("==> #{title}")
32
+ end
33
+ end
34
+ ```
35
+
36
+ ![puppeteer-ruby](https://user-images.githubusercontent.com/11763113/78505735-6e7f3000-77b0-11ea-9c82-9016828dd2a9.gif)
37
+
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/YusukeIwaki/puppeteer-ruby.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'puppeteer'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ version: "3"
2
+ services:
3
+ dev:
4
+ image: ruby-with-chrome:2.6
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ volumes:
9
+ - .:/usr/src/app
10
+ - bundler-cache:/usr/local/bundle
11
+ working_dir: /usr/src/app
12
+
13
+ volumes:
14
+ bundler-cache:
15
+ driver: local
data/example.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'puppeteer';
2
+
3
+ Puppeteer.launch(headless: false) do |browser|
4
+ page = browser.pages.first || browser.new_page
5
+ page.goto("https://github.com/YusukeIwaki")
6
+ page.screenshot(path: "YusukeIwaki.png")
7
+ end
data/lib/puppeteer.rb ADDED
@@ -0,0 +1,192 @@
1
+ require 'concurrent'
2
+
3
+ class Puppeteer ; end
4
+
5
+ # Custom data types.
6
+ require 'puppeteer/device'
7
+ require 'puppeteer/errors'
8
+ require 'puppeteer/viewport'
9
+
10
+ # Modules
11
+ require 'puppeteer/async_await_behavior'
12
+ require 'puppeteer/concurrent_ruby_utils'
13
+ require 'puppeteer/debug_print'
14
+ require 'puppeteer/event_callbackable'
15
+ require 'puppeteer/if_present'
16
+
17
+ # Classes & values.
18
+ require 'puppeteer/browser'
19
+ require 'puppeteer/browser_context'
20
+ require 'puppeteer/browser_runner'
21
+ require 'puppeteer/cdp_session'
22
+ require 'puppeteer/connection'
23
+ require 'puppeteer/console_message'
24
+ require 'puppeteer/devices'
25
+ require 'puppeteer/dom_world'
26
+ require 'puppeteer/emulation_manager'
27
+ require 'puppeteer/execution_context'
28
+ require 'puppeteer/frame'
29
+ require 'puppeteer/frame_manager'
30
+ require 'puppeteer/js_handle'
31
+ require 'puppeteer/keyboard'
32
+ require 'puppeteer/launcher'
33
+ require 'puppeteer/lifecycle_watcher'
34
+ require 'puppeteer/mouse'
35
+ require 'puppeteer/network_manager'
36
+ require 'puppeteer/page'
37
+ require 'puppeteer/remote_object'
38
+ require 'puppeteer/target'
39
+ require 'puppeteer/timeout_settings'
40
+ require 'puppeteer/touch_screen'
41
+ require 'puppeteer/version'
42
+ require 'puppeteer/wait_task'
43
+ require 'puppeteer/web_socket'
44
+ require 'puppeteer/web_socket_transport'
45
+
46
+ # subclasses
47
+ require 'puppeteer/element_handle'
48
+
49
+ # ref: https://github.com/puppeteer/puppeteer/blob/master/lib/Puppeteer.js
50
+ class Puppeteer
51
+ class << self
52
+ def method_missing(method, *args, **kwargs, &block)
53
+ instance.send(method, *args, **kwargs, &block)
54
+ end
55
+
56
+ def instance
57
+ @instance ||= Puppeteer.new(
58
+ project_root: __dir__,
59
+ preferred_revision: "706915",
60
+ is_puppeteer_core: true)
61
+ end
62
+ end
63
+
64
+ # @param {string} projectRoot
65
+ # @param {string} preferredRevision
66
+ # @param {boolean} isPuppeteerCore
67
+ def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
68
+ @project_root = project_root
69
+ @preferred_revision = preferred_revision
70
+ @is_puppeteer_core = is_puppeteer_core
71
+ end
72
+
73
+ # @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions & {product?: string, extraPrefsFirefox?: !object})=} options
74
+ # @return {!Promise<!Puppeteer.Browser>}
75
+ def launch(
76
+ product: nil,
77
+ executable_path: nil,
78
+ ignore_default_args: nil,
79
+ handle_SIGINT: nil,
80
+ handle_SIGTERM: nil,
81
+ handle_SIGHUP: nil,
82
+ timeout: nil,
83
+ dumpio: nil,
84
+ env: nil,
85
+ pipe: nil,
86
+ args: nil,
87
+ user_data_dir: nil,
88
+ devtools: nil,
89
+ headless: nil,
90
+ ignore_https_errors: nil,
91
+ default_viewport: nil,
92
+ slow_mo: nil
93
+ )
94
+ options = {
95
+ executable_path: executable_path,
96
+ ignore_default_args: ignore_default_args,
97
+ handle_SIGINT: handle_SIGINT,
98
+ handle_SIGTERM: handle_SIGTERM,
99
+ handle_SIGHUP: handle_SIGHUP,
100
+ timeout: timeout,
101
+ dumpio: dumpio,
102
+ env: env,
103
+ pipe: pipe,
104
+ args: args,
105
+ user_data_dir: user_data_dir,
106
+ devtools: devtools,
107
+ headless: headless,
108
+ ignore_https_errors: ignore_https_errors,
109
+ default_viewport: default_viewport,
110
+ slow_mo: slow_mo,
111
+ }.compact
112
+
113
+ @product_name ||= product
114
+ browser = launcher.launch(options)
115
+ if block_given?
116
+ begin
117
+ yield(browser)
118
+ ensure
119
+ browser.close
120
+ end
121
+ else
122
+ browser
123
+ end
124
+ end
125
+
126
+ # @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
127
+ # @return {!Promise<!Puppeteer.Browser>}
128
+ def connect(
129
+ browser_ws_endpoint: nil,
130
+ browser_url: nil,
131
+ transport: nil,
132
+ ignore_https_errors: nil,
133
+ default_viewport: nil,
134
+ slow_mo: nil
135
+ )
136
+ options = {
137
+ browser_ws_endpoint: browser_ws_endpoint,
138
+ browser_url: browser_url,
139
+ transport: transport,
140
+ ignore_https_errors: ignore_https_errors,
141
+ default_viewport: default_viewport,
142
+ slow_mo: slow_mo,
143
+ }.compact
144
+ launcher.connect(options)
145
+ end
146
+
147
+ # @return {string}
148
+ def executable_path
149
+ launcher.executable_path
150
+ end
151
+
152
+ private def launcher
153
+ @launcher ||= Puppeteer::Launcher.new(
154
+ project_root: @project_root,
155
+ preferred_revision: @preferred_revision,
156
+ is_puppeteer_core: @is_puppeteer_core,
157
+ product: @product_name)
158
+ end
159
+
160
+ # @return {string}
161
+ def product
162
+ launcher.product
163
+ end
164
+
165
+ # @return {Puppeteer::Devices}
166
+ def devices
167
+ Puppeteer::Devices
168
+ end
169
+
170
+ # # @return {Object}
171
+ # def errors
172
+ # # ???
173
+ # end
174
+
175
+ # @param {!Launcher.ChromeArgOptions=} options
176
+ # @return {!Array<string>}
177
+ def default_args(args: nil, user_data_dir: nil, devtools: nil, headless: nil)
178
+ options = {
179
+ args: args,
180
+ user_data_dir: user_data_dir,
181
+ devtools: devtools,
182
+ headless: headless,
183
+ }.compact
184
+ launcher.default_args(options)
185
+ end
186
+
187
+ # @param {!BrowserFetcher.Options=} options
188
+ # @return {!BrowserFetcher}
189
+ def createBrowserFetcher(options = {})
190
+ BrowserFetcher.new(@project_root, options)
191
+ end
192
+ end
@@ -0,0 +1,34 @@
1
+ module Puppeteer::AsyncAwaitBehavior
2
+ refine Class do
3
+ # wrap with Concurrent::Promises.future
4
+ def async(method_name)
5
+ begin
6
+ original_method = instance_method(method_name)
7
+
8
+ unless method_name.to_s.start_with?("async_")
9
+ puts "async method should start with 'async_': #{self.name}##{method_name}"
10
+ end
11
+
12
+ define_method(method_name) do |*args|
13
+ Concurrent::Promises.future {
14
+ original_method.bind(self).call(*args)
15
+ }
16
+ end
17
+ rescue NameError
18
+ if respond_to?(method_name)
19
+ original_method = singleton_method(method_name)
20
+
21
+ unless method_name.to_s.start_with?("async_")
22
+ puts "async method should start with 'async_': #{method_name}"
23
+ end
24
+
25
+ define_singleton_method(method_name) do |*args|
26
+ Concurrent::Promises.future {
27
+ original_method.call(*args)
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,240 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ class Puppeteer::Browser
5
+ include Puppeteer::DebugPrint
6
+ include Puppeteer::EventCallbackable
7
+ using Puppeteer::AsyncAwaitBehavior
8
+
9
+ # @param {!Puppeteer.Connection} connection
10
+ # @param {!Array<string>} contextIds
11
+ # @param {boolean} ignoreHTTPSErrors
12
+ # @param {?Puppeteer.Viewport} defaultViewport
13
+ # @param process [Puppeteer::BrowserRunner::BrowserProcess|NilClass]
14
+ # @param {function()=} closeCallback
15
+ def self.create(connection:, context_ids:, ignore_https_errors:, default_viewport:, process:, close_callback:)
16
+ browser = Puppeteer::Browser.new(
17
+ connection: connection,
18
+ context_ids: context_ids,
19
+ ignore_https_errors: ignore_https_errors,
20
+ default_viewport: default_viewport,
21
+ process: process,
22
+ close_callback: close_callback,
23
+ )
24
+ connection.send_message('Target.setDiscoverTargets', discover: true)
25
+ browser
26
+ end
27
+
28
+ # @param {!Puppeteer.Connection} connection
29
+ # @param {!Array<string>} contextIds
30
+ # @param {boolean} ignoreHTTPSErrors
31
+ # @param {?Puppeteer.Viewport} defaultViewport
32
+ # @param {?Puppeteer.ChildProcess} process
33
+ # @param {(function():Promise)=} closeCallback
34
+ def initialize(connection:, context_ids:, ignore_https_errors:, default_viewport:, process:, close_callback:)
35
+ @ignore_https_errors = ignore_https_errors
36
+ @default_viewport = default_viewport
37
+ @process = process
38
+ # @screenshot_task_queue = TaskQueue.new
39
+ @connection = connection
40
+ @close_callback = close_callback
41
+
42
+ @default_context = Puppeteer::BrowserContext.new(@connection, self, nil)
43
+ @contexts = {}
44
+ context_ids.each do |context_id|
45
+ @contexts[context_id] = Puppeteer::BrowserContext.new(@connection, self. context_id)
46
+ end
47
+ @targets = {}
48
+ @connection.on_event 'Events.CDPSession.Disconnected' do
49
+ emit_event 'Events.Browser.Disconnected'
50
+ end
51
+ @connection.on_event 'Target.targetCreated', &method(:handle_target_created)
52
+ @connection.on_event 'Target.targetDestroyed', &method(:handle_target_destroyed)
53
+ @connection.on_event 'Target.targetInfoChanged', &method(:handle_target_info_changed)
54
+ end
55
+
56
+ # @return [Puppeteer::BrowserRunner::BrowserProcess]
57
+ def process
58
+ @process
59
+ end
60
+
61
+ # @return [Puppeteer::BrowserContext]
62
+ def create_incognito_browser_context
63
+ result = @connection.send_message('Target.createBrowserContext')
64
+ browser_context_id = result['browserContextId']
65
+ @contexts[browser_context_id] = Puppeteer::BrowserContext.new(@connection, self, browser_context_id)
66
+ end
67
+
68
+ def browser_contexts
69
+ [@default_context].concat(@contexts.values)
70
+ end
71
+
72
+ # @return [Puppeteer::BrowserContext]
73
+ def default_browser_context
74
+ @default_context
75
+ end
76
+
77
+ # @param context_id [String]
78
+ def dispose_context(context_id)
79
+ @connection.send_message('Target.disposeBrowserContext', browserContextId: context_id)
80
+ @contexts.remove(context_id)
81
+ end
82
+
83
+ # @param {!Protocol.Target.targetCreatedPayload} event
84
+ def handle_target_created(event)
85
+ target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
86
+ browser_context_id = target_info.browser_context_id
87
+ context =
88
+ if browser_context_id && @contexts.has_key?(browser_context_id)
89
+ @contexts[browser_context_id]
90
+ else
91
+ @default_context
92
+ end
93
+
94
+ target = Puppeteer::Target.new(
95
+ target_info: target_info,
96
+ browser_context: context,
97
+ session_factory: ->{ @connection.create_session(target_info) },
98
+ ignore_https_errors: @ignore_https_errors,
99
+ default_viewport: @default_viewport,
100
+ screenshot_task_queue: @screenshot_task_queue,
101
+ )
102
+ # assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
103
+ @targets[target_info.target_id] = target
104
+
105
+ target.on_initialize_succeeded do
106
+ emit_event 'Events.Browser.TargetCreated', target
107
+ context.emit_event 'Events.BrowserContext.TargetCreated', target
108
+ end
109
+ end
110
+
111
+
112
+ # @param {{targetId: string}} event
113
+ def handle_target_destroyed(event)
114
+ target_id = event['targetId']
115
+ target = @targets[target_id]
116
+ target.handle_initialized(false)
117
+ @targets.delete(target_id)
118
+ target.handle_closed
119
+ target.on_initialize_succeeded do
120
+ emit_event 'Events.Browser.TargetDestroyed', target
121
+ target.browser_context.emit_event 'Events.BrowserContext.TargetDestroyed', target
122
+ end
123
+ end
124
+
125
+ # @param {!Protocol.Target.targetInfoChangedPayload} event
126
+ def handle_target_info_changed(event)
127
+ target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
128
+ target = @targets[target_info.target_id]
129
+ if !target
130
+ throw StandardError.new('target should exist before targetInfoChanged')
131
+ end
132
+ previous_url = target.url
133
+ was_initialized = target.initialized?
134
+ target.handle_target_info_changed(target_info)
135
+ if was_initialized && previous_url != target.url
136
+ emit_event 'Events.Browser.TargetChanged', target
137
+ target.browser_context.emit_event 'Events.BrowserContext.TargetChanged', target
138
+ end
139
+ end
140
+
141
+ # @return [String]
142
+ def websocket_endpoint
143
+ @connection.url
144
+ end
145
+
146
+ def new_page
147
+ @default_context.new_page
148
+ end
149
+
150
+ # @param {?string} contextId
151
+ # @return {!Promise<!Puppeteer.Page>}
152
+ def create_page_in_context(context_id)
153
+ create_target_params = { url: 'about:blank' }
154
+ if context_id
155
+ create_target_params[:browserContextId] = context_id
156
+ end
157
+ result = @connection.send_message('Target.createTarget', **create_target_params)
158
+ target_id = result['targetId']
159
+ target = @targets[target_id]
160
+ await target.initialized_promise
161
+ await target.page;
162
+ end
163
+
164
+ # @return {!Array<!Target>}
165
+ def targets
166
+ @targets.values.select{ |target| target.initialized? }
167
+ end
168
+
169
+
170
+ # @return {!Target}
171
+ def target
172
+ targets.first{ |target| target.type == 'browser' }
173
+ end
174
+
175
+ # @param {function(!Target):boolean} predicate
176
+ # @param {{timeout?: number}=} options
177
+ # @return {!Promise<!Target>}
178
+ def wait_for_target(predicate:, timeout: nil)
179
+ timeout_in_sec = (timeout || 30000).to_i / 1000.0
180
+ existing_target = targets.first{ |target| predicate.call(target) }
181
+ return existing_target if existing_target
182
+
183
+ event_listening_ids = []
184
+ target_promise = resolvable_future
185
+ event_listening_ids << add_event_listener('Events.Browser.TargetCreated') do |target|
186
+ if predicate.call(target)
187
+ target_promise.fulfill(target)
188
+ end
189
+ end
190
+ event_listening_ids << add_event_listener('Events.Browser.TargetChanged') do |target|
191
+ if predicate.call(target)
192
+ target_promise.fulfill(target)
193
+ end
194
+ end
195
+
196
+ begin
197
+ if timeout_in_sec > 0
198
+ Timeout.timeout(timeout_in_sec) do
199
+ target_promise.value!
200
+ end
201
+ else
202
+ target_promise.value!
203
+ end
204
+ ensure
205
+ remove_event_listener(*event_listening_ids)
206
+ end
207
+ end
208
+
209
+ # @return {!Promise<!Array<!Puppeteer.Page>>}
210
+ def pages
211
+ browser_contexts.flat_map(&:pages)
212
+ end
213
+
214
+ # @return [String]
215
+ def version
216
+ get_version.product
217
+ end
218
+
219
+ # @return [String]
220
+ def user_agent
221
+ get_version.user_agent
222
+ end
223
+
224
+ def close
225
+ @close_callback.call
226
+ disconnect
227
+ end
228
+
229
+ def disconnect
230
+ @connection.dispose
231
+ end
232
+
233
+ def connected?
234
+ !@connection.closed?
235
+ end
236
+
237
+ private def get_version
238
+ @connection.send_message('Browser.getVersion')
239
+ end
240
+ end