diff --git a/rb/.rubocop.yml b/rb/.rubocop.yml index 3ad29a42c56a2..356f43b475b2f 100644 --- a/rb/.rubocop.yml +++ b/rb/.rubocop.yml @@ -35,6 +35,7 @@ Metrics/ClassLength: - 'lib/selenium/webdriver/common/driver.rb' - 'lib/selenium/webdriver/remote/bridge.rb' - 'lib/selenium/webdriver/remote/capabilities.rb' + - 'spec/integration/selenium/webdriver/bidi/log_inspector_spec.rb' - 'spec/integration/selenium/webdriver/spec_support/test_environment.rb' Metrics/CyclomaticComplexity: diff --git a/rb/lib/selenium/webdriver/bidi/log/filter_by.rb b/rb/lib/selenium/webdriver/bidi/log/filter_by.rb new file mode 100644 index 0000000000000..81dc49224c423 --- /dev/null +++ b/rb/lib/selenium/webdriver/bidi/log/filter_by.rb @@ -0,0 +1,40 @@ +# 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 FilterBy + attr_accessor :level + + def initialize(level) + @level = level + end + + def self.log_level(level = nil) + unless %w[debug error info warning].include?(level) + raise Error::WebDriverError, + "Valid log levels are 'debug', 'error', 'info' and 'warning'. Received: #{level}" + end + FilterBy.new(level) + end + end # FilterBy + end # BiDi + end # WebDriver +end # Selenium diff --git a/rb/lib/selenium/webdriver/bidi/log_inspector.rb b/rb/lib/selenium/webdriver/bidi/log_inspector.rb index 6e01627e00a34..76853d785bf97 100644 --- a/rb/lib/selenium/webdriver/bidi/log_inspector.rb +++ b/rb/lib/selenium/webdriver/bidi/log_inspector.rb @@ -23,6 +23,7 @@ require_relative 'log/generic_log_entry' require_relative 'log/console_log_entry' require_relative 'log/javascript_log_entry' +require_relative 'log/filter_by' module Selenium module WebDriver @@ -49,43 +50,41 @@ def initialize(driver, browsing_context_ids = nil) @bidi.session.subscribe('log.entryAdded', browsing_context_ids) end - def on_console_entry(&block) - enabled = log_listeners[:console].any? - log_listeners[:console] << block - return if enabled + def on_console_entry(filter_by = nil, &block) + check_valid_filter(filter_by) on_log do |params| type = params['type'] - console_log_events(params) if type.eql?('console') + console_log_events(params, filter_by, &block) if type.eql?('console') end end - def on_javascript_log(&block) - enabled = log_listeners[:javascript].any? - log_listeners[:javascript] << block - return if enabled + def on_javascript_log(filter_by = nil, &block) + check_valid_filter(filter_by) on_log do |params| type = params['type'] - javascript_log_events(params) if type.eql?('javascript') + javascript_log_events(params, filter_by, &block) if type.eql?('javascript') end end def on_javascript_exception(&block) - enabled = log_listeners[:js_exception].any? - log_listeners[:js_exception] << block - log_listeners[:javascript] << block - return if enabled - on_log do |params| type = params['type'] - level = params['level'] - - javascript_log_events(params) if type.eql?('javascript') && level.eql?(LOG_LEVEL[:ERROR]) + javascript_log_events(params, FilterBy.log_level('error'), &block) if type.eql?('javascript') end end - def on_log(&block) + def on_log(filter_by = nil, &block) + unless filter_by.nil? + check_valid_filter(filter_by) + + on(:entry_added) do |params| + yield(params) if params['level'] == filter_by.level + end + return + end + on(:entry_added, &block) end @@ -96,11 +95,13 @@ def on(event, &block) @bidi.callbacks["log.#{event}"] << block end - def log_listeners - @log_listeners ||= Hash.new { |listeners, kind| listeners[kind] = [] } + def check_valid_filter(filter_by) + return if filter_by.nil? || filter_by.instance_of?(FilterBy) + + raise "Pass valid FilterBy object. Received: #{filter_by.inspect}" end - def console_log_events(params) + def console_log_events(params, filter_by) event = ConsoleLogEntry.new( level: params['level'], text: params['text'], @@ -111,12 +112,16 @@ def console_log_events(params) args: params['args'], stack_trace: params['stackTrace'] ) - log_listeners[:console].each do |listener| - listener.call(event) + + unless filter_by.nil? + yield(event) if params['level'] == filter_by.level + return end + + yield(event) end - def javascript_log_events(params) + def javascript_log_events(params, filter_by) event = JavascriptLogEntry.new( level: params['level'], text: params['text'], @@ -124,15 +129,13 @@ def javascript_log_events(params) type: params['type'], stack_trace: params['stackTrace'] ) - log_listeners[:javascript].each do |listener| - listener.call(event) - end - return unless params['level'].eql?(LOG_LEVEL[:ERROR]) - - log_listeners[:js_exception].each do |listener| - listener.call(event) + unless filter_by.nil? + yield(event) if params['level'] == filter_by.level + return end + + yield(event) end end # LogInspector end # Bidi diff --git a/rb/spec/integration/selenium/webdriver/bidi/log_inspector_spec.rb b/rb/spec/integration/selenium/webdriver/bidi/log_inspector_spec.rb index 06c61508d2942..d546657d6aa5f 100644 --- a/rb/spec/integration/selenium/webdriver/bidi/log_inspector_spec.rb +++ b/rb/spec/integration/selenium/webdriver/bidi/log_inspector_spec.rb @@ -22,18 +22,40 @@ module Selenium module WebDriver class BiDi - describe LogInspector, exclusive: {browser: %i[chrome firefox]} do - before do - @page = '/bidi/logEntryAdded.html' + describe LogInspector, only: {browser: %i[chrome edge firefox]} do + let(:page) { '/bidi/logEntryAdded.html' } + + it 'can listen to console log' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_console_entry { |log| log_entry = log } + + driver.navigate.to url_for(page) + driver.find_element(id: 'consoleLog').click + wait.until { !log_entry.nil? } + + expect(log_entry).to have_attributes( + text: 'Hello, world!', + realm: nil, + type: 'console', + level: LogInspector::LOG_LEVEL[:INFO], + method: 'log' + ) + expect(log_entry.args.size).to eq(1) + end end - it 'can listen to console log', except: {browser: :chrome} do + it 'can listen to console log with different consumers' do reset_driver!(web_socket_url: true) do |driver| log_entry = nil log_inspector = described_class.new(driver) log_inspector.on_console_entry { |log| log_entry = log } - driver.navigate.to url_for(@page) + log_entry_text = nil + log_inspector.on_console_entry { |log| log_entry_text = log.text } + + driver.navigate.to url_for(page) driver.find_element(id: 'consoleLog').click wait.until { !log_entry.nil? } @@ -42,10 +64,46 @@ class BiDi realm: nil, type: 'console', level: LogInspector::LOG_LEVEL[:INFO], - method: 'log', - stack_trace: nil + method: 'log' ) expect(log_entry.args.size).to eq(1) + expect(log_entry_text).to eq('Hello, world!') + end + end + + it 'can filter console info level log' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_console_entry(FilterBy.log_level('info')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + driver.find_element(id: 'consoleLog').click + wait.until { !log_entry.nil? } + + expect(log_entry).to have_attributes( + text: 'Hello, world!', + realm: nil, + type: 'console', + level: LogInspector::LOG_LEVEL[:INFO], + method: 'log' + ) + expect(log_entry.args.size).to eq(1) + end + end + + it 'can filter console log' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_console_entry(FilterBy.log_level('error')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + # Generating info level log but we are filtering by error level + wait.until { driver.find_element(id: 'consoleLog').displayed? } + driver.find_element(id: 'consoleLog').click + + expect(log_entry).to be_nil end end @@ -55,7 +113,7 @@ class BiDi log_inspector = described_class.new(driver) log_inspector.on_javascript_log { |log| log_entry = log } - driver.navigate.to url_for(@page) + driver.navigate.to url_for(page) driver.find_element(id: 'jsException').click wait.until { !log_entry.nil? } @@ -67,13 +125,46 @@ class BiDi end end + it 'can filter javascript log at error level' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_javascript_log(FilterBy.log_level('error')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + driver.find_element(id: 'jsException').click + wait.until { !log_entry.nil? } + + expect(log_entry).to have_attributes( + text: 'Error: Not working', + type: 'javascript', + level: LogInspector::LOG_LEVEL[:ERROR] + ) + end + end + + it 'can filter javascript log' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_javascript_log(FilterBy.log_level('info')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + # Generating js error level log but we are filtering by info level + wait.until { driver.find_element(id: 'jsException').displayed? } + driver.find_element(id: 'jsException').click + + expect(log_entry).to be_nil + end + end + it 'can listen to javascript error log' do reset_driver!(web_socket_url: true) do |driver| log_entry = nil log_inspector = described_class.new(driver) log_inspector.on_javascript_exception { |log| log_entry = log } - driver.navigate.to url_for(@page) + driver.navigate.to url_for(page) driver.find_element(id: 'jsException').click wait.until { !log_entry.nil? } @@ -91,7 +182,7 @@ class BiDi log_inspector = described_class.new(driver) log_inspector.on_log { |log| log_entry = log } - driver.navigate.to url_for(@page) + driver.navigate.to url_for(page) driver.find_element(id: 'consoleError').click wait.until { !log_entry.nil? } @@ -101,20 +192,54 @@ class BiDi end end - it 'can retrieve stack trace for a log', except: {browser: :chrome} do + it 'can filter any log' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_log(FilterBy.log_level('info')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + driver.find_element(id: 'consoleLog').click + wait.until { !log_entry.nil? } + + expect(log_entry['text']).to eq('Hello, world!') + expect(log_entry['realm']).to be_nil + expect(log_entry['type']).to eq('console') + expect(log_entry['level']).to eq('info') + expect(log_entry['method']).to eq('log') + expect(log_entry['args'].size).to eq(1) + end + end + + it 'can filter any log at error level' do + reset_driver!(web_socket_url: true) do |driver| + log_entry = nil + log_inspector = described_class.new(driver) + log_inspector.on_log(FilterBy.log_level('error')) { |log| log_entry = log } + + driver.navigate.to url_for(page) + driver.find_element(id: 'jsException').click + wait.until { !log_entry.nil? } + + expect(log_entry['text']).to eq('Error: Not working') + expect(log_entry['type']).to eq('javascript') + expect(log_entry['level']).to eq('error') + end + end + + it 'can retrieve stack trace for a log' do reset_driver!(web_socket_url: true) do |driver| log_entry = nil log_inspector = described_class.new(driver) log_inspector.on_javascript_log { |log| log_entry = log } - driver.navigate.to url_for(@page) + driver.navigate.to url_for(page) driver.find_element(id: 'jsException').click wait.until { !log_entry.nil? } stack_trace = log_entry.stack_trace expect(stack_trace).not_to be_nil - expect(stack_trace['callFrames'].size).to eq(3) end end end