Skip to content
9 changes: 9 additions & 0 deletions docs/2-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ Ferrum::Browser.new(options)
* `:save_path` (String) - Path to save attachments with [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header.
* `:env` (Hash) - Environment variables you'd like to pass through to the process

You can emulate specific devices at the time you register a driver:

```ruby
require "capybara/cuprite/devices"
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, Capybara::Cuprite::Devices::IPHONE_14)
end
```

## Examples

```ruby
Expand Down
2 changes: 2 additions & 0 deletions lib/ferrum/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class Browser
# @option options [Hash] :env
# Environment variables you'd like to pass through to the process.
#
# @option options [Boolean] :mobile
# Specify whether to enable mobile emulation and touch UI.
def initialize(options = nil)
@options = Options.new(options)
@client = @process = @contexts = nil
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, :ws_url, :env, :process_timeout, :browser_name, :browser_path,
:save_path, :proxy, :port, :host, :headless, :incognito, :dockerize, :browser_options,
:ignore_default_browser_options, :xvfb, :flatten
:ignore_default_browser_options, :xvfb, :flatten, :mobile
attr_accessor :timeout, :default_user_agent

def initialize(options = nil)
Expand All @@ -33,6 +33,7 @@ def initialize(options = nil)
@pending_connection_errors = @options.fetch(:pending_connection_errors, true)
@process_timeout = @options.fetch(:process_timeout, PROCESS_TIMEOUT)
@slowmo = @options[:slowmo].to_f
@mobile = @options.fetch(:mobile, false)

@env = @options[:env]
@xvfb = @options[:xvfb]
Expand Down
8 changes: 6 additions & 2 deletions lib/ferrum/browser/process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ def start
process_options[:out] = process_options[:err] = write_io

if @command.xvfb?
@xvfb = Xvfb.start(@command.options)
ObjectSpace.define_finalizer(self, self.class.process_killer(@xvfb.pid))
if @command.options.xvfb.respond_to?(:start)
@xvfb = @command.options.xvfb.start(@command.options)
else
@xvfb = Xvfb.start(@command.options)
ObjectSpace.define_finalizer(self, self.class.process_killer(@xvfb.pid))
end
end

env = Hash(@xvfb&.to_env).merge(@env)
Expand Down
2 changes: 2 additions & 0 deletions lib/ferrum/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Client
delegate %i[timeout timeout=] => :options

attr_reader :ws_url, :options, :subscriber
attr_accessor :on_synchronous_message

def initialize(ws_url, options)
@command_id = 0
Expand Down Expand Up @@ -97,6 +98,7 @@ def send_message(message, async:)
raise TimeoutError unless data

error, response = data.values_at("error", "result")
on_synchronous_message&.call(message:, error:, response:)
raise_browser_error(error) if error
response
end
Expand Down
27 changes: 20 additions & 7 deletions lib/ferrum/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def close_connection
end

#
# Overrides device screen dimensions and emulates viewport according to parameters
# Overrides device screen dimensions and emulates viewport according to parameters.
#
# Read more [here](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride).
#
Expand All @@ -150,17 +150,30 @@ def close_connection
# @param [Boolean] mobile whether to emulate mobile device
#
def set_viewport(width:, height:, scale_factor: 0, mobile: false)
if mobile
command(
"Emulation.setTouchEmulationEnabled",
enabled: true,
maxTouchPoints: 1
)
else
command(
"Emulation.setTouchEmulationEnabled",
enabled: false
)
end

command(
"Emulation.setDeviceMetricsOverride",
slowmoable: true,
width: width,
height: height,
deviceScaleFactor: scale_factor,
mobile: mobile
height: height,
mobile: mobile,
slowmoable: true,
width: width
)
end

def resize(width: nil, height: nil, fullscreen: false)
def resize(width: nil, height: nil, fullscreen: false, mobile: false)
if fullscreen
width, height = document_size
self.window_bounds = { window_state: "fullscreen" }
Expand All @@ -169,7 +182,7 @@ def resize(width: nil, height: nil, fullscreen: false)
self.window_bounds = { width: width, height: height }
end

set_viewport(width: width, height: height)
set_viewport(width: width, height: height, mobile: mobile)
end

#
Expand Down
44 changes: 44 additions & 0 deletions spec/page_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,48 @@
wait_for { message_b }.to eq("goodbye")
end
end

describe "#resize" do
def body_size
{
height: page.evaluate("document.body.clientHeight"),
width: page.evaluate("document.body.clientWidth")
}
end

def is_mobile?
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't really test whether mobile emulation is enabled, but I couldn't find a good way of doing that directly via JavaScript evaluation. So we use touch emulation as a proxy.

page.evaluate("'ontouchstart' in window || navigator.maxTouchPoints > 0")
end

before do
page.go_to("/")
end

context "given a different size" do
it "resizes the page" do
expect { page.resize(width: 2000, height: 1000) }.to change { body_size }.to(width: 2000, height: 1000)
end
end

context "given a zero height" do
it "does not change the height" do
expect { page.resize(width: 2000, height: 0) }.not_to(change { body_size[:height] })
end
end

context "given a zero width" do
it "does not change the width" do
expect { page.resize(width: 0, height: 1000) }.not_to(change { body_size[:width] })
end
end

context "when mobile is true" do
it "enables mobile emulation in the browser" do
expect do
page.resize(width: 0, height: 0, mobile: true)
page.reload
end.to change { is_mobile? }.to(true)
end
end
end
end
Loading