diff --git a/rb/lib/selenium/webdriver/bidi.rb b/rb/lib/selenium/webdriver/bidi.rb
index 214091791fc45..ed3bcc7b12098 100644
--- a/rb/lib/selenium/webdriver/bidi.rb
+++ b/rb/lib/selenium/webdriver/bidi.rb
@@ -26,6 +26,10 @@ class BiDi
       autoload :BrowsingContext, 'selenium/webdriver/bidi/browsing_context'
       autoload :Struct, 'selenium/webdriver/bidi/struct'
       autoload :Network, 'selenium/webdriver/bidi/network'
+      autoload :InterceptedRequest, 'selenium/webdriver/bidi/network/intercepted_request'
+      autoload :InterceptedResponse, 'selenium/webdriver/bidi/network/intercepted_response'
+      autoload :InterceptedAuth, 'selenium/webdriver/bidi/network/intercepted_auth'
+      autoload :InterceptedItem, 'selenium/webdriver/bidi/network/intercepted_item'
 
       def initialize(url:)
         @ws = WebSocketConnection.new(url: url)
diff --git a/rb/lib/selenium/webdriver/bidi/network.rb b/rb/lib/selenium/webdriver/bidi/network.rb
index ae55f9200efe4..f3074fb6173b7 100644
--- a/rb/lib/selenium/webdriver/bidi/network.rb
+++ b/rb/lib/selenium/webdriver/bidi/network.rb
@@ -16,6 +16,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+require_relative 'network/url_pattern'
 
 module Selenium
   module WebDriver
@@ -39,8 +40,12 @@ def initialize(bidi)
           @bidi = bidi
         end
 
-        def add_intercept(phases: [], contexts: nil, url_patterns: nil)
-          @bidi.send_cmd('network.addIntercept', phases: phases, contexts: contexts, urlPatterns: url_patterns)
+        def add_intercept(phases: [], contexts: nil, url_patterns: nil, pattern_type: :string)
+          url_patterns = url_patterns && pattern_type ? UrlPattern.format_pattern(url_patterns, pattern_type) : nil
+          @bidi.send_cmd('network.addIntercept',
+                         phases: phases,
+                         contexts: contexts,
+                         urlPatterns: url_patterns)
         end
 
         def remove_intercept(intercept)
