diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index e9573c25b8a5f..fa019cb9eedab 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -26,6 +26,11 @@ on: required: false type: boolean default: false + safari-preview: + description: Whether this job needs Safari Technology Preview installed (macos only) + required: false + type: boolean + default: false node-version: description: Custom Node version to install required: false @@ -190,6 +195,11 @@ jobs: - name: Setup Safari if: inputs.needs-display && inputs.os == 'macos' run: sudo safaridriver --enable + - name: Setup Safari Technology Preview + if: inputs.safari-preview && inputs.os == 'macos' + run: | + brew install --cask safari-technology-preview + sudo "/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver" --enable - name: Import GPG key if: inputs.gpg-sign uses: crazy-max/ghaction-import-gpg@v7 diff --git a/.github/workflows/ci-ruby.yml b/.github/workflows/ci-ruby.yml index c6a3702fed30f..8698f4c15a47b 100644 --- a/.github/workflows/ci-ruby.yml +++ b/.github/workflows/ci-ruby.yml @@ -90,10 +90,11 @@ jobs: tag_filters: [chrome-beta, firefox-beta, edge-local] include: - os: macos - tag_filters: safari-local + tag_filters: safari-local,safari-preview-bidi with: name: (${{ matrix.tag_filters }}) needs-display: true + safari-preview: ${{ contains(matrix.tag_filters, 'safari-preview') }} os: ${{ matrix.os }} rerun-with-debug: true run: > diff --git a/rb/lib/selenium/webdriver/chromium/driver.rb b/rb/lib/selenium/webdriver/chromium/driver.rb index aa6af16aa0433..e83f9b9d5eb56 100644 --- a/rb/lib/selenium/webdriver/chromium/driver.rb +++ b/rb/lib/selenium/webdriver/chromium/driver.rb @@ -27,7 +27,6 @@ module Chromium class Driver < WebDriver::Driver EXTENSIONS = [DriverExtensions::HasCDP, - DriverExtensions::HasBiDi, DriverExtensions::HasCasting, DriverExtensions::HasFedCmDialog, DriverExtensions::HasNetworkConditions, diff --git a/rb/lib/selenium/webdriver/common.rb b/rb/lib/selenium/webdriver/common.rb index cc5df02d92be4..0412195a74266 100644 --- a/rb/lib/selenium/webdriver/common.rb +++ b/rb/lib/selenium/webdriver/common.rb @@ -76,7 +76,6 @@ require 'selenium/webdriver/common/driver_extensions/uploads_files' require 'selenium/webdriver/common/driver_extensions/full_page_screenshot' require 'selenium/webdriver/common/driver_extensions/has_addons' -require 'selenium/webdriver/common/driver_extensions/has_bidi' require 'selenium/webdriver/common/driver_extensions/has_devtools' require 'selenium/webdriver/common/driver_extensions/has_file_downloads' require 'selenium/webdriver/common/driver_extensions/has_session_events' diff --git a/rb/lib/selenium/webdriver/common/driver.rb b/rb/lib/selenium/webdriver/common/driver.rb index 568ba48dd6668..3a9de28b24805 100644 --- a/rb/lib/selenium/webdriver/common/driver.rb +++ b/rb/lib/selenium/webdriver/common/driver.rb @@ -90,6 +90,17 @@ def status @bridge.status end + # + # Returns the WebDriver BiDi connection for this session. + # + # @return [BiDi] + # @raise [Error::WebDriverError] if BiDi was not enabled for this session + # + + def bidi + @bridge.bidi + end + # # @return [Navigation] # @see Navigation diff --git a/rb/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb b/rb/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb deleted file mode 100644 index 859e44737e705..0000000000000 --- a/rb/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb +++ /dev/null @@ -1,36 +0,0 @@ -# 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 - module DriverExtensions - module HasBiDi - # - # Retrieves WebDriver BiDi connection. - # - # @return [BiDi] - # - - def bidi - @bridge.bidi - end - end # HasBiDi - end # DriverExtensions - end # WebDriver -end # Selenium diff --git a/rb/lib/selenium/webdriver/common/options.rb b/rb/lib/selenium/webdriver/common/options.rb index 4616a2c4f7472..0067ec40c9905 100644 --- a/rb/lib/selenium/webdriver/common/options.rb +++ b/rb/lib/selenium/webdriver/common/options.rb @@ -75,6 +75,8 @@ def initialize(**opts) @options = opts @options[:browser_name] = self.class::BROWSER + + enable_bidi! if @options[:web_socket_url] end # @@ -93,6 +95,12 @@ def add_option(name, value = nil) @options[name] = value end + # + # Enables WebDriver BiDi by requesting the W3C webSocketUrl capability. + # + # @return [Boolean] + # + def enable_bidi! @options[:web_socket_url] = true end diff --git a/rb/lib/selenium/webdriver/firefox/driver.rb b/rb/lib/selenium/webdriver/firefox/driver.rb index 3c2afa1d82848..39db8bc58a81e 100644 --- a/rb/lib/selenium/webdriver/firefox/driver.rb +++ b/rb/lib/selenium/webdriver/firefox/driver.rb @@ -29,7 +29,6 @@ class Driver < WebDriver::Driver EXTENSIONS = [DriverExtensions::HasAddons, DriverExtensions::FullPageScreenshot, DriverExtensions::HasContext, - DriverExtensions::HasBiDi, DriverExtensions::HasLogEvents, DriverExtensions::HasNetworkInterception, DriverExtensions::PrintsPage].freeze diff --git a/rb/lib/selenium/webdriver/remote/bidi_bridge.rb b/rb/lib/selenium/webdriver/remote/bidi_bridge.rb index fe5d3e8cc6677..9207914230ae3 100644 --- a/rb/lib/selenium/webdriver/remote/bidi_bridge.rb +++ b/rb/lib/selenium/webdriver/remote/bidi_bridge.rb @@ -25,8 +25,13 @@ class BiDiBridge < Bridge def create_session(capabilities) super - socket_url = @capabilities[:web_socket_url] - @bidi = Selenium::WebDriver::BiDi.new(url: socket_url) + + begin + @bidi = Selenium::WebDriver::BiDi.new(url: validated_socket_url) + rescue StandardError + quit + raise + end end def get(url) @@ -46,7 +51,7 @@ def refresh end def quit - bidi.close + bidi&.close rescue *QUIT_ERRORS nil ensure @@ -59,6 +64,14 @@ def close private + def validated_socket_url + url = @capabilities[:web_socket_url] + return url if url.is_a?(String) && url.start_with?('ws://', 'wss://') + + raise Error::WebDriverError, + "BiDi was enabled, but the remote end did not return a valid webSocketUrl: #{url.inspect}." + end + def browsing_context @browsing_context ||= WebDriver::BiDi::BrowsingContext.new(self) end diff --git a/rb/lib/selenium/webdriver/safari/options.rb b/rb/lib/selenium/webdriver/safari/options.rb index ed0a7408e18ac..2af690ec425a9 100644 --- a/rb/lib/selenium/webdriver/safari/options.rb +++ b/rb/lib/selenium/webdriver/safari/options.rb @@ -25,7 +25,8 @@ class Options < WebDriver::Options # @see https://developer.apple.com/documentation/webkit/about_webdriver_for_safari CAPABILITIES = {automatic_inspection: 'safari:automaticInspection', - automatic_profiling: 'safari:automaticProfiling'}.freeze + automatic_profiling: 'safari:automaticProfiling', + experimental_web_socket_url: 'safari:experimentalWebSocketUrl'}.freeze BROWSER = 'safari' TECHNOLOGY_PREVIEW = 'Safari Technology Preview' @@ -43,6 +44,20 @@ def browser_name=(value) def browser_name @options[:browser_name] = Safari.technology_preview? ? TECHNOLOGY_PREVIEW : BROWSER end + + # + # Enables WebDriver BiDi for Safari, which also requires the experimental capability, and + # warns that Safari's BiDi support is experimental. + # + # @return [Boolean] + # + + def enable_bidi! + super + WebDriver.logger.warn("Safari's WebDriver BiDi support is experimental and may be incomplete", + id: :safari_bidi) + @options[:experimental_web_socket_url] = true + end end # Options end # Safari end # WebDriver diff --git a/rb/sig/lib/selenium/webdriver/common/driver.rbs b/rb/sig/lib/selenium/webdriver/common/driver.rbs index 2fdc2c5e66692..f7294f63ed787 100644 --- a/rb/sig/lib/selenium/webdriver/common/driver.rbs +++ b/rb/sig/lib/selenium/webdriver/common/driver.rbs @@ -39,6 +39,8 @@ module Selenium def status: () -> Hash[untyped, untyped] + def bidi: () -> BiDi + def navigate: () -> Navigation def switch_to: () -> TargetLocator diff --git a/rb/sig/lib/selenium/webdriver/common/driver_extensions/has_bidi.rbs b/rb/sig/lib/selenium/webdriver/common/driver_extensions/has_bidi.rbs deleted file mode 100644 index 8518eae49f96f..0000000000000 --- a/rb/sig/lib/selenium/webdriver/common/driver_extensions/has_bidi.rbs +++ /dev/null @@ -1,31 +0,0 @@ -# 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 - module DriverExtensions - module HasBiDi - include _Driver - - @bidi: untyped - - def bidi: () -> untyped - end - end - end -end diff --git a/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs b/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs index c314ccec06019..dcc7b40fc5238 100644 --- a/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs +++ b/rb/sig/lib/selenium/webdriver/remote/bidi_bridge.rbs @@ -40,6 +40,8 @@ module Selenium private + def validated_socket_url: () -> String + def browsing_context: () -> BiDi::BrowsingContext end end diff --git a/rb/spec/integration/selenium/webdriver/BUILD.bazel b/rb/spec/integration/selenium/webdriver/BUILD.bazel index 9fbb393ab8ecb..9415d899e23c9 100644 --- a/rb/spec/integration/selenium/webdriver/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/BUILD.bazel @@ -1,5 +1,5 @@ load("@rules_ruby//ruby:defs.bzl", "rb_library") -load("//rb/spec:tests.bzl", "DEFAULT_BROWSERS", "rb_integration_test") +load("//rb/spec:tests.bzl", "rb_integration_test") rb_library( name = "spec_helper", @@ -66,8 +66,7 @@ _NO_GRID = ["driver_finder_spec.rb"] rb_integration_test( name = f[:-8], srcs = [f], - browsers = DEFAULT_BROWSERS, - no_grid = True, + grid = False, tags = ["se-manager"], deps = [ "//rb/lib/selenium/webdriver:common", @@ -92,7 +91,7 @@ _NO_GRID = ["driver_finder_spec.rb"] rb_integration_test( name = f[:-8], srcs = [f], - tags = ["bidi"], + bidi = True, deps = [ "//rb/lib/selenium/webdriver:bidi", ], @@ -104,7 +103,8 @@ _NO_GRID = ["driver_finder_spec.rb"] rb_integration_test( name = f[:-8], srcs = [f], - bidi_only = True, + bidi = True, + classic = False, deps = [ "//rb/lib/selenium/webdriver:bidi", ], diff --git a/rb/spec/integration/selenium/webdriver/bidi/BUILD.bazel b/rb/spec/integration/selenium/webdriver/bidi/BUILD.bazel index 5b3b700e354bc..8cef0b2ee03b2 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/bidi/BUILD.bazel @@ -4,7 +4,8 @@ load("//rb/spec:tests.bzl", "rb_integration_test") rb_integration_test( name = file[:-8], srcs = [file], - bidi_only = True, + bidi = True, + classic = False, tags = ["exclusive-if-local"], deps = [ "//rb/lib/selenium/webdriver:bidi", diff --git a/rb/spec/integration/selenium/webdriver/bidi/browser_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/browser_spec.rb index cd0d7c075917f..0af1eff8cdb4e 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/browser_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/browser_spec.rb @@ -27,14 +27,18 @@ class BiDi let(:bidi) { driver.bidi } - it 'creates an user context' do + it 'creates a user context', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support BiDi user contexts or getClientWindows'} do browser = described_class.new(bidi) user_context = browser.create_user_context expect(user_context).not_to be_nil expect(user_context['userContext']).to be_a String end - it 'gets user contexts' do + it 'gets user contexts', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support BiDi user contexts or getClientWindows'} do browser = described_class.new(bidi) created_context_id = browser.create_user_context['userContext'] all_context_ids = browser.user_contexts['userContexts'].map { |c| c['userContext'] } @@ -51,21 +55,27 @@ class BiDi expect(all_ids_after_removal).not_to include(context_id_to_remove) end - it 'throws an error when removing the default user context' do + it 'throws an error when removing the default user context', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support BiDi user contexts or getClientWindows'} do browser = described_class.new(bidi) expect { browser.remove_user_context('default') }.to raise_error(Error::WebDriverError, /user context cannot be removed/) end - it 'throws an error when removing a non-existent user context' do + it 'throws an error when removing a non-existent user context', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support BiDi user contexts or getClientWindows'} do browser = described_class.new(bidi) expect { browser.remove_user_context('fake_context') }.to raise_error(Error::WebDriverError) end - it 'get windows' do + it 'gets windows', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support BiDi user contexts or getClientWindows'} do browser = described_class.new(bidi) windows = browser.windows active_window = windows.first diff --git a/rb/spec/integration/selenium/webdriver/bidi/browsing_context_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/browsing_context_spec.rb index fed7ba3b34194..5d235667a48e4 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/browsing_context_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/browsing_context_spec.rb @@ -46,7 +46,8 @@ class BiDi expect(driver.window_handles).to include(id) end - it 'errors on unknown type', pending_if: {browser: :firefox, reason: "Doesn't return the expected error"} do + it 'errors on unknown type', + pending_if: {browser: %i[firefox safari safari_preview], reason: "Doesn't return the expected error"} do msg = /invalid argument: Invalid enum value. Expected 'tab' | 'window', received 'unknown'/ expect { described_class.new(bridge).create(type: :unknown) @@ -123,7 +124,8 @@ class BiDi expect(driver.title).to eq('Testing Alerts') end - it 'activates a browser context' do + it 'activates a browser context', + pending_if: {browser: %i[safari safari_preview], reason: 'Safari does not focus the activated context'} do browsing_context = described_class.new(bridge) browsing_context.create diff --git a/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb index 0c6ac78f2b081..ec793e56ded79 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/network_spec.rb @@ -25,20 +25,26 @@ class BiDi describe Network, skip_unless: {bidi: true, reason: 'only executed when bidi is enabled'} do after { |example| reset_driver!(example: example) } - it 'adds an intercept' do + it 'adds an intercept', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]]) expect(intercept).not_to be_nil end - it 'adds an intercept with a default pattern type' do + it 'adds an intercept with a default pattern type', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do 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 - it 'adds an intercept with a url pattern' do + it 'adds an intercept with a url pattern', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) pattern = 'http://localhost:4444/formPage.html' intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]], @@ -47,13 +53,17 @@ class BiDi expect(intercept).not_to be_nil end - it 'removes an intercept' do + it 'removes an intercept', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) intercept = network.add_intercept(phases: [described_class::PHASES[:before_request]]) expect(network.remove_intercept(intercept['intercept'])).to be_empty end - it 'continues with auth' do + it 'continues with auth', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do username = SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first password = SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last network = described_class.new(driver.bidi) @@ -68,7 +78,9 @@ class BiDi expect(driver.find_element(tag_name: 'h1').text).to eq('authorized') end - it 'continues without auth' do + it 'continues without auth', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:auth_required]]) network.on(:auth_required) do |event| @@ -79,7 +91,9 @@ class BiDi expect { driver.navigate.to url_for('basicAuth') }.to raise_error(Error::WebDriverError) end - it 'cancels auth' do + it 'cancels auth', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:auth_required]]) network.on(:auth_required) do |event| @@ -91,7 +105,9 @@ class BiDi expect(driver.find_element(tag_name: 'pre').text).to eq('Login please') end - it 'continues request' do + it 'continues request', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:before_request]]) network.on(:before_request) do |event| @@ -103,7 +119,9 @@ class BiDi expect(driver.find_element(name: 'login')).to be_displayed end - it 'fails request' do + it 'fails request', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:before_request]]) network.on(:before_request) do |event| @@ -114,7 +132,9 @@ class BiDi expect { driver.navigate.to url_for('formPage.html') }.to raise_error(Error::WebDriverError) end - it 'continues response' do + it 'continues response', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:response_started]]) network.on(:response_started) do |event| @@ -126,8 +146,10 @@ class BiDi expect(driver.find_element(name: 'login')).to be_displayed end - it 'provides response', pending_if: {browser: :firefox, - reason: 'https://github.com/w3c/webdriver-bidi/issues/747'} do + it 'provides response', + pending_if: [{browser: :firefox, reason: 'https://github.com/w3c/webdriver-bidi/issues/747'}, + {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'}] do network = described_class.new(driver.bidi) network.add_intercept(phases: [described_class::PHASES[:response_started]]) network.on(:response_started) do |event| diff --git a/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb index 2c1efe21bb56b..fb6c1b419d145 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/script_spec.rb @@ -45,7 +45,9 @@ def a_stack_frame(**options) end end - it 'logs console messages' do + it 'logs console messages', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not deliver BiDi log entries for console messages'} do driver.navigate.to url_for('bidi/logEntryAdded.html') log_entries = [] @@ -80,7 +82,9 @@ def a_stack_frame(**options) ) end - it 'logs multiple console messages' do + it 'logs multiple console messages', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not deliver BiDi log entries for console messages'} do driver.navigate.to url_for('bidi/logEntryAdded.html') log_entries = [] @@ -94,7 +98,9 @@ def a_stack_frame(**options) expect(log_entries.size).to eq(2) end - it 'removes console message handler' do + it 'removes console message handler', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not deliver BiDi log entries for console messages'} do driver.navigate.to url_for('bidi/logEntryAdded.html') log_entries = [] @@ -113,7 +119,9 @@ def a_stack_frame(**options) expect(log_entries.size).to eq(3) end - it 'logs javascript errors' do + it 'logs javascript errors', + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not deliver BiDi log entries for console messages'} do driver.navigate.to url_for('bidi/logEntryAdded.html') log_entries = [] diff --git a/rb/spec/integration/selenium/webdriver/bidi_spec.rb b/rb/spec/integration/selenium/webdriver/bidi_spec.rb index c1721e91487cc..f19e3496f1b99 100644 --- a/rb/spec/integration/selenium/webdriver/bidi_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi_spec.rb @@ -36,7 +36,8 @@ module WebDriver expect(status.message).not_to be_empty end - it 'does not close BiDi session if at least one window is opened' do + it 'does not close BiDi session if at least one window is opened', + pending_if: {browser: %i[safari safari_preview], reason: 'Safari always reports session.status ready: true'} do status = driver.bidi.session.status expect(status.ready).to be false expect(status.message).to be_a String @@ -52,7 +53,8 @@ module WebDriver expect(status_after_closing.message).to be_a String end - it 'closes BiDi session if last window is closed' do + it 'closes BiDi session if last window is closed', + pending_if: {browser: %i[safari safari_preview], reason: 'Safari always reports session.status ready: true'} do status = driver.bidi.session.status expect(status.ready).to be false expect(status.message).to be_a String diff --git a/rb/spec/integration/selenium/webdriver/chrome/BUILD.bazel b/rb/spec/integration/selenium/webdriver/chrome/BUILD.bazel index be70e37f1bfb9..3a9b4760eae25 100644 --- a/rb/spec/integration/selenium/webdriver/chrome/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/chrome/BUILD.bazel @@ -23,5 +23,5 @@ rb_integration_test( "chrome", "chrome-beta", ], - no_grid = True, + grid = False, ) diff --git a/rb/spec/integration/selenium/webdriver/edge/BUILD.bazel b/rb/spec/integration/selenium/webdriver/edge/BUILD.bazel index 5b16850eb1e84..a6269de503d6d 100644 --- a/rb/spec/integration/selenium/webdriver/edge/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/edge/BUILD.bazel @@ -17,5 +17,5 @@ rb_integration_test( name = "service", srcs = ["service_spec.rb"], browsers = ["edge"], - no_grid = True, + grid = False, ) diff --git a/rb/spec/integration/selenium/webdriver/firefox/BUILD.bazel b/rb/spec/integration/selenium/webdriver/firefox/BUILD.bazel index 1bb311b049056..ccddab6d7c900 100644 --- a/rb/spec/integration/selenium/webdriver/firefox/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/firefox/BUILD.bazel @@ -23,5 +23,5 @@ rb_integration_test( "firefox", "firefox-beta", ], - no_grid = True, + grid = False, ) diff --git a/rb/spec/integration/selenium/webdriver/navigation_spec.rb b/rb/spec/integration/selenium/webdriver/navigation_spec.rb index 1f7f5506c4b4b..1b53c0c443988 100644 --- a/rb/spec/integration/selenium/webdriver/navigation_spec.rb +++ b/rb/spec/integration/selenium/webdriver/navigation_spec.rb @@ -21,7 +21,9 @@ module Selenium module WebDriver describe Navigation do - it 'navigates back and forward' do + it 'navigates back and forward', + pending_if: {browser: %i[safari safari_preview], bidi: true, + reason: 'Safari does not support BiDi browsingContext.traverseHistory'} do form_title = 'We Leave From Here' result_title = 'We Arrive Here' form_url = 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 4d1fa3a124565..c0afeb3c39a2a 100644 --- a/rb/spec/integration/selenium/webdriver/network_spec.rb +++ b/rb/spec/integration/selenium/webdriver/network_spec.rb @@ -21,7 +21,9 @@ module Selenium module WebDriver - describe Network, skip_unless: {bidi: true, reason: 'only executed when bidi is enabled'} do + describe Network, skip_unless: {bidi: true, reason: 'only executed when bidi is enabled'}, + pending_if: {browser: %i[safari safari_preview], + reason: 'Safari does not support the BiDi network domain'} do let(:username) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first } let(:password) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last } diff --git a/rb/spec/integration/selenium/webdriver/safari/BUILD.bazel b/rb/spec/integration/selenium/webdriver/safari/BUILD.bazel index 54d03e20334c8..7c17fbfdd0fd3 100644 --- a/rb/spec/integration/selenium/webdriver/safari/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/safari/BUILD.bazel @@ -4,7 +4,6 @@ load("//rb/spec:tests.bzl", "rb_integration_test") rb_integration_test( name = file[:-8], srcs = [file], - # No need to run in other browsers. browsers = [ "safari", "safari-preview", diff --git a/rb/spec/integration/selenium/webdriver/spec_support/test_environment.rb b/rb/spec/integration/selenium/webdriver/spec_support/test_environment.rb index 3eca11f046e6a..7281f8acfbf29 100644 --- a/rb/spec/integration/selenium/webdriver/spec_support/test_environment.rb +++ b/rb/spec/integration/selenium/webdriver/spec_support/test_environment.rb @@ -375,9 +375,10 @@ def ie_options(**opts) WebDriver::Options.ie(**opts) end - def safari_preview_options(**) + def safari_preview_options(**opts) WebDriver::Safari.technology_preview! - WebDriver::Options.safari(**) + opts[:web_socket_url] = true if ENV['WEBDRIVER_BIDI'] && !opts.key?(:web_socket_url) + WebDriver::Options.safari(**opts) end def random_port diff --git a/rb/spec/tests.bzl b/rb/spec/tests.bzl index 03b932b68e754..34306bbdd547e 100644 --- a/rb/spec/tests.bzl +++ b/rb/spec/tests.bzl @@ -135,6 +135,7 @@ BROWSERS = { "deps": ["//rb/lib/selenium/webdriver:ie"], "tags": [], "target_compatible_with": ["@platforms//os:windows"], + "classic": False, "env": { "WD_REMOTE_BROWSER": "ie", "WD_SPEC_DRIVER": "ie", @@ -159,6 +160,8 @@ BROWSERS = { "exclusive-if-local", # Safari cannot run in parallel. ], "target_compatible_with": ["@platforms//os:macos"], + "classic": False, + "bidi": True, "env": { "WD_REMOTE_BROWSER": "safari-preview", "WD_SPEC_DRIVER": "safari-preview", @@ -166,21 +169,14 @@ BROWSERS = { }, } -DEFAULT_BROWSERS = [b for b in BROWSERS.keys() if b not in ("ie", "safari-preview")] - # Tags listed here apply only to the local target of the listed browsers. _BROWSER_TAG_FILTERS = { "os-sensitive": ["chrome", "edge", "firefox", "safari"], "se-manager": ["chrome", "edge", "firefox", "safari"], } -# Input tags that act as control signals (e.g. "bidi" requests a bidi variant). Stripped -# from universal_tags so they don't leak onto local/remote targets — the bidi variant -# emits "bidi" explicitly where it belongs. -_CONTROL_TAGS = ["bidi"] - def _split_filtered_tags(tags, browser): - universal_tags = [t for t in tags if t not in _BROWSER_TAG_FILTERS and t not in _CONTROL_TAGS] + universal_tags = [t for t in tags if t not in _BROWSER_TAG_FILTERS] local_tags = [t for t in tags if browser in _BROWSER_TAG_FILTERS.get(t, [])] return universal_tags, local_tags @@ -189,10 +185,11 @@ def rb_integration_test( srcs, deps = [], data = [], - browsers = DEFAULT_BROWSERS, + browsers = BROWSERS, tags = [], - bidi_only = False, - no_grid = False): + bidi = False, + classic = True, + grid = True): # Generate a library target that is used by //rb/spec:spec to expose all tests to //rb:rubocop. rb_library( name = name, @@ -201,13 +198,17 @@ def rb_integration_test( ) for browser in browsers: + generate_classic = BROWSERS[browser].get("classic", True) + generate_bidi = BROWSERS[browser].get("bidi", False) + universal_tags, local_tags = _split_filtered_tags(tags, browser) # Family groups beta/preview variants with their stable counterpart so # e.g. `--test_tag_filters=chrome` matches chrome and chrome-beta targets. family = browser.split("-")[0] family_tags = [browser, family] if family != browser else [browser] - if not bidi_only: + + if classic and generate_classic: # Generate a test target for local browser execution. rb_test( name = "{}-{}".format(name, browser), @@ -224,7 +225,7 @@ def rb_integration_test( ) # Generate a test target for remote browser execution (Grid). - if not no_grid: + if grid: rb_test( name = "{}-{}-remote".format(name, browser), size = "large", @@ -247,8 +248,7 @@ def rb_integration_test( target_compatible_with = BROWSERS[browser]["target_compatible_with"], ) - # Generate a test target for bidi browser execution on browsers that opt in. - if ("bidi" in tags or bidi_only) and BROWSERS[browser].get("bidi", False): + if bidi and generate_bidi: rb_test( name = "{}-{}-bidi".format(name, browser), size = "large", diff --git a/rb/spec/unit/selenium/webdriver/remote/bidi_bridge_spec.rb b/rb/spec/unit/selenium/webdriver/remote/bidi_bridge_spec.rb new file mode 100644 index 0000000000000..d03039869e7af --- /dev/null +++ b/rb/spec/unit/selenium/webdriver/remote/bidi_bridge_spec.rb @@ -0,0 +1,60 @@ +# 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__) + +module Selenium + module WebDriver + module Remote + describe BiDiBridge do + let(:http) { WebDriver::Remote::Http::Default.new.tap { |client| client.server_url = 'http://localhost' } } + let(:bridge) { described_class.new(http_client: http) } + + def stub_new_session(web_socket_url) + capabilities = {'browserName' => 'safari', 'webSocketUrl' => web_socket_url} + allow(http).to receive(:request) + .and_return('value' => {'sessionId' => 'foo', 'capabilities' => capabilities}) + end + + describe '#create_session' do + it 'raises a clear error when webSocketUrl is returned as a boolean' do + stub_new_session(true) + + expect { bridge.create_session(Capabilities.new) } + .to raise_error(Error::WebDriverError, /did not return a valid webSocketUrl/) + end + + it 'raises a clear error when webSocketUrl is not a ws(s) url' do + stub_new_session('http://localhost:1234/session/foo') + + expect { bridge.create_session(Capabilities.new) } + .to raise_error(Error::WebDriverError, /did not return a valid webSocketUrl/) + end + + it 'quits the remote session when the webSocketUrl is invalid' do + stub_new_session(true) + + expect { bridge.create_session(Capabilities.new) }.to raise_error(Error::WebDriverError) + expect(http).to have_received(:request).at_least(:twice) + end + end + end + end # Remote + end # WebDriver +end # Selenium diff --git a/rb/spec/unit/selenium/webdriver/safari/options_spec.rb b/rb/spec/unit/selenium/webdriver/safari/options_spec.rb index 8d0870ab5b596..a1291ef34530b 100644 --- a/rb/spec/unit/selenium/webdriver/safari/options_spec.rb +++ b/rb/spec/unit/selenium/webdriver/safari/options_spec.rb @@ -56,6 +56,23 @@ module Safari expect(opts.set_window_rect).to be(false) expect(opts.options[:'custom:options']).to eq(foo: 'bar') end + + it 'enables the experimental capability when web socket url is set' do + opts = nil + expect { opts = described_class.new(web_socket_url: true) }.to have_warning(:safari_bidi) + + expect(opts.web_socket_url).to be(true) + expect(opts.experimental_web_socket_url).to be(true) + end + end + + describe '#enable_bidi!' do + it 'enables the experimental capability and warns it is experimental' do + expect { options.enable_bidi! }.to have_warning(:safari_bidi) + + expect(options.web_socket_url).to be(true) + expect(options.experimental_web_socket_url).to be(true) + end end describe '#add_option' do @@ -97,6 +114,14 @@ module Safari 'safari:foo' => 'bar') end + it 'serializes the capabilities enabled by #enable_bidi!' do + expect { options.enable_bidi! }.to have_warning(:safari_bidi) + + expect(options.as_json).to eq('browserName' => 'safari', + 'webSocketUrl' => true, + 'safari:experimentalWebSocketUrl' => true) + end + it 'returns JSON hash' do opts = described_class.new(browser_version: '12', platform_name: 'mac_sierra',