Skip to content

Commit ec912a1

Browse files
authored
feat: Flatten mode (#434)
* feat: Flatten mode * chore: Add README entry
1 parent d3be6ff commit ec912a1

File tree

9 files changed

+101
-15
lines changed

9 files changed

+101
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- `#wait` wait for file download to be completed
99
- `#set_behavior` where and whether to store file
1010
- `Browser::Client#command` accepts :async parameter [#433]
11+
- `Ferrum::Browser` introduce `:flatten` mode with one connection and sessions [#434]
1112

1213
### Changed
1314
- `Ferrum::Page#screeshot` accepts :area option [#410]
@@ -533,9 +534,8 @@ to `Ferrum::Browser#default_context`
533534
### Fixed
534535

535536
### Removed
536-
- `Ferrum::EmptyTargetsError`
537-
- the `hack` to handle `new window` which doesn't have events at all by `Ferrum::Page#session_id` with
538-
`Target.attachToTarget` and `Target.detachFromTarget` usage
537+
- `Ferrum::EmptyTargetsError` the hack to handle `new window` which doesn't have events at all by
538+
`Ferrum::Page#session_id` with `Target.attachToTarget` and `Target.detachFromTarget` usage
539539
- `Ferrum::Page#close_connection` - the logic is moved to `Ferrum::Page#close` directly
540540
- the third argument (`new_window = false`) for `Ferrum::Page` initializer
541541
- `Ferrum::Targets` class with the delegations to `Ferrum::Targets` instance in `Ferrum::Browser` instance:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Ferrum::Browser.new(options)
156156
* `:headless` (String | Boolean) - Set browser as headless or not, `true` by default. You can set `"new"` to support
157157
[new headless mode](https://developer.chrome.com/articles/new-headless/).
158158
* `:xvfb` (Boolean) - Run browser in a virtual framebuffer, `false` by default.
159+
* `:flatten` (Boolean) - Use one websocket connection to the browser and all the pages in flatten mode.
159160
* `:window_size` (Array) - The dimensions of the browser window in which to
160161
test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
161162
* `:extensions` (Array[String | Hash]) - An array of paths to files or JS

lib/ferrum/browser.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class Browser
4747
# @option options [Boolean] :xvfb (false)
4848
# Run browser in a virtual framebuffer.
4949
#
50+
# @option options [Boolean] :flatten (true)
51+
# Use one websocket connection to the browser and all the pages in flatten mode.
52+
#
5053
# @option options [(Integer, Integer)] :window_size ([1024, 768])
5154
# The dimensions of the browser window in which to test, expressed as a
5255
# 2-element array, e.g. `[1024, 768]`.

lib/ferrum/browser/options.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Options
1515
:js_errors, :base_url, :slowmo, :pending_connection_errors,
1616
:url, :env, :process_timeout, :browser_name, :browser_path,
1717
:save_path, :proxy, :port, :host, :headless, :browser_options,
18-
:ignore_default_browser_options, :xvfb
18+
:ignore_default_browser_options, :xvfb, :flatten
1919
attr_accessor :timeout, :ws_url, :default_user_agent
2020

2121
def initialize(options = nil)
@@ -27,6 +27,7 @@ def initialize(options = nil)
2727
@window_size = @options.fetch(:window_size, WINDOW_SIZE)
2828
@js_errors = @options.fetch(:js_errors, false)
2929
@headless = @options.fetch(:headless, true)
30+
@flatten = @options.fetch(:flatten, true)
3031
@pending_connection_errors = @options.fetch(:pending_connection_errors, true)
3132
@process_timeout = @options.fetch(:process_timeout, PROCESS_TIMEOUT)
3233
@slowmo = @options[:slowmo].to_f

lib/ferrum/client.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,59 @@
55
require "ferrum/client/web_socket"
66

77
module Ferrum
8+
class SessionClient
9+
attr_reader :client, :session_id
10+
11+
def self.event_name(event, session_id)
12+
[event, session_id].compact.join("_")
13+
end
14+
15+
def initialize(client, session_id)
16+
@client = client
17+
@session_id = session_id
18+
end
19+
20+
def command(method, async: false, **params)
21+
message = build_message(method, params)
22+
@client.send_message(message, async: async)
23+
end
24+
25+
def on(event, &block)
26+
@client.on(event_name(event), &block)
27+
end
28+
29+
def subscribed?(event)
30+
@client.subscribed?(event_name(event))
31+
end
32+
33+
def respond_to_missing?(name, include_private)
34+
@client.respond_to?(name, include_private)
35+
end
36+
37+
def method_missing(name, ...)
38+
@client.send(name, ...)
39+
end
40+
41+
def close
42+
@client.subscriber.clear(session_id: session_id)
43+
end
44+
45+
private
46+
47+
def build_message(method, params)
48+
@client.build_message(method, params).merge(sessionId: session_id)
49+
end
50+
51+
def event_name(event)
52+
self.class.event_name(event, session_id)
53+
end
54+
end
55+
856
class Client
957
extend Forwardable
1058
delegate %i[timeout timeout=] => :options
1159

12-
attr_reader :options
60+
attr_reader :options, :subscriber
1361

1462
def initialize(ws_url, options)
1563
@command_id = 0
@@ -54,6 +102,10 @@ def subscribed?(event)
54102
@subscriber.subscribed?(event)
55103
end
56104

105+
def session(session_id)
106+
SessionClient.new(self, session_id)
107+
end
108+
57109
def close
58110
@ws.close
59111
# Give a thread some time to handle a tail of messages

lib/ferrum/client/subscriber.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ def close
3535
@priority_thread&.kill
3636
end
3737

38+
def clear(session_id:)
39+
@on.delete_if { |k, _| k.match?(session_id) }
40+
end
41+
3842
private
3943

4044
def start
@@ -58,9 +62,11 @@ def start
5862
end
5963

6064
def call(message)
61-
method, params = message.values_at("method", "params")
62-
total = @on[method].size
63-
@on[method].each_with_index do |block, index|
65+
method, session_id, params = message.values_at("method", "sessionId", "params")
66+
event = SessionClient.event_name(method, session_id)
67+
68+
total = @on[event].size
69+
@on[event].each_with_index do |block, index|
6470
# In case of multiple callbacks we provide current index and total
6571
block.call(params, index, total)
6672
end

lib/ferrum/context.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ def create_target
5353
target
5454
end
5555

56-
def add_target(params)
57-
new_target = Target.new(@client, params)
56+
def add_target(params:, session_id: nil)
57+
new_target = Target.new(@client, session_id, params)
5858
target = @targets.put_if_absent(new_target.id, new_target)
5959
target ||= new_target # `put_if_absent` returns nil if added a new value or existing if there was one already
6060
@pendings.put(target, @client.timeout) if @pendings.empty?
@@ -71,7 +71,7 @@ def delete_target(target_id)
7171

7272
def close_targets_connection
7373
@targets.each_value do |target|
74-
next unless target.attached?
74+
next unless target.connected?
7575

7676
target.page.close_connection
7777
end

lib/ferrum/contexts.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def initialize(client)
1212
@contexts = Concurrent::Map.new
1313
@client = client
1414
subscribe
15+
auto_attach
1516
discover
1617
end
1718

@@ -67,12 +68,23 @@ def size
6768
private
6869

6970
def subscribe
71+
@client.on("Target.attachedToTarget") do |params|
72+
info, session_id = params.values_at("targetInfo", "sessionId")
73+
next unless info["type"] == "page"
74+
75+
context_id = info["browserContextId"]
76+
@contexts[context_id]&.add_target(session_id: session_id, params: info)
77+
if params["waitingForDebugger"]
78+
@client.session(session_id).command("Runtime.runIfWaitingForDebugger", async: true)
79+
end
80+
end
81+
7082
@client.on("Target.targetCreated") do |params|
7183
info = params["targetInfo"]
7284
next unless info["type"] == "page"
7385

7486
context_id = info["browserContextId"]
75-
@contexts[context_id]&.add_target(info)
87+
@contexts[context_id]&.add_target(params: info)
7688
end
7789

7890
@client.on("Target.targetInfoChanged") do |params|
@@ -97,5 +109,11 @@ def subscribe
97109
def discover
98110
@client.command("Target.setDiscoverTargets", discover: true)
99111
end
112+
113+
def auto_attach
114+
return unless @client.options.flatten
115+
116+
@client.command("Target.setAutoAttach", autoAttach: true, waitForDebuggerOnStart: true, flatten: true)
117+
end
100118
end
101119
end

lib/ferrum/target.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ class Target
88
# where we enhance page class and build page ourselves.
99
attr_writer :page
1010

11-
def initialize(client, params = nil)
11+
attr_reader :session_id
12+
13+
def initialize(client, session_id = nil, params = nil)
1214
@page = nil
1315
@client = client
16+
@session_id = session_id
1417
@params = params
1518
end
1619

1720
def update(params)
18-
@params = params
21+
@params.merge!(params)
1922
end
2023

21-
def attached?
24+
def connected?
2225
!!@page
2326
end
2427

@@ -68,6 +71,8 @@ def maybe_sleep_if_new_window
6871

6972
def build_client
7073
options = @client.options
74+
return @client.session(session_id) if options.flatten
75+
7176
ws_url = options.ws_url.merge(path: "/devtools/page/#{id}").to_s
7277
Client.new(ws_url, options)
7378
end

0 commit comments

Comments
 (0)