puppeteer-bidi 0.0.2 → 0.0.3.beta1

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: 7242d594b90af9ed2739ef56e234d3d26805cf19d688212e9b0b68ad823a96c2
4
- data.tar.gz: 6bca1fe1f82165851376466367cee72ec59f10fabb6b2970a70bcffde5ef42b6
3
+ metadata.gz: 56fae0e1e155b3fe5bcce854ac7ff615c86af3815b0b9b186bf97fa905421a00
4
+ data.tar.gz: 3b13efc226d2a7d84da91056679707ff3d0b3a5d511f2211c9cfc4da1c0fef23
5
5
  SHA512:
6
- metadata.gz: 1c7f73fa6f5351d1e1e12a87388e56c05faa11286bf724e400ec97e19092a81a6e22c6718970c028305717afdaf560ddd16dbfa6f499b5dbc28313b8b55d3347
7
- data.tar.gz: ad2cc2f58a6da1d6bff87bde5eab283536f46aa160bbc3ac197c6574f9fd5f35950ca47331c9363feea4a93eaa8af76322f38b123e31d9212206d01e01d2d9d4
6
+ metadata.gz: abd414f479202d7f642c6f3b4b0ce3db308a18c5116bd4545483b88cf03193cdfb49e41931e596b3cb99c7699ac8f6773ba4ffff7bf160dd8f0a165745f6d0cc
7
+ data.tar.gz: 62c840fafb7844a586286eb5776b58763e2d7d3547194233e74e75d997aeb239d875fa263b20dbdfe0a326bc2608748c5c4cc479148299096a6b11f43d116cca
@@ -0,0 +1,111 @@
1
+ # ReactorRunner - Using Browser Outside Sync Blocks
2
+
3
+ ## Problem
4
+
5
+ The socketry/async library requires all async operations to run inside a `Sync do ... end` block. However, some use cases cannot wrap their entire code in a Sync block:
6
+
7
+ ```ruby
8
+ # This pattern doesn't work with plain async:
9
+ browser = Puppeteer::Bidi.launch_browser_instance(headless: true)
10
+ at_exit { browser.close } # Called outside any Sync block!
11
+
12
+ Sync do
13
+ page = browser.new_page
14
+ page.goto("https://example.com")
15
+ end
16
+ ```
17
+
18
+ The `at_exit` hook runs after the Sync block has finished, so `browser.close` would fail.
19
+
20
+ ## Solution: ReactorRunner
21
+
22
+ `ReactorRunner` creates a dedicated Async reactor in a background thread and provides a way to execute code within that reactor from any thread.
23
+
24
+ ### How It Works
25
+
26
+ 1. **Background Thread with Reactor**: ReactorRunner spawns a new thread that runs `Sync do ... end` with an `Async::Queue` for receiving jobs
27
+ 2. **Proxy Pattern**: Returns a `Proxy` object that wraps the real Browser and forwards all method calls through the ReactorRunner
28
+ 3. **Automatic Detection**: `launch_browser_instance` and `connect_to_browser_instance` check `Async::Task.current` to decide whether to use ReactorRunner
29
+
30
+ ### Architecture
31
+
32
+ ```
33
+ Main Thread Background Thread (ReactorRunner)
34
+ │ │
35
+ │ launch_browser_instance() │
36
+ │ ─────────────────────────────────>│ Sync do
37
+ │ │ Browser.launch()
38
+ │ <─────────────────────────────────│ (browser created)
39
+ │ returns Proxy │
40
+ │ │
41
+ │ proxy.new_page() │
42
+ │ ─────────────────────────────────>│ browser.new_page()
43
+ │ <─────────────────────────────────│ (returns page)
44
+ │ │
45
+ │ at_exit { proxy.close } │
46
+ │ ─────────────────────────────────>│ browser.close()
47
+ │ │ end
48
+ │ │
49
+ ```
50
+
51
+ ### Key Components
52
+
53
+ #### ReactorRunner
54
+
55
+ - Creates background thread with `Sync` reactor
56
+ - Uses `Async::Queue` to receive jobs from other threads
57
+ - `sync(&block)` method executes block in reactor and returns result
58
+ - Handles proper cleanup when closed
59
+
60
+ #### ReactorRunner::Proxy
61
+
62
+ - Extends `SimpleDelegator` for transparent method forwarding
63
+ - Wraps/unwraps return values (e.g., Page becomes Proxy too)
64
+ - `owns_runner: true` means closing browser also closes the ReactorRunner
65
+ - Handles edge cases like calling `close` after runner is already closed
66
+
67
+ ### Usage Patterns
68
+
69
+ #### Pattern 1: Block-based (Recommended)
70
+
71
+ ```ruby
72
+ Puppeteer::Bidi.launch do |browser|
73
+ page = browser.new_page
74
+ # ... use browser
75
+ end # automatically closed
76
+ ```
77
+
78
+ #### Pattern 2: Instance with at_exit
79
+
80
+ ```ruby
81
+ browser = Puppeteer::Bidi.launch_browser_instance(headless: true)
82
+ at_exit { browser.close }
83
+
84
+ Sync do
85
+ page = browser.new_page
86
+ page.goto("https://example.com")
87
+ end
88
+ ```
89
+
90
+ #### Pattern 3: Inside existing Async context
91
+
92
+ ```ruby
93
+ Sync do
94
+ # No ReactorRunner used - browser is returned directly
95
+ browser = Puppeteer::Bidi.launch_browser_instance(headless: true)
96
+ page = browser.new_page
97
+ # ...
98
+ browser.close
99
+ end
100
+ ```
101
+
102
+ ### Implementation Notes
103
+
104
+ 1. **Thread Safety**: `Async::Queue` handles cross-thread communication safely
105
+ 2. **Proxyable Check**: Only `Puppeteer::Bidi::*` objects (excluding Core layer) are wrapped in Proxy
106
+ 3. **Error Handling**: Errors in reactor are propagated back to calling thread via `Async::Promise`
107
+ 4. **Type Annotations**: Return type is `Browser` (Proxy is an internal detail)
108
+
109
+ ### Reference
110
+
111
+ This pattern is inspired by [async-webdriver](https://github.com/socketry/async-webdriver) by Samuel Williams (author of socketry/async).
data/CLAUDE.md CHANGED
@@ -186,6 +186,7 @@ See the [CLAUDE/](CLAUDE/) directory for detailed implementation guides:
186
186
 
187
187
  - **[Two-Layer Architecture](CLAUDE/two_layer_architecture.md)** - Core vs Upper layer, async patterns
188
188
  - **[Async Programming](CLAUDE/async_programming.md)** - Fiber-based concurrency with socketry/async
189
+ - **[ReactorRunner](CLAUDE/reactor_runner.md)** - Using browser outside Sync blocks (at_exit, etc.)
189
190
  - **[Porting Puppeteer](CLAUDE/porting_puppeteer.md)** - Best practices for implementing features
190
191
  - **[Core Layer Gotchas](CLAUDE/core_layer_gotchas.md)** - EventEmitter/Disposable pitfalls, @disposed conflicts
191
192
 
data/README.md CHANGED
@@ -78,9 +78,13 @@ Puppeteer::Bidi.launch(
78
78
  ### Connect to an existing browser
79
79
 
80
80
  ```ruby
81
- browser = Puppeteer::Bidi.launch_browser_instance(headless: true)
82
- ws_endpoint = browser.ws_endpoint
83
- browser.disconnect
81
+ ws_endpoint = nil
82
+
83
+ Sync do
84
+ browser = Puppeteer::Bidi.launch_browser_instance(headless: true)
85
+ ws_endpoint = browser.ws_endpoint
86
+ browser.disconnect
87
+ end
84
88
 
85
89
  Puppeteer::Bidi.connect(ws_endpoint) do |session|
86
90
  puts "Reconnected to browser"
data/Rakefile CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ t.verbose = true
11
+ end
5
12
 
6
13
  RSpec::Core::RakeTask.new(:spec)
7
14
 
@@ -92,7 +92,7 @@ module Puppeteer
92
92
  # Start transport connection in background thread with Sync reactor
93
93
  # Sync is the preferred way to run async code at the top level
94
94
  timeout_ms = ((timeout || 30) * 1000).to_i
95
- AsyncUtils.async_timeout(timeout_ms, transport.connect).wait
95
+ AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait
96
96
 
97
97
  connection = Connection.new(transport)
98
98
 
@@ -111,7 +111,7 @@ module Puppeteer
111
111
  transport = Transport.new(ws_endpoint)
112
112
  ws_endpoint = transport.url
113
113
  timeout_ms = ((timeout || 30) * 1000).to_i
114
- AsyncUtils.async_timeout(timeout_ms, transport.connect).wait
114
+ AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait
115
115
  connection = Connection.new(transport)
116
116
 
117
117
  # Verify that this endpoint speaks WebDriver BiDi (and is ready) before creating a new session.
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "async"
5
+ require "async/promise"
6
+ require "async/queue"
7
+ require "delegate"
8
+ require "thread"
9
+
10
+ module Puppeteer
11
+ module Bidi
12
+ # Runs a dedicated Async reactor in a background thread and proxies calls into it.
13
+ class ReactorRunner
14
+ class Proxy < SimpleDelegator
15
+ # @rbs runner: ReactorRunner -- Reactor runner
16
+ # @rbs target: untyped -- Target object to proxy
17
+ # @rbs owns_runner: bool -- Whether to close runner on close/disconnect
18
+ # @rbs return: void
19
+ def initialize(runner, target, owns_runner: false)
20
+ super(target)
21
+ @runner = runner
22
+ @owns_runner = owns_runner
23
+ end
24
+
25
+ def method_missing(name, *args, **kwargs, &block)
26
+ if @owns_runner && @runner.closed? && close_like?(name)
27
+ return nil
28
+ end
29
+
30
+ begin
31
+ @runner.sync do
32
+ args = args.map { |arg| @runner.unwrap(arg) }
33
+ kwargs = kwargs.transform_values { |value| @runner.unwrap(value) }
34
+ result = __getobj__.public_send(name, *args, **kwargs, &block)
35
+ @runner.wrap(result)
36
+ end
37
+ ensure
38
+ @runner.close if @owns_runner && close_like?(name)
39
+ end
40
+ end
41
+
42
+ def respond_to_missing?(name, include_private = false)
43
+ __getobj__.respond_to?(name, include_private) || super
44
+ end
45
+
46
+ def class
47
+ __getobj__.class
48
+ end
49
+
50
+ def is_a?(klass)
51
+ __getobj__.is_a?(klass)
52
+ end
53
+
54
+ alias kind_of? is_a?
55
+
56
+ def instance_of?(klass)
57
+ __getobj__.instance_of?(klass)
58
+ end
59
+
60
+ def ==(other)
61
+ __getobj__ == @runner.unwrap(other)
62
+ end
63
+
64
+ def eql?(other)
65
+ __getobj__.eql?(@runner.unwrap(other))
66
+ end
67
+
68
+ def hash
69
+ __getobj__.hash
70
+ end
71
+
72
+ private
73
+
74
+ def close_like?(name)
75
+ name == :close || name == :disconnect
76
+ end
77
+ end
78
+
79
+ # @rbs return: void
80
+ def initialize
81
+ @queue = Async::Queue.new
82
+ @ready = Queue.new
83
+ @closed = false
84
+ @thread = Thread.new do
85
+ Sync do |task|
86
+ @ready << true
87
+ @queue.async(parent: task) do |_async_task, job|
88
+ job.call
89
+ end
90
+ ensure
91
+ @closed = true
92
+ end
93
+ end
94
+
95
+ @ready.pop
96
+ end
97
+
98
+ # @rbs &block: () -> untyped
99
+ # @rbs return: untyped
100
+ def sync(&block)
101
+ return block.call if runner_thread?
102
+ raise Error, "ReactorRunner is closed" if closed?
103
+
104
+ promise = Async::Promise.new
105
+ job = lambda do
106
+ begin
107
+ promise.resolve(block.call)
108
+ rescue => e
109
+ promise.reject(e)
110
+ end
111
+ end
112
+
113
+ begin
114
+ @queue << job
115
+ rescue Async::Queue::ClosedError
116
+ raise Error, "ReactorRunner is closed"
117
+ end
118
+
119
+ promise.wait
120
+ end
121
+
122
+ # @rbs return: void
123
+ def close
124
+ return if closed?
125
+
126
+ @closed = true
127
+ @queue.close
128
+ @thread.join unless runner_thread?
129
+ end
130
+
131
+ # @rbs return: bool
132
+ def closed?
133
+ @closed
134
+ end
135
+
136
+ # @rbs value: untyped
137
+ # @rbs return: untyped
138
+ def wrap(value)
139
+ return value if value.nil? || value.is_a?(Proxy)
140
+
141
+ if value.is_a?(Array)
142
+ return value.map { |item| wrap(item) }
143
+ end
144
+
145
+ return Proxy.new(self, value) if proxyable?(value)
146
+
147
+ value
148
+ end
149
+
150
+ # @rbs value: untyped
151
+ # @rbs return: untyped
152
+ def unwrap(value)
153
+ case value
154
+ when Proxy
155
+ value.__getobj__
156
+ when Array
157
+ value.map { |item| unwrap(item) }
158
+ when Hash
159
+ value.transform_values { |item| unwrap(item) }
160
+ else
161
+ value
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def runner_thread?
168
+ Thread.current == @thread
169
+ end
170
+
171
+ def proxyable?(value)
172
+ return false if value.is_a?(Module) || value.is_a?(Class)
173
+
174
+ name = value.class.name
175
+ return false unless name&.start_with?("Puppeteer::Bidi")
176
+ return false if name.start_with?("Puppeteer::Bidi::Core")
177
+ return false if value.is_a?(ReactorRunner) || value.is_a?(Proxy)
178
+
179
+ true
180
+ end
181
+ end
182
+ end
183
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Puppeteer
4
4
  module Bidi
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3.beta1"
6
6
  end
7
7
  end
@@ -5,6 +5,7 @@ require "puppeteer/bidi/version"
5
5
  require "puppeteer/bidi/errors"
6
6
 
7
7
  require "puppeteer/bidi/async_utils"
8
+ require "puppeteer/bidi/reactor_runner"
8
9
  require "puppeteer/bidi/timeout_settings"
9
10
  require "puppeteer/bidi/task_manager"
10
11
  require "puppeteer/bidi/serializer"
@@ -75,17 +76,39 @@ module Puppeteer
75
76
  # @rbs args: Array[String]? -- Additional browser arguments
76
77
  # @rbs timeout: Numeric? -- Launch timeout in seconds
77
78
  # @rbs accept_insecure_certs: bool -- Accept insecure certificates
78
- # @rbs return: Browser -- Browser instance (if no block given)
79
+ # @rbs return: Browser -- Browser instance
79
80
  def self.launch_browser_instance(executable_path: nil, user_data_dir: nil, headless: true, args: nil, timeout: nil,
80
81
  accept_insecure_certs: false)
81
- Browser.launch(
82
- executable_path: executable_path,
83
- user_data_dir: user_data_dir,
84
- headless: headless,
85
- args: args,
86
- timeout: timeout,
87
- accept_insecure_certs: accept_insecure_certs
88
- )
82
+ if async_context?
83
+ Browser.launch(
84
+ executable_path: executable_path,
85
+ user_data_dir: user_data_dir,
86
+ headless: headless,
87
+ args: args,
88
+ timeout: timeout,
89
+ accept_insecure_certs: accept_insecure_certs
90
+ )
91
+ else
92
+ runner = ReactorRunner.new
93
+ begin
94
+ browser = runner.sync do
95
+ Browser.launch(
96
+ executable_path: executable_path,
97
+ user_data_dir: user_data_dir,
98
+ headless: headless,
99
+ args: args,
100
+ timeout: timeout,
101
+ accept_insecure_certs: accept_insecure_certs
102
+ )
103
+ end
104
+ rescue StandardError
105
+ runner.close
106
+ raise
107
+ end
108
+ # @type var proxy: Browser
109
+ proxy = ReactorRunner::Proxy.new(runner, browser, owns_runner: true)
110
+ proxy
111
+ end
89
112
  end
90
113
 
91
114
  # Connect to an existing browser instance
@@ -116,7 +139,31 @@ module Puppeteer
116
139
  # @rbs accept_insecure_certs: bool -- Accept insecure certificates
117
140
  # @rbs return: Browser -- Browser instance
118
141
  def self.connect_to_browser_instance(ws_endpoint, timeout: nil, accept_insecure_certs: false)
119
- Browser.connect(ws_endpoint, timeout: timeout, accept_insecure_certs: accept_insecure_certs)
142
+ if async_context?
143
+ Browser.connect(ws_endpoint, timeout: timeout, accept_insecure_certs: accept_insecure_certs)
144
+ else
145
+ runner = ReactorRunner.new
146
+ begin
147
+ browser = runner.sync do
148
+ Browser.connect(ws_endpoint, timeout: timeout, accept_insecure_certs: accept_insecure_certs)
149
+ end
150
+ rescue StandardError
151
+ runner.close
152
+ raise
153
+ end
154
+ # @type var proxy: Browser
155
+ proxy = ReactorRunner::Proxy.new(runner, browser, owns_runner: true)
156
+ proxy
157
+ end
158
+ end
159
+
160
+ # @rbs return: bool -- Whether we're inside an Async task
161
+ def self.async_context?
162
+ task = Async::Task.current
163
+ !task.nil?
164
+ rescue RuntimeError, NoMethodError
165
+ false
120
166
  end
167
+ private_class_method :async_context?
121
168
  end
122
169
  end
data/sig/_external.rbs CHANGED
@@ -11,12 +11,22 @@ module Async
11
11
  def self.call: [T] () { () -> T } -> Task[T]
12
12
 
13
13
  class Task[T]
14
+ def self.current: () -> Task[untyped]?
14
15
  def wait: () -> T
15
16
  def sleep: (Numeric) -> void
16
17
  def stop: () -> void
17
18
  def with_timeout: [U] (Numeric) { () -> U } -> U
18
19
  end
19
20
 
21
+ class Queue[T]
22
+ class ClosedError < StandardError
23
+ end
24
+ def initialize: () -> void
25
+ def <<: (T) -> void
26
+ def close: () -> void
27
+ def async: [U] (?parent: Task[untyped]?) { (Task[untyped], T) -> U } -> void
28
+ end
29
+
20
30
  class Promise[T]
21
31
  def initialize: () -> void
22
32
  def wait: () -> T
@@ -126,3 +136,8 @@ class DateTime
126
136
  def to_time: () -> Time
127
137
  end
128
138
 
139
+ # Delegate library
140
+ class SimpleDelegator
141
+ def __getobj__: () -> untyped
142
+ def __setobj__: (untyped) -> untyped
143
+ end
@@ -0,0 +1,65 @@
1
+ # Generated from lib/puppeteer/bidi/reactor_runner.rb with RBS::Inline
2
+
3
+ module Puppeteer
4
+ module Bidi
5
+ # Runs a dedicated Async reactor in a background thread and proxies calls into it.
6
+ class ReactorRunner
7
+ class Proxy < SimpleDelegator
8
+ # @rbs runner: ReactorRunner -- Reactor runner
9
+ # @rbs target: untyped -- Target object to proxy
10
+ # @rbs owns_runner: bool -- Whether to close runner on close/disconnect
11
+ # @rbs return: void
12
+ def initialize: (ReactorRunner runner, untyped target, ?owns_runner: bool) -> void
13
+
14
+ def method_missing: (untyped name, *untyped args, **untyped kwargs) ?{ (?) -> untyped } -> untyped
15
+
16
+ def respond_to_missing?: (untyped name, ?untyped include_private) -> untyped
17
+
18
+ def class: () -> untyped
19
+
20
+ def is_a?: (untyped klass) -> untyped
21
+
22
+ alias kind_of? is_a?
23
+
24
+ def instance_of?: (untyped klass) -> untyped
25
+
26
+ def ==: (untyped other) -> untyped
27
+
28
+ def eql?: (untyped other) -> untyped
29
+
30
+ def hash: () -> untyped
31
+
32
+ private
33
+
34
+ def close_like?: (untyped name) -> untyped
35
+ end
36
+
37
+ # @rbs return: void
38
+ def initialize: () -> void
39
+
40
+ # @rbs &block: () -> untyped
41
+ # @rbs return: untyped
42
+ def sync: () { () -> untyped } -> untyped
43
+
44
+ # @rbs return: void
45
+ def close: () -> void
46
+
47
+ # @rbs return: bool
48
+ def closed?: () -> bool
49
+
50
+ # @rbs value: untyped
51
+ # @rbs return: untyped
52
+ def wrap: (untyped value) -> untyped
53
+
54
+ # @rbs value: untyped
55
+ # @rbs return: untyped
56
+ def unwrap: (untyped value) -> untyped
57
+
58
+ private
59
+
60
+ def runner_thread?: () -> untyped
61
+
62
+ def proxyable?: (untyped value) -> untyped
63
+ end
64
+ end
65
+ end
@@ -20,7 +20,7 @@ module Puppeteer
20
20
  # @rbs args: Array[String]? -- Additional browser arguments
21
21
  # @rbs timeout: Numeric? -- Launch timeout in seconds
22
22
  # @rbs accept_insecure_certs: bool -- Accept insecure certificates
23
- # @rbs return: Browser -- Browser instance (if no block given)
23
+ # @rbs return: Browser -- Browser instance
24
24
  def self.launch_browser_instance: (?executable_path: String?, ?user_data_dir: String?, ?headless: bool, ?args: Array[String]?, ?timeout: Numeric?, ?accept_insecure_certs: bool) -> Browser
25
25
 
26
26
  # Connect to an existing browser instance
@@ -37,5 +37,8 @@ module Puppeteer
37
37
  # @rbs accept_insecure_certs: bool -- Accept insecure certificates
38
38
  # @rbs return: Browser -- Browser instance
39
39
  def self.connect_to_browser_instance: (String ws_endpoint, ?timeout: Numeric?, ?accept_insecure_certs: bool) -> Browser
40
+
41
+ # @rbs return: bool -- Whether we're inside an Async task
42
+ def self.async_context?: () -> bool
40
43
  end
41
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppeteer-bidi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
@@ -95,6 +95,7 @@ files:
95
95
  - CLAUDE/navigation_waiting.md
96
96
  - CLAUDE/porting_puppeteer.md
97
97
  - CLAUDE/query_handler.md
98
+ - CLAUDE/reactor_runner.md
98
99
  - CLAUDE/rspec_pending_vs_skip.md
99
100
  - CLAUDE/selector_evaluation.md
100
101
  - CLAUDE/test_server_routes.md
@@ -145,6 +146,7 @@ files:
145
146
  - lib/puppeteer/bidi/mouse.rb
146
147
  - lib/puppeteer/bidi/page.rb
147
148
  - lib/puppeteer/bidi/query_handler.rb
149
+ - lib/puppeteer/bidi/reactor_runner.rb
148
150
  - lib/puppeteer/bidi/realm.rb
149
151
  - lib/puppeteer/bidi/serializer.rb
150
152
  - lib/puppeteer/bidi/target.rb
@@ -191,6 +193,7 @@ files:
191
193
  - sig/puppeteer/bidi/mouse.rbs
192
194
  - sig/puppeteer/bidi/page.rbs
193
195
  - sig/puppeteer/bidi/query_handler.rbs
196
+ - sig/puppeteer/bidi/reactor_runner.rbs
194
197
  - sig/puppeteer/bidi/realm.rbs
195
198
  - sig/puppeteer/bidi/serializer.rbs
196
199
  - sig/puppeteer/bidi/target.rbs