puppeteer-ruby 0.0.2

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