@@ -50,31 +55,67 @@ def remove_intercept(intercept)
         def continue_with_auth(request_id, username, password)
           @bidi.send_cmd(
             'network.continueWithAuth',
-            'request' => request_id,
-            'action' => 'provideCredentials',
-            'credentials' => {
-              'type' => 'password',
-              'username' => username,
-              'password' => password
+            request: request_id,
+            action: 'provideCredentials',
+            credentials: {
+              type: 'password',
+              username: username,
+              password: password
             }
           )
         end
 
-        def continue_with_request(**args)
+        def continue_without_auth(request_id)
           @bidi.send_cmd(
-            'network.continueWithRequest',
-            request: args[:request_id],
-            'body' => args[:body],
-            'cookies' => args[:cookies],
-            'headers' => args[:headers],
-            'method' => args[:method],
-            'url' => args[:url]
+            'network.continueWithAuth',
+            request: request_id,
+            action: 'default'
+          )
+        end
+
+        def cancel_auth(request_id)
+          @bidi.send_cmd(
+            'network.continueWithAuth',
+            request: request_id,
+            action: 'cancel'
+          )
+        end
+
+        def continue_request(**args)
+          @bidi.send_cmd(
+            'network.continueRequest',
+            request: args[:id],
+            body: args[:body],
+            cookies: args[:cookies],
+            headers: args[:headers],
+            method: args[:method],
+            url: args[:url]
+          )
+        end
+
+        def fail_request(request_id)
+          @bidi.send_cmd(
+            'network.failRequest',
+            request: request_id
+          )
+        end
+
+        def continue_response(**args)
+          @bidi.send_cmd(
+            'network.continueResponse',
+            request: args[:id],
+            cookies: args[:cookies],
+            credentials: args[:credentials],
+            headers: args[:headers],
+            reasonPhrase: args[:reason],
+            statusCode: args[:status]
           )
         end
 
         def on(event, &)
           event = EVENTS[event] if event.is_a?(Symbol)
           @bidi.add_callback(event, &)
+          @bidi.session.subscribe(event)
         end
       end # Network
     end # BiDi
diff --git a/rb/lib/selenium/webdriver/bidi/network/cookies.rb b/rb/lib/selenium/webdriver/bidi/network/cookies.rb
new file mode 100644
index 0000000000000..7cc5d7871ce23
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/cookies.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class Cookies < Hash
+        def initialize(cookies = {})
+          super()
+          merge!(cookies)
+        end
+
+        def as_json
+          self[:name] = self[:name].to_s
+          self[:value] = {type: 'string', value: self[:value].to_s}
+
+          [compact]
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/credentials.rb b/rb/lib/selenium/webdriver/bidi/network/credentials.rb
new file mode 100644
index 0000000000000..a0e3db7905410
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/credentials.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class Credentials
+        attr_accessor :username, :password
+
+        def initialize(username: nil, password: nil)
+          @username = username
+          @password = password
+        end
+
+        def as_json
+          return nil unless username && password
+
+          {
+            type: 'password',
+            username: username,
+            password: password
+          }
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/headers.rb b/rb/lib/selenium/webdriver/bidi/network/headers.rb
new file mode 100644
index 0000000000000..44983f6eb58fb
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/headers.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class Headers < Hash
+        def as_json
+          map do |name, val|
+            {
+              name: name.to_s,
+              value: {
+                type: 'string',
+                value: val.to_s
+              }
+            }
+          end
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_auth.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_auth.rb
new file mode 100644
index 0000000000000..71c5d7bcac785
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_auth.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedAuth < InterceptedItem
+        def authenticate(username, password)
+          network.continue_with_auth(id, username, password)
+        end
+
+        def skip
+          network.continue_without_auth(id)
+        end
+
+        def cancel
+          network.cancel_auth(id)
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_item.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_item.rb
new file mode 100644
index 0000000000000..1f7445ebbe78b
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_item.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedItem
+        attr_reader :network, :request
+
+        def initialize(network, request)
+          @network = network
+          @request = request
+        end
+
+        def id
+          @id ||= @request['request']
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb
new file mode 100644
index 0000000000000..ad2ef94295092
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require_relative 'cookies'
+require_relative 'headers'
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedRequest < InterceptedItem
+        attr_accessor :method, :url
+        attr_reader :body
+
+        def initialize(network, request)
+          super
+          @method = nil
+          @url = nil
+          @body = nil
+        end
+
+        def continue
+          network.continue_request(
+            id: id,
+            body: body,
+            cookies: cookies.as_json,
+            headers: headers.as_json,
+            method: method,
+            url: url
+          )
+        end
+
+        def fail
+          network.fail_request(id)
+        end
+
+        def body=(value)
+          @body = {
+            type: 'string',
+            value: value.to_json
+          }
+        end
+
+        def headers
+          @headers ||= Headers.new
+        end
+
+        def cookies(cookies = {})
+          @cookies ||= Cookies.new(cookies)
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb
new file mode 100644
index 0000000000000..08c67d9745f65
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require_relative 'credentials'
+require_relative 'headers'
+require_relative 'cookies'
+
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedResponse < InterceptedItem
+        attr_accessor :reason
+
+        def initialize(network, request)
+          super
+          @reason = nil
+        end
+
+        def continue
+          network.continue_response(
+            id: id,
+            cookies: cookies.as_json,
+            headers: headers.as_json,
+            credentials: credentials.as_json,
+            reason: reason
+          )
+        end
+
+        def credentials(username: nil, password: nil)
+          @credentials ||= Credentials.new(username: username, password: password)
+        end
+
+        def headers
+          @headers ||= Headers.new
+        end
+
+        def cookies(cookies = {})
+          @cookies ||= Cookies.new(cookies)
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/bidi/network/url_pattern.rb b/rb/lib/selenium/webdriver/bidi/network/url_pattern.rb
new file mode 100644
index 0000000000000..651d7f95471b2
--- /dev/null
+++ b/rb/lib/selenium/webdriver/bidi/network/url_pattern.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require 'uri'
+
+module Selenium
+  module WebDriver
+    class BiDi
+      module UrlPattern
+        module_function
+
+        def format_pattern(url_patterns, pattern_type)
+          case pattern_type
+          when :string
+            to_url_string_pattern(url_patterns)
+          when :url
+            to_url_pattern(url_patterns)
+          else
+            raise ArgumentError, "Unknown pattern type: #{pattern_type}"
+          end
+        end
+
+        def to_url_pattern(*url_patterns)
+          url_patterns.flatten.map do |url_pattern|
+            uri = URI.parse(url_pattern)
+
+            {
+              type: 'pattern',
+              protocol: uri.scheme || '',
+              hostname: uri.host || '',
+              port: uri.port.to_s || '',
+              pathname: uri.path || '',
+              search: uri.query || ''
+            }
+          end
+        end
+
+        def to_url_string_pattern(*url_patterns)
+          url_patterns.flatten.map do |url_pattern|
+            {
+              type: 'string',
+              pattern: url_pattern
+            }
+          end
+        end
+      end
+    end # BiDi
+  end # WebDriver
+end # Selenium
diff --git a/rb/lib/selenium/webdriver/common/network.rb b/rb/lib/selenium/webdriver/common/network.rb
index 91bc0525c8340..3ac30b6eb68f6 100644
--- a/rb/lib/selenium/webdriver/common/network.rb
+++ b/rb/lib/selenium/webdriver/common/network.rb
@@ -17,47 +17,85 @@
 # specific language governing permissions and limitations
 # under the License.
 
+require 'forwardable'
+
 module Selenium
   module WebDriver
     class Network
-      attr_reader :callbacks
+      extend Forwardable
+
+      attr_reader :callbacks, :network
+      alias bidi network
+
+      def_delegators :network, :continue_with_auth, :continue_with_request, :continue_with_response
 
       def initialize(bridge)
         @network = BiDi::Network.new(bridge.bidi)
         @callbacks = {}
       end
 
-      def add_authentication_handler(username, password)
-        intercept = @network.add_intercept(phases: [BiDi::Network::PHASES[:auth_required]])
-        auth_id = @network.on(:auth_required) do |event|
-          request_id = event['requestId']
-          @network.continue_with_auth(request_id, username, password)
-        end
-        @callbacks[auth_id] = intercept
+      def remove_handler(id)
+        intercept = callbacks[id]
+        network.remove_intercept(intercept['intercept'])
+        callbacks.delete(id)
+      end
+
+      def clear_handlers
+        callbacks.each_key { |id| remove_handler(id) }
+      end
 
-        auth_id
+      def add_authentication_handler(username = nil, password = nil, *filter, pattern_type: nil, &block)
+        selected_block =
+          if username && password
+            proc { |auth| auth.authenticate(username, password) }
+          else
+            block
+          end
+
+        add_handler(
+          :auth_required,
+          BiDi::Network::PHASES[:auth_required],
+          BiDi::InterceptedAuth,
+          filter,
+          pattern_type: pattern_type,
+          &selected_block
+        )
       end
 
-      def remove_handler(id)
-        intercept = @callbacks[id]
-        @network.remove_intercept(intercept['intercept'])
-        @callbacks.delete(id)
+      def add_request_handler(*filter, pattern_type: nil, &)
+        add_handler(
+          :before_request,
+          BiDi::Network::PHASES[:before_request],
+          BiDi::InterceptedRequest,
+          filter,
+          pattern_type: pattern_type,
+          &
+        )
       end
 
-      def clear_handlers
-        @callbacks.each_key { |id| remove_handler(id) }
+      def add_response_handler(*filter, pattern_type: nil, &)
+        add_handler(
+          :response_started,
+          BiDi::Network::PHASES[:response_started],
+          BiDi::InterceptedResponse,
+          filter,
+          pattern_type: pattern_type,
+          &
+        )
       end
 
-      def add_request_handler
-        intercept = @network.add_intercept(phases: [BiDi::Network::PHASES[:before_request]])
-        request_id = @network.on(:before_request) do |event|
-          request_id = event['requestId']
-          @network.continue_with_request(request_id: request_id)
-        end
+      private
 
-        @callbacks[request_id] = intercept
+      def add_handler(event_type, phase, intercept_type, filter, pattern_type: nil)
+        intercept = network.add_intercept(phases: [phase], url_patterns: [filter].flatten, pattern_type: pattern_type)
+        callback_id = network.on(event_type) do |event|
+          request = event['request']
+          intercepted_item = intercept_type.new(network, request)
+          yield(intercepted_item)
+        end
 
-        request_id
+        callbacks[callback_id] = intercept
+        callback_id
       end
     end # Network
   end # WebDriver
diff --git a/rb/sig/lib/selenium/webdriver/bidi.rbs b/rb/sig/lib/selenium/webdriver/bidi.rbs
index 314dc70fdd5f3..d5c257921d144 100644
--- a/rb/sig/lib/selenium/webdriver/bidi.rbs
+++ b/rb/sig/lib/selenium/webdriver/bidi.rbs
@@ -7,7 +7,7 @@ module Selenium
 
       def initialize: (url: String) -> void
 
-      def add_callback: (String event) { () -> void } -> Integer
+      def add_callback: (String | Symbol event) { () -> void } -> Integer
 
       def close: () -> nil
 
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network.rbs b/rb/sig/lib/selenium/webdriver/bidi/network.rbs
index 44645cf05d250..71d2588c65750 100644
--- a/rb/sig/lib/selenium/webdriver/bidi/network.rbs
+++ b/rb/sig/lib/selenium/webdriver/bidi/network.rbs
@@ -10,15 +10,23 @@ module Selenium
 
         def initialize: (BiDi bidi) -> void
 
-        def add_intercept: (?phases: Array[String], ?contexts: BrowsingContext?, ?url_patterns: untyped?) -> Hash[String, String]
+        def add_intercept: (?phases: Array[String], ?contexts: BrowsingContext?, ?url_patterns: String | Array[String]?) -> Hash[String, String]
 
-        def continue_with_request: -> untyped
+        def cancel_auth: -> Hash[nil, nil]
 
-        def remove_intercept: (String intercept) -> untyped
+        def continue_request: -> Hash[nil, nil]
 
-        def continue_with_auth: (String request_id, String username, String password) -> untyped
+        def continue_response: -> Hash[nil, nil]
 
-        def on: (Symbol event) { (?) -> untyped } -> untyped
+        def continue_without_auth: -> Hash[nil, nil]
+
+        def fail_request: -> Hash[nil, nil]
+
+        def remove_intercept: (String intercept) -> Hash[nil, nil]
+
+        def continue_with_auth: (String request_id, String username, String password) -> Hash[nil, nil]
+
+        def on: (Symbol event) { (?) -> untyped } -> Hash[nil, nil]
       end
     end
   end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs
new file mode 100644
index 0000000000000..bc5ceb7f1b680
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs
@@ -0,0 +1,13 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class Cookies
+        KNOWN_KEYS: Array[Symbol]
+
+        def initialize: (Hash[Symbol, String | Integer | bool] cookies) -> void
+
+        def as_json: () -> Array[Hash[Symbol, untyped]]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/credentials.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/credentials.rbs
new file mode 100644
index 0000000000000..5f9c75340ca72
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/credentials.rbs
@@ -0,0 +1,19 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class Credentials
+        @username: String
+
+        @password: String
+
+        attr_accessor username: String
+
+        attr_accessor password: String
+
+        def initialize: (?username: String?, ?password: String?) -> void
+
+        def as_json: () -> Hash[Symbol, String]?
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/headers.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/headers.rbs
new file mode 100644
index 0000000000000..987952ee9c397
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/headers.rbs
@@ -0,0 +1,11 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class Headers
+        def initialize: () -> void
+
+        def as_json: () -> Array[Hash[Symbol, String]]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_auth.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_auth.rbs
new file mode 100644
index 0000000000000..c7fb60df6a32e
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_auth.rbs
@@ -0,0 +1,13 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedAuth < InterceptedItem
+        def authenticate: (String username, String password) -> Hash[nil, nil]
+
+        def skip: () -> Hash[nil, nil]
+
+        def cancel: () -> Hash[nil, nil]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_item.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_item.rbs
new file mode 100644
index 0000000000000..ab73782de2d5f
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_item.rbs
@@ -0,0 +1,21 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedItem
+        @network: Network
+
+        @request: untyped
+
+        @id: Integer
+
+        attr_reader network: Network
+
+        attr_reader request: untyped
+
+        def initialize: (Network network, Hash[untyped, untyped] request) -> void
+
+        def id: () -> Integer
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_request.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_request.rbs
new file mode 100644
index 0000000000000..04cecb59457dc
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_request.rbs
@@ -0,0 +1,35 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedRequest < InterceptedItem
+        @method: String
+
+        @url: String
+
+        @body: Hash[untyped, untyped]
+
+        @headers: Hash[untyped, untyped]
+
+        @cookies: Hash[untyped, untyped]
+
+        attr_accessor method: String
+
+        attr_accessor url: String
+
+        attr_reader body: Hash[untyped, untyped]
+
+        def initialize: (Network network, Hash[untyped, untyped] request) -> void
+
+        def continue: () -> Hash[nil, nil]
+
+        def fail: () -> Hash[nil, nil]
+
+        def body=: (Hash[untyped, untyped] value) -> Hash[untyped, untyped]
+
+        def headers: () -> Hash[untyped, untyped]
+
+        def cookies: () -> Hash[untyped, untyped]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_response.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_response.rbs
new file mode 100644
index 0000000000000..ecba7f2bac96f
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_response.rbs
@@ -0,0 +1,27 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class InterceptedResponse < InterceptedItem
+        @cookies: Hash[Symbol, String | Integer]?
+
+        @reason: String
+
+        @credentials: untyped
+
+        @headers: untyped
+
+        attr_accessor reason: String
+
+        def initialize: (untyped network, untyped request) -> void
+
+        def continue: () -> untyped
+
+        def cookies:(?Hash[Symbol, String | Integer]? set_cookie_headers) -> untyped
+
+        def credentials: (?username: untyped?, ?password: untyped?) -> untyped
+
+        def headers: () -> untyped
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/set_cookie_headers.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/set_cookie_headers.rbs
new file mode 100644
index 0000000000000..7d7ce657d33ac
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/set_cookie_headers.rbs
@@ -0,0 +1,11 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      class SetCookieHeaders
+        def initialize: (Hash[Symbol, untyped] set_cookie_headers) -> void
+
+        def as_json: () -> Array[Hash[Symbol, untyped]]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/url_pattern.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/url_pattern.rbs
new file mode 100644
index 0000000000000..57353479f69f7
--- /dev/null
+++ b/rb/sig/lib/selenium/webdriver/bidi/network/url_pattern.rbs
@@ -0,0 +1,13 @@
+module Selenium
+  module WebDriver
+    class BiDi
+      module UrlPattern
+        def self?.format_pattern: (Array[String] | String url_patterns, Symbol pattern_type) -> Array[Hash[Symbol, String]]
+
+        def self?.to_url_pattern: (*String url_patterns) -> Array[Hash[Symbol, String]]
+
+        def self?.to_url_string_pattern: (*String url_patterns) -> Array[Hash[Symbol, String]]
+      end
+    end
+  end
+end
diff --git a/rb/sig/lib/selenium/webdriver/bidi/session.rbs b/rb/sig/lib/selenium/webdriver/bidi/session.rbs
index 99b94c52afd5b..ecd687ba8e8e6 100644
--- a/rb/sig/lib/selenium/webdriver/bidi/session.rbs
+++ b/rb/sig/lib/selenium/webdriver/bidi/session.rbs
@@ -10,7 +10,7 @@ module Selenium
 
         def status: () -> untyped
 
-        def subscribe: (untyped events, ?untyped? browsing_contexts) -> untyped
+        def subscribe: (untyped events, ?untyped? browsing_contexts) -> Hash[nil, nil]
 
         def unsubscribe: (untyped events, ?untyped? browsing_contexts) -> untyped
       end
diff --git a/rb/sig/lib/selenium/webdriver/common/network.rbs b/rb/sig/lib/selenium/webdriver/common/network.rbs
index 2ad03a748167e..193e7385439af 100644
--- a/rb/sig/lib/selenium/webdriver/common/network.rbs
+++ b/rb/sig/lib/selenium/webdriver/common/network.rbs
@@ -7,15 +7,25 @@ module Selenium
 
       attr_reader callbacks: Hash[String, String]
 
+      attr_reader network: BiDi::Network
+
+      alias bidi network
+
       def initialize: (Remote::Bridge bridge) -> void
 
-      def add_authentication_handler: (String username, String password) -> String
+      def remove_handler: (Numeric id) -> untyped
+
+      def clear_handlers: () -> untyped
+
+      def add_authentication_handler: (?String? username, ?String? password, *String filter, ?pattern_type: Symbol?) { (?) -> untyped } -> untyped
+
+      def add_request_handler: (*String filter, ?pattern_type: Symbol?) -> Hash[String, String]
 
-      def add_request_handler: -> Integer
+      def add_response_handler: (*String filter, ?pattern_type: Symbol?) -> Hash[String, String]
 
-      def clear_handlers: -> Hash[nil, nil]
+      private
 
-      def remove_handler: (Integer id) -> nil
+      def add_handler: (Symbol event_type, String phase, BiDi::InterceptedRequest | BiDi::InterceptedAuth | BiDi::InterceptedResponse intercept_type, Array[String] filter, ?pattern_type: Symbol?) { (untyped) -> untyped } -> untyped
     end
   end
 end
diff --git a/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb
index ad75644decac6..c02eee189450d 100644
--- a/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb
+++ b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb
@@ -32,6 +32,26 @@ class BiDi
           end
         end
 
+        it 'adds an intercept with a default pattern type' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            pattern = 'http://localhost:4444/formPage.html'
+            intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]], url_patterns: pattern)
+            expect(intercept).not_to be_nil
+          end
+        end
+
+        it 'adds an intercept with a url pattern' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            pattern = 'http://localhost:4444/formPage.html'
+            intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]],
+                                              url_patterns: pattern,
+                                              pattern_type: :url)
+            expect(intercept).not_to be_nil
+          end
+        end
+
         it 'removes an intercept' do
           reset_driver!(web_socket_url: true) do |driver|
             network = described_class.new(driver.bidi)
