Skip to content

Commit

Permalink
feat: Flatten mode (#434)
Browse files Browse the repository at this point in the history
* feat: Flatten mode

* chore: Add README entry
  • Loading branch information
route authored Jan 6, 2024
1 parent d3be6ff commit ec912a1
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 15 deletions.
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `#wait` wait for file download to be completed
- `#set_behavior` where and whether to store file
- `Browser::Client#command` accepts :async parameter [#433]
- `Ferrum::Browser` introduce `:flatten` mode with one connection and sessions [#434]

### Changed
- `Ferrum::Page#screeshot` accepts :area option [#410]
Expand Down Expand Up @@ -533,9 +534,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:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ Ferrum::Browser.new(options)
* `:headless` (String | Boolean) - Set browser as headless or not, `true` by default. You can set `"new"` to support
[new headless mode](https://developer.chrome.com/articles/new-headless/).
* `:xvfb` (Boolean) - Run browser in a virtual framebuffer, `false` by default.
* `:flatten` (Boolean) - Use one websocket connection to the browser and all the pages in flatten mode.
* `:window_size` (Array) - The dimensions of the browser window in which to
test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
* `:extensions` (Array[String | Hash]) - An array of paths to files or JS
Expand Down
3 changes: 3 additions & 0 deletions lib/ferrum/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]`.
Expand Down
3 changes: 2 additions & 1 deletion lib/ferrum/browser/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
54 changes: 53 additions & 1 deletion lib/ferrum/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,59 @@
require "ferrum/client/web_socket"

module Ferrum
class SessionClient
attr_reader :client, :session_id

def self.event_name(event, session_id)
[event, session_id].compact.join("_")
end

def initialize(client, session_id)
@client = client
@session_id = session_id
end

def command(method, async: false, **params)
message = build_message(method, params)
@client.send_message(message, async: async)
end

def on(event, &block)
@client.on(event_name(event), &block)
end

def subscribed?(event)
@client.subscribed?(event_name(event))
end

def respond_to_missing?(name, include_private)
@client.respond_to?(name, include_private)
end

def method_missing(name, ...)
@client.send(name, ...)
end

def close
@client.subscriber.clear(session_id: session_id)
end

private

def build_message(method, params)
@client.build_message(method, params).merge(sessionId: session_id)
end

def event_name(event)
self.class.event_name(event, session_id)
end
end

class Client
extend Forwardable
delegate %i[timeout timeout=] => :options

attr_reader :options
attr_reader :options, :subscriber

def initialize(ws_url, options)
@command_id = 0
Expand Down Expand Up @@ -54,6 +102,10 @@ def subscribed?(event)
@subscriber.subscribed?(event)
end

def session(session_id)
SessionClient.new(self, session_id)
end

def close
@ws.close
# Give a thread some time to handle a tail of messages
Expand Down
12 changes: 9 additions & 3 deletions lib/ferrum/client/subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def close
@priority_thread&.kill
end

def clear(session_id:)
@on.delete_if { |k, _| k.match?(session_id) }
end

private

def start
Expand All @@ -58,9 +62,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 = SessionClient.event_name(method, session_id)

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
Expand Down
6 changes: 3 additions & 3 deletions lib/ferrum/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -71,7 +71,7 @@ def delete_target(target_id)

def close_targets_connection
@targets.each_value do |target|
next unless target.attached?
next unless target.connected?

target.page.close_connection
end
Expand Down
20 changes: 19 additions & 1 deletion lib/ferrum/contexts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def initialize(client)
@contexts = Concurrent::Map.new
@client = client
subscribe
auto_attach
discover
end

Expand Down Expand Up @@ -67,12 +68,23 @@ 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)
if params["waitingForDebugger"]
@client.session(session_id).command("Runtime.runIfWaitingForDebugger", async: true)
end
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|
Expand All @@ -97,5 +109,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
11 changes: 8 additions & 3 deletions lib/ferrum/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ 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?
def connected?
!!@page
end

Expand Down Expand Up @@ -68,6 +71,8 @@ def maybe_sleep_if_new_window

def build_client
options = @client.options
return @client.session(session_id) if options.flatten

ws_url = options.ws_url.merge(path: "/devtools/page/#{id}").to_s
Client.new(ws_url, options)
end
Expand Down

0 comments on commit ec912a1

Please sign in to comment.