diff --git a/rb/lib/selenium/webdriver/bidi/network.rb b/rb/lib/selenium/webdriver/bidi/network.rb index 26f62f64a22dc..0042ac8578d1d 100644 --- a/rb/lib/selenium/webdriver/bidi/network.rb +++ b/rb/lib/selenium/webdriver/bidi/network.rb @@ -82,15 +82,16 @@ def cancel_auth(request_id) end def continue_request(**args) - @bidi.send_cmd( - 'network.continueRequest', + args = { request: args[:id], body: args[:body], cookies: args[:cookies], headers: args[:headers], method: args[:method], url: args[:url] - ) + }.compact + + @bidi.send_cmd('network.continueRequest', **args) end def fail_request(request_id) @@ -101,27 +102,29 @@ def fail_request(request_id) end def continue_response(**args) - @bidi.send_cmd( - 'network.continueResponse', + args = { request: args[:id], cookies: args[:cookies], credentials: args[:credentials], headers: args[:headers], reasonPhrase: args[:reason], statusCode: args[:status] - ) + }.compact + + @bidi.send_cmd('network.continueResponse', **args) end def provide_response(**args) - @bidi.send_cmd( - 'network.provideResponse', + args = { request: args[:id], body: args[:body], cookies: args[:cookies], headers: args[:headers], reasonPhrase: args[:reason], statusCode: args[:status] - ) + }.compact + + @bidi.send_cmd('network.provideResponse', **args) end def set_cache_behavior(behavior, *contexts) diff --git a/rb/lib/selenium/webdriver/bidi/network/cookies.rb b/rb/lib/selenium/webdriver/bidi/network/cookies.rb index 7cc5d7871ce23..027a658a59f77 100644 --- a/rb/lib/selenium/webdriver/bidi/network/cookies.rb +++ b/rb/lib/selenium/webdriver/bidi/network/cookies.rb @@ -21,16 +21,13 @@ 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} + map do |name, val| + self[:name] = name.to_s + self[:value] = {type: 'string', value: val.to_s} - [compact] + [compact] + end end end end # BiDi diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb index ad2ef94295092..f87c99d620252 100644 --- a/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb +++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_request.rb @@ -32,14 +32,18 @@ def initialize(network, request) @method = nil @url = nil @body = nil + @headers = nil + @cookies = nil end def continue + cookies = @cookies&.as_json + headers = @headers&.as_json network.continue_request( id: id, body: body, - cookies: cookies.as_json, - headers: headers.as_json, + cookies: cookies, + headers: headers, method: method, url: url ) @@ -56,13 +60,21 @@ def body=(value) } end - def headers - @headers ||= Headers.new + def headers=(headers = {}) + @headers = Headers.new(headers) + end + + def headers(headers = {}) + @headers ||= Headers.new(headers) end def cookies(cookies = {}) @cookies ||= Cookies.new(cookies) end + + def cookies=(cookies = {}) + @cookies = Cookies.new(cookies) + end end end # BiDi end # WebDriver diff --git a/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb b/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb index 08e4b9c84a715..433c9edcadb23 100644 --- a/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb +++ b/rb/lib/selenium/webdriver/bidi/network/intercepted_response.rb @@ -33,13 +33,17 @@ def initialize(network, request) @reason = nil @status = nil @body = nil + @headers = nil + @cookies = nil end def continue + cookies = @cookies&.as_json + headers = @headers&.as_json network.continue_response( id: id, - cookies: cookies.as_json, - headers: headers.as_json, + cookies: cookies, + headers: headers, credentials: credentials.as_json, reason: reason, status: status @@ -47,10 +51,12 @@ def continue end def provide_response + cookies = @cookies&.as_json + headers = @headers&.as_json network.provide_response( id: id, - cookies: cookies.as_json, - headers: headers.as_json, + cookies: cookies, + headers: headers, body: body, reason: reason, status: status @@ -61,14 +67,22 @@ def credentials(username: nil, password: nil) @credentials ||= Credentials.new(username: username, password: password) end - def headers - @headers ||= Headers.new + def headers(headers = {}) + @headers ||= Headers.new(headers) + end + + def headers=(*headers) + @headers = Headers.new(headers) end def cookies(cookies = {}) @cookies ||= Cookies.new(cookies) end + def cookies=(cookies = {}) + @cookies ||= Cookies.new(cookies) + end + def body=(value) @body = { type: 'string', diff --git a/rb/sig/lib/selenium/webdriver/bidi/network.rbs b/rb/sig/lib/selenium/webdriver/bidi/network.rbs index e660f684d9ffd..bc286a14b245d 100644 --- a/rb/sig/lib/selenium/webdriver/bidi/network.rbs +++ b/rb/sig/lib/selenium/webdriver/bidi/network.rbs @@ -24,7 +24,7 @@ module Selenium def remove_intercept: (String intercept) -> Hash[nil, nil] - def continue_with_auth: (String request_id, String username, String password) -> Hash[nil, nil] + def continue_with_auth: (Integer request_id, String username, String password) -> Hash[nil, nil] def provide_response: -> Hash[nil, nil] diff --git a/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs b/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs index 5688a4e9b63cc..4d735480e64a7 100644 --- a/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs +++ b/rb/sig/lib/selenium/webdriver/bidi/network/cookies.rbs @@ -2,7 +2,7 @@ module Selenium module WebDriver class BiDi class Cookies - def initialize: (Hash[Symbol, String | Integer | bool] cookies) -> void + def initialize: (Hash[Symbol, String | Integer | bool]? cookies) -> void def as_json: () -> Array[Hash[Symbol, untyped]] 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 index 04cecb59457dc..ad226a1922efa 100644 --- a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_request.rbs +++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_request.rbs @@ -2,15 +2,15 @@ module Selenium module WebDriver class BiDi class InterceptedRequest < InterceptedItem - @method: String + @method: String? - @url: String + @url: String? - @body: Hash[untyped, untyped] + @body: Hash[untyped, untyped]? - @headers: Hash[untyped, untyped] + @headers: Hash[untyped, untyped]? - @cookies: Hash[untyped, untyped] + @cookies: Hash[untyped, untyped]? attr_accessor method: String @@ -22,6 +22,8 @@ module Selenium def continue: () -> Hash[nil, nil] + def cookies=: -> Hash[nil, nil] + def fail: () -> Hash[nil, nil] def body=: (Hash[untyped, untyped] value) -> Hash[untyped, untyped] @@ -29,6 +31,8 @@ module Selenium def headers: () -> Hash[untyped, untyped] def cookies: () -> Hash[untyped, untyped] + + def headers=: -> Hash[untyped, untyped] 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 index 22263c22165b3..f087452867b7b 100644 --- a/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_response.rbs +++ b/rb/sig/lib/selenium/webdriver/bidi/network/intercepted_response.rbs @@ -5,7 +5,7 @@ module Selenium @body: untyped @cookies: Hash[Symbol, String | Integer]? - @reason: String + @reason: String? @credentials: untyped @@ -24,10 +24,14 @@ module Selenium def cookies:(?Hash[Symbol, String | Integer]? set_cookie_headers) -> untyped + def cookies=: -> Hash[Symbol, String | Integer] + def credentials: (?username: untyped?, ?password: untyped?) -> untyped def headers: () -> untyped + def headers=: -> Hash[untyped, untyped] + def provide_response: -> untyped def set_cookie_headers: (?untyped? set_cookie_headers) -> untyped diff --git a/rb/spec/unit/selenium/webdriver/bidi/intercepted_request_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/intercepted_request_spec.rb new file mode 100644 index 0000000000000..961e7dc3b8256 --- /dev/null +++ b/rb/spec/unit/selenium/webdriver/bidi/intercepted_request_spec.rb @@ -0,0 +1,104 @@ +# 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__) +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/headers', __dir__) +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/intercepted_request', __dir__) + +module Selenium + module WebDriver + class BiDi + describe InterceptedRequest do + let(:mock_network) { instance_double(Network) } + let(:mock_request) { {'request' => 'req-123'} } + let(:request_id) { 'req-123' } + let(:intercepted_request) { described_class.new(mock_network, mock_request) } + let(:mock_headers) { [{name: 'Auth', value: 'token'}] } + let(:mock_cookies) { [{name: 'session', value: {type: 'string', value: '123'}}] } + + describe '#continue' do + before do + allow(mock_network).to receive(:continue_request) + end + + it 'sends only the mandatory ID when no optional fields are set' do + expected_payload = {id: request_id, body: nil, cookies: nil, headers: nil, method: nil, url: nil} + intercepted_request.continue + + expect(mock_network).to have_received(:continue_request).with(expected_payload) + end + + it 'sends headers payload when headers are explicitly set' do + intercepted_request.headers = mock_headers + + expected_payload = { + id: request_id, + body: nil, + cookies: nil, + headers: Headers.new(mock_headers).as_json, + method: nil, + url: nil + } + + intercepted_request.continue + + expect(mock_network).to have_received(:continue_request).with(expected_payload) + end + + it 'sends cookies payload when cookies are explicitly set' do + intercepted_request.cookies = mock_cookies + + expected_payload = { + id: request_id, + body: nil, + cookies: Cookies.new(mock_cookies).as_json, + headers: nil, + method: nil, + url: nil + } + + intercepted_request.continue + + expect(mock_network).to have_received(:continue_request).with(expected_payload) + end + + it 'sends full custom payload when all fields are set' do + intercepted_request.headers = mock_headers + intercepted_request.cookies = mock_cookies + intercepted_request.method = 'POST' + + expected_payload = { + id: request_id, + body: nil, + cookies: Cookies.new(mock_cookies).as_json, + headers: Headers.new(mock_headers).as_json, + method: 'POST', + url: nil + } + + intercepted_request.continue + + expect(mock_network).to have_received(:continue_request).with(expected_payload) + end + end + end + end + end +end diff --git a/rb/spec/unit/selenium/webdriver/bidi/intercepted_response_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/intercepted_response_spec.rb new file mode 100644 index 0000000000000..8895c59f3520b --- /dev/null +++ b/rb/spec/unit/selenium/webdriver/bidi/intercepted_response_spec.rb @@ -0,0 +1,194 @@ +# 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__) +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/headers', __dir__) +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/credentials', __dir__) +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network/intercepted_response', __dir__) + +module Selenium + module WebDriver + class BiDi + describe InterceptedResponse do + let(:mock_network) { instance_double(Network) } + let(:response_id) { 'resp-456' } + let(:intercepted_response) { described_class.new(mock_network, {'request' => response_id}) } + let(:mock_headers_data) { [{name: 'Content-Type', value: 'application/json'}] } + let(:mock_cookies_data) { [{name: 'session', value: {type: 'string', value: 'abc'}}] } + let(:mock_body_string) { '{"message": "Access Denied"}' } + + before do + allow(intercepted_response).to receive(:id).and_return(response_id) + end + + describe 'Initialization and Default State' do + it 'has an empty hash as headers by default' do + expect(intercepted_response.headers).to be_empty + end + + it 'has an empty hash as cookies by default' do + expect(intercepted_response.cookies).to be_empty + end + end + + describe '#continue' do + before do + allow(mock_network).to receive(:continue_response) + end + + it 'sends nil headers and cookies when not explicitly set' do + expected_payload = {id: response_id, cookies: nil, headers: nil, credentials: nil, reason: nil, status: nil} + intercepted_response.continue + + expect(mock_network).to have_received(:continue_response).with(expected_payload) + end + + it 'sends headers payload and uses default nil cookies/credentials' do + intercepted_response.headers = mock_headers_data + reason = 'Custom Reason' + status = 201 + + expected_payload = { + id: response_id, + cookies: nil, + headers: Headers.new(mock_headers_data).as_json, + credentials: nil, + reason: reason, + status: status + } + intercepted_response.reason = reason + intercepted_response.status = status + intercepted_response.continue + + expect(mock_network).to have_received(:continue_response).with(expected_payload) + end + + it 'sends full custom payload when all fields are set' do + credentials = Credentials.new(username: 'user', password: 'pass') + + intercepted_response.headers = mock_headers_data + intercepted_response.cookies = mock_cookies_data + intercepted_response.credentials(username: 'user', password: 'pass') + intercepted_response.reason = 'Test Reason' + intercepted_response.status = 404 + + expected_payload = { + id: response_id, + cookies: Cookies.new(mock_cookies_data).as_json, + headers: Headers.new(mock_headers_data).as_json, + credentials: credentials.as_json, + reason: 'Test Reason', + status: 404 + } + + intercepted_response.continue + + expect(mock_network).to have_received(:continue_response).with(expected_payload) + end + end + + describe '#provide_response' do + before do + allow(mock_network).to receive(:provide_response) + end + + it 'sends nil headers, cookies, and body when not explicitly set' do + expected_payload = {id: response_id, cookies: nil, headers: nil, body: nil, reason: nil, status: nil} + intercepted_response.provide_response + + expect(mock_network).to have_received(:provide_response).with(expected_payload) + end + + it 'sends body payload and uses default [] cookies/headers' do + intercepted_response.body = mock_body_string + reason = 'Provided Success' + status = 200 + + expected_payload = { + id: response_id, + cookies: nil, + headers: nil, + body: {type: 'string', value: mock_body_string.to_json}, + reason: reason, + status: status + } + intercepted_response.reason = reason + intercepted_response.status = status + intercepted_response.provide_response + + expect(mock_network).to have_received(:provide_response).with(expected_payload) + end + + it 'sends full custom payload when all fields are set' do + intercepted_response.headers = mock_headers_data + intercepted_response.cookies = mock_cookies_data + intercepted_response.body = mock_body_string + intercepted_response.reason = 'Forbidden' + intercepted_response.status = 403 + + expected_payload = { + id: response_id, + cookies: Cookies.new(mock_cookies_data).as_json, + headers: Headers.new(mock_headers_data).as_json, + body: {type: 'string', value: mock_body_string.to_json}, + reason: 'Forbidden', + status: 403 + } + + intercepted_response.provide_response + + expect(mock_network).to have_received(:provide_response).with(expected_payload) + end + end + + describe 'setters/getters' do + it '#credentials' do + credentials = intercepted_response.credentials(username: 'u', password: 'p') + expect(credentials).to be_a(Credentials) + expect(intercepted_response.credentials).to be(credentials) + end + + it '#headers=' do + intercepted_response.headers = mock_headers_data + expect(intercepted_response.headers).to be_a(Headers) + end + + it '#cookies=' do + intercepted_response.cookies = mock_cookies_data + first_cookies = intercepted_response.cookies + intercepted_response.cookies = [{name: 'c2', value: 'v2'}] + second_cookies = intercepted_response.cookies + expect(first_cookies).to be(second_cookies) + end + + it '#body=' do + data = {key: 'value', number: 123} + intercepted_response.body = data + expected_body = { + type: 'string', + value: data.to_json + } + expect(intercepted_response.body).to eq(expected_body) + end + end + end + end + end +end diff --git a/rb/spec/unit/selenium/webdriver/bidi/network_spec.rb b/rb/spec/unit/selenium/webdriver/bidi/network_spec.rb new file mode 100644 index 0000000000000..ca4f4dcf6191e --- /dev/null +++ b/rb/spec/unit/selenium/webdriver/bidi/network_spec.rb @@ -0,0 +1,123 @@ +# 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__) +# Adjust the require path as necessary for your project structure +require File.expand_path('../../../../../lib/selenium/webdriver/bidi/network', __dir__) + +module Selenium + module WebDriver + class BiDi + describe Network do + let(:mock_bidi) { instance_double(BiDi, 'Bidi') } + let(:network) { described_class.new(mock_bidi) } + let(:request_id) { '12345-request-id' } + + before { allow(mock_bidi).to receive(:send_cmd).and_return({}) } + + describe '#continue_request' do + it 'sends only the mandatory request ID when all optional args are nil' do + expected_payload = {request: request_id} + + network.continue_request(id: request_id) + + expect(mock_bidi).to have_received(:send_cmd).with('network.continueRequest', expected_payload) + end + + it 'sends only provided optional args' do + expected_payload = { + request: request_id, + body: {type: 'string', value: 'new body'}, + method: 'POST' + } + + network.continue_request( + id: request_id, + body: {type: 'string', value: 'new body'}, + cookies: nil, + headers: nil, + method: 'POST' + ) + + expect(mock_bidi).to have_received(:send_cmd).with('network.continueRequest', expected_payload) + end + end + + describe '#continue_response' do + it 'sends only the mandatory request ID when all optional args are nil' do + expected_payload = {request: request_id} + + network.continue_response(id: request_id) + + expect(mock_bidi).to have_received(:send_cmd).with('network.continueResponse', expected_payload) + end + + it 'sends only provided optional args' do + expected_headers = [{name: 'Auth', value: {type: 'string', value: 'Token'}}] + expected_payload = { + request: request_id, + headers: expected_headers, + statusCode: 202 + } + + network.continue_response( + id: request_id, + cookies: nil, + credentials: nil, + headers: expected_headers, + reason: nil, + status: 202 + ) + + expect(mock_bidi).to have_received(:send_cmd).with('network.continueResponse', expected_payload) + end + end + + describe '#provide_response' do + it 'sends only the mandatory request ID when all optional args are nil' do + expected_payload = {request: request_id} + + network.provide_response(id: request_id) + + expect(mock_bidi).to have_received(:send_cmd).with('network.provideResponse', expected_payload) + end + + it 'sends only provided optional args' do + expected_payload = { + request: request_id, + body: {type: 'string', value: 'Hello'}, + reasonPhrase: 'OK-Custom' + } + + network.provide_response( + id: request_id, + body: {type: 'string', value: 'Hello'}, + cookies: nil, + headers: nil, + reason: 'OK-Custom', + status: nil + ) + + expect(mock_bidi).to have_received(:send_cmd).with('network.provideResponse', expected_payload) + end + end + end + end + end +end