@@ -45,9 +65,10 @@ class BiDi
           password = SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last
           reset_driver!(web_socket_url: true) do |driver|
             network = described_class.new(driver.bidi)
-            network.add_intercept(phases: [described_class::PHASES[:auth_required]])
+            phases = [Selenium::WebDriver::BiDi::Network::PHASES[:auth_required]]
+            network.add_intercept(phases: phases)
             network.on(:auth_required) do |event|
-              request_id = event['requestId']
+              request_id = event['request']['request']
               network.continue_with_auth(request_id, username, password)
             end
 
@@ -56,13 +77,67 @@ class BiDi
           end
         end
 
-        it 'continues with request' do
+        it 'continues without auth' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            network.add_intercept(phases: [described_class::PHASES[:auth_required]])
+            network.on(:auth_required) do |event|
+              request_id = event['request']['request']
+              network.continue_without_auth(request_id)
+            end
+
+            expect { driver.navigate.to url_for('basicAuth') }.to raise_error(Error::WebDriverError)
+          end
+        end
+
+        it 'cancels auth' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            network.add_intercept(phases: [described_class::PHASES[:auth_required]])
+            network.on(:auth_required) do |event|
+              request_id = event['request']['request']
+              network.cancel_auth(request_id)
+            end
+
+            driver.navigate.to url_for('basicAuth')
+            expect(driver.find_element(tag_name: 'pre').text).to eq('Login please')
+          end
+        end
+
+        it 'continues request' do
           reset_driver!(web_socket_url: true) do |driver|
             network = described_class.new(driver.bidi)
             network.add_intercept(phases: [described_class::PHASES[:before_request]])
             network.on(:before_request) do |event|
