diff --git a/rb/lib/selenium/webdriver/bidi.rb b/rb/lib/selenium/webdriver/bidi.rb index 500f75859bc37..879ac260ec37e 100644 --- a/rb/lib/selenium/webdriver/bidi.rb +++ b/rb/lib/selenium/webdriver/bidi.rb @@ -25,6 +25,7 @@ class BiDi autoload :LogHandler, 'selenium/webdriver/bidi/log_handler' autoload :BrowsingContext, 'selenium/webdriver/bidi/browsing_context' autoload :Struct, 'selenium/webdriver/bidi/struct' + autoload :NetworkInspector, 'selenium/webdriver/bidi/network_inspector' def initialize(url:) @ws = WebSocketConnection.new(url: url) diff --git a/rb/lib/selenium/webdriver/bidi/network_inspector.rb b/rb/lib/selenium/webdriver/bidi/network_inspector.rb new file mode 100644 index 0000000000000..22dc72ee971ff --- /dev/null +++ b/rb/lib/selenium/webdriver/bidi/network_inspector.rb @@ -0,0 +1,64 @@ +# 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 NetworkInspector + EVENTS = { + before_request_sent: 'beforeRequestSent', + fetch_error: 'fetchError', + response_completed: 'responseCompleted', + response_started: 'responseStarted' + }.freeze + + def initialize(driver, browsing_context_ids = nil) + unless driver.capabilities.web_socket_url + raise Error::WebDriverError, + 'WebDriver instance must support BiDi protocol' + end + + @bidi = driver.bidi + @bidi.session.subscribe('network.beforeRequestSent', browsing_context_ids) + @bidi.session.subscribe('network.responseStarted', browsing_context_ids) + @bidi.session.subscribe('network.responseCompleted', browsing_context_ids) + end + + def before_request_sent(&) + on(:before_request_sent, &) + end + + def response_started(&) + on(:response_started, &) + end + + def response_completed(&) + on(:response_completed, &) + end + + private + + def on(event, &block) + event = EVENTS[event] if event.is_a?(Symbol) + @bidi.callbacks["network.#{event}"] << block + end + end # NetworkInspector + end # Bidi + end # WebDriver +end # Selenium diff --git a/rb/spec/integration/selenium/webdriver/bidi/network_inspector_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/network_inspector_spec.rb new file mode 100644 index 0000000000000..455cceb047956 --- /dev/null +++ b/rb/spec/integration/selenium/webdriver/bidi/network_inspector_spec.rb @@ -0,0 +1,202 @@ +# 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 '../spec_helper' + +module Selenium + module WebDriver + class BiDi + describe NetworkInspector, only: {browser: %i[firefox]} do + let(:empty_page) { '/bidi/emptyPage.html' } + let(:empty_text) { '/bidi/emptyText.txt' } + let(:redirected_http_equiv) { '/bidi/redirected_http_equiv.html' } + + it 'can listen to event before request is sent' do + reset_driver!(web_socket_url: true) do |driver| + before_request_event = [] + inspector = described_class.new(driver) + inspector.before_request_sent do |event| + before_request_event.push(event) if event.dig('request', 'url').include? 'emptyPage.html' + end + + driver.navigate.to url_for(empty_page) + wait.until { !before_request_event.empty? } + + expect(before_request_event[0].dig('request', 'method')).to eq 'GET' + expect(before_request_event[0].dig('request', 'url')).to eq driver.current_url + end + end + + it 'can request cookies' do + reset_driver!(web_socket_url: true) do |driver| + before_request_event = [] + inspector = described_class.new(driver) + inspector.before_request_sent do |event| + before_request_event.push(event) if event.dig('request', 'url').include? 'emptyText.txt' + end + + driver.navigate.to url_for(empty_text) + driver.manage.add_cookie name: 'north', + value: 'biryani' + driver.navigate.refresh + wait.until { !before_request_event.empty? } + + expect(before_request_event[1].dig('request', 'method')).to eq 'GET' + expect(before_request_event[1].dig('request', 'url')).to eq driver.current_url + expect(before_request_event[1].dig('request', 'cookies', 0, 'name')).to eq 'north' + expect(before_request_event[1].dig('request', 'cookies', 0, 'value')).to eq 'biryani' + + driver.manage.add_cookie name: 'south', + value: 'dosa' + driver.navigate.refresh + + expect(before_request_event[2].dig('request', 'cookies', 1, 'name')).to eq 'south' + expect(before_request_event[2].dig('request', 'cookies', 1, 'value')).to eq 'dosa' + end + end + + it 'can redirect http equiv' do + reset_driver!(web_socket_url: true) do |driver| + before_request_event = [] + inspector = described_class.new(driver) + inspector.before_request_sent do |event| + if (event.dig('request', 'url').include? 'redirected_http_equiv.html') || + (event.dig('request', 'url').include? 'redirected.html') + before_request_event.push(event) + end + end + + driver.navigate.to url_for(redirected_http_equiv) + wait.until { driver.current_url.include? 'redirected.html' } + + expect(before_request_event[0].dig('request', 'method')).to eq 'GET' + expect(before_request_event[0].dig('request', 'url')).to include 'redirected_http_equiv.html' + expect(before_request_event[1].dig('request', 'method')).to eq 'GET' + expect(before_request_event[1].dig('request', 'url')).to include 'redirected.html' + end + end + + it 'can subscribe to response started' do + reset_driver!(web_socket_url: true) do |driver| + on_response_started = [] + inspector = described_class.new(driver) + inspector.response_started do |event| + on_response_started.push(event) if event.dig('request', 'url').include? 'emptyText.txt' + end + + driver.navigate.to url_for(empty_text) + wait.until { !on_response_started.empty? } + + expect(on_response_started[0].dig('request', 'method')).to eq 'GET' + expect(on_response_started[0].dig('request', 'url')).to eq driver.current_url + expect(on_response_started[0].dig('response', 'url')).to eq driver.current_url + expect(on_response_started[0].dig('response', 'fromCache')).to be false + expect(on_response_started[0].dig('response', 'mimeType')).to include 'text/plain' + expect(on_response_started[0].dig('response', 'status')).to eq 200 + expect(on_response_started[0].dig('response', 'statusText')).to eq 'OK' + end + end + + it 'test response started mime type' do + reset_driver!(web_socket_url: true) do |driver| + on_response_started = [] + inspector = described_class.new(driver) + inspector.response_started do |event| + if (event.dig('request', 'url').include? 'emptyPage.html') || + (event.dig('request', 'url').include? 'emptyText.txt') + on_response_started.push(event) + end + end + + # Checking mime type for 'html' text + driver.navigate.to url_for(empty_page) + wait.until { !on_response_started.empty? } + + expect(on_response_started[0].dig('request', 'method')).to eq 'GET' + expect(on_response_started[0].dig('request', 'url')).to eq driver.current_url + expect(on_response_started[0].dig('response', 'url')).to eq driver.current_url + expect(on_response_started[0].dig('response', 'mimeType')).to include 'text/html' + + # Checking mime type for 'plain' text + driver.navigate.to url_for(empty_text) + wait.until { !on_response_started.empty? } + + expect(on_response_started[1].dig('request', 'method')).to eq 'GET' + expect(on_response_started[1].dig('request', 'url')).to eq driver.current_url + expect(on_response_started[1].dig('response', 'url')).to eq driver.current_url + expect(on_response_started[1].dig('response', 'mimeType')).to include 'text/plain' + end + end + + it 'can subscribe to response completed' do + reset_driver!(web_socket_url: true) do |driver| + on_response_completed = [] + inspector = described_class.new(driver) + inspector.response_completed do |event| + on_response_completed.push(event) if event.dig('request', 'url').include? 'emptyPage.html' + end + + driver.navigate.to url_for(empty_page) + wait.until { !on_response_completed.empty? } + + expect(on_response_completed[0].dig('request', 'method')).to eq 'GET' + expect(on_response_completed[0].dig('request', 'url')).to eq driver.current_url + expect(on_response_completed[0].dig('response', 'url')).to eq driver.current_url + expect(on_response_completed[0].dig('response', 'fromCache')).to be false + expect(on_response_completed[0].dig('response', 'mimeType')).to include 'text/html' + expect(on_response_completed[0].dig('response', 'status')).to eq 200 + expect(on_response_completed[0].dig('response', 'statusText')).to eq 'OK' + expect(on_response_completed[0]['redirectCount']).to eq 0 + end + end + + it 'test response completed mime type' do + reset_driver!(web_socket_url: true) do |driver| + on_response_completed = [] + inspector = described_class.new(driver) + inspector.response_completed do |event| + if (event.dig('request', 'url').include? 'emptyPage.html') || + (event.dig('request', 'url').include? 'emptyText.txt') + on_response_completed.push(event) + end + end + + # Checking mime type for 'html' text + driver.navigate.to url_for(empty_page) + wait.until { !on_response_completed.empty? } + + expect(on_response_completed[0].dig('request', 'method')).to eq 'GET' + expect(on_response_completed[0].dig('request', 'url')).to eq driver.current_url + expect(on_response_completed[0].dig('response', 'url')).to eq driver.current_url + expect(on_response_completed[0].dig('response', 'mimeType')).to include 'text/html' + + # Checking mime type for 'plain' text + driver.navigate.to url_for(empty_text) + wait.until { !on_response_completed.empty? } + + expect(on_response_completed[1].dig('request', 'method')).to eq 'GET' + expect(on_response_completed[1].dig('request', 'url')).to eq driver.current_url + expect(on_response_completed[1].dig('response', 'url')).to eq driver.current_url + expect(on_response_completed[1].dig('response', 'mimeType')).to include 'text/plain' + end + end + end + end + end +end