diff --git a/README.md b/README.md index 6c7b77a0..c0404bbe 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ Ferrum::Browser.new(options) * `:proxy` (Hash) - Specify proxy settings, [read more](https://github.com/rubycdp/ferrum#proxy) * `: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 - + * `:mobile` (Boolean) - Specify whether to enable mobile emulation and touch UI. ## Navigation @@ -1089,8 +1089,15 @@ Overrides device screen dimensions and emulates viewport. * :scale_factor `Float`, device scale factor. `0` by default * :mobile `Boolean`, whether to emulate mobile device. `false` by default +Values of `0` for either `:width` or `:height` will be ignored; i.e., no viewport resize will take place. + +If `:mobile` is `true`: + +1. `:height` and `:width` will be ignored, and instead the viewport size of an iPhone 14 will be used (390 x 844). +2. Touch emulation will be enabled, with a maximum of 1 touch point. + ```ruby -page.set_viewport(width: 1000, height: 600, scale_factor: 3) +page.set_viewport(width: 1000, height: 600, scale_factor: 3, mobile: true) ``` diff --git a/lib/ferrum/browser.rb b/lib/ferrum/browser.rb index 12542f9d..4f6d7ed1 100644 --- a/lib/ferrum/browser.rb +++ b/lib/ferrum/browser.rb @@ -129,6 +129,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 diff --git a/lib/ferrum/browser/options.rb b/lib/ferrum/browser/options.rb index 000f826e..52c0874c 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, :ws_url, :env, :process_timeout, :browser_name, :browser_path, :save_path, :proxy, :port, :host, :headless, :incognito, :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) @@ -32,6 +32,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] diff --git a/lib/ferrum/browser/process.rb b/lib/ferrum/browser/process.rb index d68769fc..7b56dbd6 100644 --- a/lib/ferrum/browser/process.rb +++ b/lib/ferrum/browser/process.rb @@ -93,8 +93,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) diff --git a/lib/ferrum/client.rb b/lib/ferrum/client.rb index 1039a475..f4c6b8a9 100644 --- a/lib/ferrum/client.rb +++ b/lib/ferrum/client.rb @@ -64,7 +64,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 @ws_url = ws_url @@ -96,6 +96,9 @@ def send_message(message, async:) raise TimeoutError unless data error, response = data.values_at("error", "result") + if on_synchronous_message + on_synchronous_message.call(message:, error:, response:) + end raise_browser_error(error) if error response end diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index 9abcf918..1960ede1 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -136,7 +136,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). # @@ -149,17 +149,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" } @@ -168,7 +181,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 # diff --git a/spec/page_spec.rb b/spec/page_spec.rb index 42b5c6f2..ab521a7a 100644 --- a/spec/page_spec.rb +++ b/spec/page_spec.rb @@ -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? + 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