-              request_id = event['requestId']
-              network.continue_with_request(request_id: request_id)
+              request_id = event['request']['request']
+              network.continue_request(id: request_id)
+            end
+
+            driver.navigate.to url_for('formPage.html')
+            expect(driver.find_element(name: 'login')).to be_displayed
+          end
+        end
+
+        it 'fails request' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            network.add_intercept(phases: [described_class::PHASES[:before_request]])
+            network.on(:before_request) do |event|
+              request_id = event['request']['request']
+              network.fail_request(request_id)
+            end
+
+            expect { driver.navigate.to url_for('formPage.html') }.to raise_error(Error::WebDriverError)
+          end
+        end
+
+        it 'continues response' do
+          reset_driver!(web_socket_url: true) do |driver|
+            network = described_class.new(driver.bidi)
+            network.add_intercept(phases: [described_class::PHASES[:response_started]])
+            network.on(:response_started) do |event|
+              request_id = event['request']['request']
+              network.continue_response(id: request_id)
             end
 
             driver.navigate.to url_for('formPage.html')
diff --git a/rb/spec/integration/selenium/webdriver/network_spec.rb b/rb/spec/integration/selenium/webdriver/network_spec.rb
index 2ad4bc9f5365c..3e74c678f76d9 100644
--- a/rb/spec/integration/selenium/webdriver/network_spec.rb
+++ b/rb/spec/integration/selenium/webdriver/network_spec.rb
@@ -30,6 +30,38 @@ module WebDriver
         reset_driver!(web_socket_url: true) do |driver|
           network = described_class.new(driver)
           network.add_authentication_handler(username, password)
