From 8e3e929e60630b1fbffd6e073eba0435eea17056 Mon Sep 17 00:00:00 2001 From: Dmitry Vorotilin Date: Fri, 5 Jan 2024 18:54:43 +0300 Subject: [PATCH] feat: flatten --- CHANGELOG.md | 5 ++-- lib/ferrum/browser.rb | 3 +++ lib/ferrum/browser/options.rb | 3 ++- lib/ferrum/client.rb | 42 ++++++++++++++++++++++++++------- lib/ferrum/client/subscriber.rb | 8 ++++--- lib/ferrum/context.rb | 4 ++-- lib/ferrum/contexts.rb | 18 +++++++++++++- lib/ferrum/page.rb | 17 ++++++------- lib/ferrum/target.rb | 19 +++++++++------ 9 files changed, 86 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc210a71..0979a82b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -533,9 +533,8 @@ to `Ferrum::Browser#default_context` ### Fixed ### Removed -- `Ferrum::EmptyTargetsError` -- the `hack` to handle `new window` which doesn't have events at all by `Ferrum::Page#session_id` with -`Target.attachToTarget` and `Target.detachFromTarget` usage +- `Ferrum::EmptyTargetsError` the hack to handle `new window` which doesn't have events at all by +`Ferrum::Page#session_id` with `Target.attachToTarget` and `Target.detachFromTarget` usage - `Ferrum::Page#close_connection` - the logic is moved to `Ferrum::Page#close` directly - the third argument (`new_window = false`) for `Ferrum::Page` initializer - `Ferrum::Targets` class with the delegations to `Ferrum::Targets` instance in `Ferrum::Browser` instance: diff --git a/lib/ferrum/browser.rb b/lib/ferrum/browser.rb index e1186472..17fb6057 100644 --- a/lib/ferrum/browser.rb +++ b/lib/ferrum/browser.rb @@ -47,6 +47,9 @@ class Browser # @option options [Boolean] :xvfb (false) # Run browser in a virtual framebuffer. # + # @option options [Boolean] :flatten (true) + # Use one websocket connection to the browser and all the pages in flatten mode. + # # @option options [(Integer, Integer)] :window_size ([1024, 768]) # The dimensions of the browser window in which to test, expressed as a # 2-element array, e.g. `[1024, 768]`. diff --git a/lib/ferrum/browser/options.rb b/lib/ferrum/browser/options.rb index 6f1c9acd..06a7cc17 100644 --- a/lib/ferrum/browser/options.rb +++ b/lib/ferrum/browser/options.rb @@ -15,7 +15,7 @@ class Options :js_errors, :base_url, :slowmo, :pending_connection_errors, :url, :env, :process_timeout, :browser_name, :browser_path, :save_path, :proxy, :port, :host, :headless, :browser_options, - :ignore_default_browser_options, :xvfb + :ignore_default_browser_options, :xvfb, :flatten attr_accessor :timeout, :ws_url, :default_user_agent def initialize(options = nil) @@ -27,6 +27,7 @@ def initialize(options = nil) @window_size = @options.fetch(:window_size, WINDOW_SIZE) @js_errors = @options.fetch(:js_errors, false) @headless = @options.fetch(:headless, true) + @flatten = @options.fetch(:flatten, true) @pending_connection_errors = @options.fetch(:pending_connection_errors, true) @process_timeout = @options.fetch(:process_timeout, PROCESS_TIMEOUT) @slowmo = @options[:slowmo].to_f diff --git a/lib/ferrum/client.rb b/lib/ferrum/client.rb index 37afa112..52c4bacc 100644 --- a/lib/ferrum/client.rb +++ b/lib/ferrum/client.rb @@ -5,6 +5,31 @@ require "ferrum/client/web_socket" module Ferrum + class SessionClient + attr_reader :client, :session_id + + def initialize(client, session_id) + @client = client + @session_id = session_id + end + + def command(method, async: false, **params) + @client.command(method, async: async, session_id: session_id, **params) + end + + def on(event, &block) + @client.on([event, session_id].compact.join("_"), &block) + end + + def respond_to_missing?(name, include_private) + @client.respond_to?(name, include_private) + end + + def method_missing(name, ...) + @client.send(name, ...) + end + end + class Client extend Forwardable delegate %i[timeout timeout=] => :options @@ -21,8 +46,8 @@ def initialize(ws_url, options) start end - def command(method, async: false, **params) - message = build_message(method, params) + def command(method, async: false, session_id: nil, **params) + message = build_message(method, session_id, params) if async @ws.send_message(message) @@ -43,12 +68,12 @@ def command(method, async: false, **params) end end - def on(event, &block) - @subscriber.on(event, &block) + def on(event, session_id = nil, &block) + @subscriber.on([event, session_id].compact.join("_"), &block) end - def subscribed?(event) - @subscriber.subscribed?(event) + def subscribed?(event, session_id = nil) + @subscriber.subscribed?([event, session_id].compact.join("_")) end def close @@ -83,8 +108,9 @@ def start end end - def build_message(method, params) - { method: method, params: params }.merge(id: next_command_id) + def build_message(method, session_id, params) + message = { method: method, params: params }.merge(id: next_command_id) + session_id ? message.merge(sessionId: session_id) : message end def next_command_id diff --git a/lib/ferrum/client/subscriber.rb b/lib/ferrum/client/subscriber.rb index 7fc52a64..2d47c219 100644 --- a/lib/ferrum/client/subscriber.rb +++ b/lib/ferrum/client/subscriber.rb @@ -58,9 +58,11 @@ def start end def call(message) - method, params = message.values_at("method", "params") - total = @on[method].size - @on[method].each_with_index do |block, index| + method, session_id, params = message.values_at("method", "sessionId", "params") + event = [method, session_id].compact.join("_") + + total = @on[event].size + @on[event].each_with_index do |block, index| # In case of multiple callbacks we provide current index and total block.call(params, index, total) end diff --git a/lib/ferrum/context.rb b/lib/ferrum/context.rb index e0628017..6a252fcd 100644 --- a/lib/ferrum/context.rb +++ b/lib/ferrum/context.rb @@ -53,8 +53,8 @@ def create_target target end - def add_target(params) - new_target = Target.new(@client, params) + def add_target(params:, session_id: nil) + new_target = Target.new(@client, session_id, params) target = @targets.put_if_absent(new_target.id, new_target) target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already @pendings.put(target, @client.timeout) if @pendings.empty? diff --git a/lib/ferrum/contexts.rb b/lib/ferrum/contexts.rb index 9753c0e7..59b971df 100644 --- a/lib/ferrum/contexts.rb +++ b/lib/ferrum/contexts.rb @@ -12,6 +12,7 @@ def initialize(client) @contexts = Concurrent::Map.new @client = client subscribe + auto_attach discover end @@ -67,12 +68,21 @@ def size private def subscribe + @client.on("Target.attachedToTarget") do |params| + info, session_id = params.values_at("targetInfo", "sessionId") + next unless info["type"] == "page" + + context_id = info["browserContextId"] + @contexts[context_id]&.add_target(session_id: session_id, params: info) + @client.command("Runtime.runIfWaitingForDebugger", async: true, session_id: session_id) if params["waitingForDebugger"] + end + @client.on("Target.targetCreated") do |params| info = params["targetInfo"] next unless info["type"] == "page" context_id = info["browserContextId"] - @contexts[context_id]&.add_target(info) + @contexts[context_id]&.add_target(params: info) end @client.on("Target.targetInfoChanged") do |params| @@ -97,5 +107,11 @@ def subscribe def discover @client.command("Target.setDiscoverTargets", discover: true) end + + def auto_attach + return unless @client.options.flatten + + @client.command("Target.setAutoAttach", autoAttach: true, waitForDebuggerOnStart: true, flatten: true) + end end end diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index 8f689ae4..ec7044bf 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -32,7 +32,7 @@ class Page include Stream attr_accessor :referrer - attr_reader :context_id, :target_id, :event, :tracing + attr_reader :context_id, :target_id, :session_id, :event, :tracing # Client connection. # @@ -69,10 +69,11 @@ class Page # @return [Downloads] attr_reader :downloads - def initialize(client, context_id:, target_id:, proxy: nil) + def initialize(client, context_id:, target_id:, session_id: nil, proxy: nil) @client = client @context_id = context_id @target_id = target_id + @session_id = session_id @options = client.options @frames = Concurrent::Map.new @@ -339,7 +340,7 @@ def bypass_csp(enabled: true) def command(method, wait: 0, slowmoable: false, **params) iteration = @event.reset if wait.positive? sleep(@options.slowmo) if slowmoable && @options.slowmo.positive? - result = client.command(method, **params) + result = client.command(method, session_id: session_id, **params) if wait.positive? # Wait a bit after command and check if iteration has @@ -357,12 +358,12 @@ def command(method, wait: 0, slowmoable: false, **params) def on(name, &block) case name when :dialog - client.on("Page.javascriptDialogOpening") do |params, index, total| + client.on("Page.javascriptDialogOpening", session_id) do |params, index, total| dialog = Dialog.new(self, params) block.call(dialog, index, total) end when :request - client.on("Fetch.requestPaused") do |params, index, total| + client.on("Fetch.requestPaused", session_id) do |params, index, total| request = Network::InterceptedRequest.new(client, params) exchange = network.select(request.network_id).last exchange ||= network.build_exchange(request.network_id) @@ -370,17 +371,17 @@ def on(name, &block) block.call(request, index, total) end when :auth - client.on("Fetch.authRequired") do |params, index, total| + client.on("Fetch.authRequired", session_id) do |params, index, total| request = Network::AuthRequest.new(self, params) block.call(request, index, total) end else - client.on(name, &block) + client.on(name, session_id, &block) end end def subscribed?(event) - client.subscribed?(event) + client.subscribed?(event, session_id) end def use_proxy? diff --git a/lib/ferrum/target.rb b/lib/ferrum/target.rb index 8dfc8af2..cd1921ba 100644 --- a/lib/ferrum/target.rb +++ b/lib/ferrum/target.rb @@ -8,14 +8,17 @@ class Target # where we enhance page class and build page ourselves. attr_writer :page - def initialize(client, params = nil) + attr_reader :session_id + + def initialize(client, session_id = nil, params = nil) @page = nil @client = client + @session_id = session_id @params = params end def update(params) - @params = params + @params.merge!(params) end def attached? @@ -28,7 +31,11 @@ def page def build_page(**options) maybe_sleep_if_new_window - Page.new(build_client, context_id: context_id, target_id: id, **options) + Page.new(build_client, context_id: context_id, target_id: id, session_id: session_id, **options) + end + + def context_id + @params["browserContextId"] end def id @@ -51,10 +58,6 @@ def opener_id @params["openerId"] end - def context_id - @params["browserContextId"] - end - def window? !!opener_id end @@ -68,6 +71,8 @@ def maybe_sleep_if_new_window def build_client options = @client.options + return SessionClient.new(@client, session_id) if options.flatten + ws_url = options.ws_url.merge(path: "/devtools/page/#{id}").to_s Client.new(ws_url, options) end