+          driver.navigate.to url_for('basicAuth')
+          expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds an auth handler with a filter' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_authentication_handler(username, password, url_for('basicAuth'))
+          driver.navigate.to url_for('basicAuth')
+          expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds an auth handler with multiple filters' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_authentication_handler(username, password, url_for('basicAuth'), url_for('formPage.html'))
+          driver.navigate.to url_for('basicAuth')
+          expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds an auth handler with a pattern type' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_authentication_handler(username, password, url_for('basicAuth'), pattern_type: :url)
+          driver.navigate.to url_for('basicAuth')
+          expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
           expect(network.callbacks.count).to be 1
         end
       end
@@ -46,25 +78,110 @@ module WebDriver
       it 'clears all auth handlers' do
         reset_driver!(web_socket_url: true) do |driver|
           network = described_class.new(driver)
-          network.add_authentication_handler(username, password)
-          network.add_authentication_handler(username, password)
+          2.times { network.add_authentication_handler(username, password) }
           network.clear_handlers
           expect(network.callbacks.count).to be 0
         end
       end
 
+      it 'continues without auth' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_authentication_handler(&:skip)
+          expect { driver.navigate.to url_for('basicAuth') }.to raise_error(Error::WebDriverError)
+        end
+      end
+
+      it 'cancels auth' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_authentication_handler(&:cancel)
+          driver.navigate.to url_for('basicAuth')
+          expect(driver.find_element(tag_name: 'pre').text).to eq('Login please')
+        end
+      end
+
       it 'adds a request handler' do
         reset_driver!(web_socket_url: true) do |driver|
           network = described_class.new(driver)
-          network.add_request_handler
+          network.add_request_handler(&:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a request handler with a filter' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_request_handler(url_for('formPage.html'), &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a request handler with multiple filters' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_request_handler(url_for('formPage.html'), url_for('basicAuth'), &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
           expect(network.callbacks.count).to be 1
         end
       end
 
+      it 'adds a request handler with a pattern type' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_request_handler(url_for('formPage.html'), pattern_type: :url, &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a request handler with attributes' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_request_handler do |request|
+            request.method = 'GET'
+            request.url = url_for('formPage.html')
+            request.headers['foo'] = 'bar'
+            request.headers['baz'] = 'qux'
+            request.cookies({
+                              name: 'test',
+                              value: 'value4',
+                              domain: 'example.com',
+                              path: '/path',
+                              size: 1234,
+                              httpOnly: true,
+                              secure: true,
+                              sameSite: 'Strict',
+                              expiry: 1234
+                            })
+            request.body = ({test: 'example'})
+            request.continue
+          end
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'fails a request' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_request_handler(&:fail)
+          expect(network.callbacks.count).to be 1
+          expect { driver.navigate.to url_for('formPage.html') }.to raise_error(Error::WebDriverError)
+        end
+      end
+
       it 'removes a request handler' do
         reset_driver!(web_socket_url: true) do |driver|
           network = described_class.new(driver)
-          id = network.add_request_handler
+          id = network.add_request_handler(&:continue)
           network.remove_handler(id)
           expect(network.callbacks.count).to be 0
         end
@@ -73,8 +190,92 @@ module WebDriver
       it 'clears all request handlers' do
         reset_driver!(web_socket_url: true) do |driver|
           network = described_class.new(driver)
-          network.add_request_handler
-          network.add_request_handler
+          2.times { network.add_request_handler(&:continue) }
+          network.clear_handlers
+          expect(network.callbacks.count).to be 0
+        end
+      end
+
+      it 'adds a response handler' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_response_handler(&:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a response handler with a filter' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_response_handler(url_for('formPage.html'), &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.find_element(name: 'login')).to be_displayed
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a response handler with multiple filters' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_response_handler(url_for('formPage.html'), url_for('basicAuth'), &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.find_element(name: 'login')).to be_displayed
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a response handler with a pattern type' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_response_handler(url_for('formPage.html'), pattern_type: :url, &:continue)
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'adds a response handler with attributes' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          network.add_response_handler do |response|
+            response.reason = 'OK'
+            response.headers['foo'] = 'bar'
+            response.credentials.username = 'foo'
+            response.credentials.password = 'bar'
+            response.cookies({
+                               name: 'foo',
+                               domain: 'localhost',
+                               httpOnly: true,
+                               expiry: '1_000_000',
+                               maxAge: 1_000,
+                               path: '/',
+                               sameSite: 'none',
+                               secure: false
+                             })
+            response.continue
+          end
+          driver.navigate.to url_for('formPage.html')
+          expect(driver.current_url).to eq(url_for('formPage.html'))
+          expect(network.callbacks.count).to be 1
+        end
+      end
+
+      it 'removes a response handler' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          id = network.add_response_handler(&:continue)
+          network.remove_handler(id)
+          network.clear_handlers
+          expect(network.callbacks.count).to be 0
+        end
+      end
+
+      it 'clears all response handlers' do
+        reset_driver!(web_socket_url: true) do |driver|
+          network = described_class.new(driver)
+          2.times { network.add_response_handler(&:continue) }
           network.clear_handlers
           expect(network.callbacks.count).to be 0
         end
diff --git a/rb/spec/unit/selenium/webdriver/bidi/cookies_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/cookies_spec.rb
new file mode 100644
index 0000000000000..582d946276989
--- /dev/null
+++ b/rb/spec/unit/selenium/webdriver/bidi/cookies_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require File.expand_path('../spec_helper', __dir__)
+require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/cookies', __dir__)
+
+module Selenium
+  module WebDriver
+    class BiDi
+      describe Cookies do
+        it 'returns the cookies as json' do
+          cookies = described_class.new
+          cookies['key4'] = 'value4'
+          cookies['session_id'] = 'xyz123'
+
+          formatted_cookies = cookies.as_json
+          expect(formatted_cookies).to be_an(Array)
+          expect(formatted_cookies.first['key4']).to eq('value4')
+          expect(formatted_cookies.first['session_id']).to eq('xyz123')
+        end
+
+        it 'serializes the cookies needed for request' do
+          cookies =
+            described_class.new(
+              {
+                name: 'test',
+                value: 'value4',
+                domain: 'example.com',
+                path: '/path',
+                size: 1234,
+                httpOnly: true,
+                secure: true,
+                sameSite: 'Strict',
+                expiry: 1234
+              }
+            )
+
+          formatted_cookies = cookies.as_json
+          expect(formatted_cookies).to be_an(Array)
+          expect(formatted_cookies.size).to eq(1)
+
+          request_cookies = formatted_cookies.first
+          expect(request_cookies[:name]).to eq('test')
+          expect(request_cookies[:value][:type]).to eq('string')
+          expect(request_cookies[:value][:value]).to eq('value4')
+          expect(request_cookies[:domain]).to eq('example.com')
+          expect(request_cookies[:path]).to eq('/path')
+          expect(request_cookies[:expiry]).to eq(1234)
+          expect(request_cookies[:httpOnly]).to be(true)
+          expect(request_cookies[:secure]).to be(true)
+          expect(request_cookies[:sameSite]).to eq('Strict')
+          expect(request_cookies[:size]).to eq(1234)
+        end
+
+        it 'serializes the cookies needed for response' do
+          cookies = described_class.new({
+                                          name: 'test',
+                                          value: 'bar',
+                                          domain: 'localhost',
+                                          httpOnly: true,
+                                          expiry: '1_000_000',
+                                          maxAge: 1_000,
+                                          path: '/',
+                                          sameSite: 'lax',
+                                          secure: false
+                                        })
+
+          formatted_cookies = cookies.as_json
+          expect(formatted_cookies).to be_an(Array)
+          expect(formatted_cookies.size).to eq(1)
+          response_cookies = formatted_cookies.first
+          expect(response_cookies[:value][:type]).to eq('string')
+          expect(response_cookies[:value][:value]).to eq('bar')
+          expect(response_cookies[:domain]).to eq('localhost')
+          expect(response_cookies[:path]).to eq('/')
+          expect(response_cookies[:expiry]).to eq('1_000_000')
+          expect(response_cookies[:httpOnly]).to be(true)
+          expect(response_cookies[:secure]).to be(false)
+          expect(response_cookies[:sameSite]).to eq('lax')
+          expect(response_cookies[:maxAge]).to eq(1_000)
+        end
+      end
+    end
+  end
+end
diff --git a/rb/spec/unit/selenium/webdriver/bidi/credentials_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/credentials_spec.rb
new file mode 100644
index 0000000000000..3366a339f5d8e
--- /dev/null
+++ b/rb/spec/unit/selenium/webdriver/bidi/credentials_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require File.expand_path('../spec_helper', __dir__)
+require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/credentials', __dir__)
+
+module Selenium
+  module WebDriver
+    class BiDi
+      describe Credentials do
+        describe '#initialize' do
+          it 'initializes with nil username/password by default' do
+            creds = described_class.new
+            expect(creds.username).to be_nil
+            expect(creds.password).to be_nil
+          end
+
+          it 'allows initialization with username and password' do
+            creds = described_class.new(username: 'alice', password: 'secret')
+            expect(creds.username).to eq('alice')
+            expect(creds.password).to eq('secret')
+          end
+        end
+
+        describe '#username / #password' do
+          it 'allows setting and retrieving username' do
+            creds = described_class.new
+            creds.username = 'bob'
+            expect(creds.username).to eq('bob')
+          end
+
+          it 'allows setting and retrieving password' do
+            creds = described_class.new
+            creds.password = 'my_password'
+            expect(creds.password).to eq('my_password')
+          end
+        end
+
+        describe '#as_json' do
+          it 'returns nil if username is missing' do
+            creds = described_class.new(password: 'secret')
+            expect(creds.as_json).to be_nil
+          end
+
+          it 'returns nil if password is missing' do
+            creds = described_class.new(username: 'alice')
+            expect(creds.as_json).to be_nil
+          end
+
+          it 'returns a hash of the credentials when both username and password are present' do
+            creds = described_class.new(username: 'alice', password: 'secret')
+            formatted_creds = creds.as_json
+
+            expect(formatted_creds).to eq(
+              type: 'password',
+              username: 'alice',
+              password: 'secret'
+            )
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/rb/spec/unit/selenium/webdriver/bidi/headers_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/headers_spec.rb
new file mode 100644
index 0000000000000..c749189851748
--- /dev/null
+++ b/rb/spec/unit/selenium/webdriver/bidi/headers_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+# Licensed to the Software Freedom Conservancy (SFC) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The SFC licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+require File.expand_path('../spec_helper', __dir__)
+require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/headers', __dir__)
+
+module Selenium
+  module WebDriver
+    class BiDi
+      describe Headers do
+        let(:headers) { described_class.new }
+
+        it 'returns headers formatted as json' do
+          headers['Accept'] = 'application/json'
+          headers['User-Agent'] = 'MyAgent/1.0'
+
+          formatted_headers = headers.as_json
+          expect(formatted_headers).to be_an(Array)
+          expect(formatted_headers.size).to eq(2)
+
+          accept_item = formatted_headers.find { |h| h[:name] == 'Accept' }
+          expect(accept_item).not_to be_nil
+          expect(accept_item[:value]).to eq({type: 'string', value: 'application/json'})
+
+          ua_item = formatted_headers.find { |h| h[:name] == 'User-Agent' }
+          expect(ua_item).not_to be_nil
+          expect(ua_item[:value]).to eq({type: 'string', value: 'MyAgent/1.0'})
+        end
+      end
+    end
+  end
+end