From dd15f1adbe7af10ee2edc8dce0e8f610885b0d21 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Thu, 20 Mar 2025 14:32:47 +1100 Subject: [PATCH 01/12] Add ruby-lsp as an optional dependency This provides an LSP interface to the codebase, allowing for easier navigation, searching, etc. in editors that support LSP. --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index dc702f11..a4edf99e 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ gem "rspec", "~> 3.8" gem "rspec-wait" gem "rubocop", "~> 1.22" gem "rubocop-rake", require: false +gem "ruby-lsp", require: false gem "sinatra", "~> 3.2" gem "yard", "~> 0.9", require: false From 42184d615e0251c8e53e20578d0bba8ae663abc9 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Thu, 20 Mar 2025 16:15:31 +1100 Subject: [PATCH 02/12] Add debug Gem This will make debugging rspec examples easier. --- Gemfile | 1 + spec/spec_helper.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index a4edf99e..cee4a7e2 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "byebug", "~> 11.0", platforms: %i[mri mingw x64_mingw] gem "chunky_png", "~> 1.3" +gem "debug", "~> 1.10", require: false, platform: :mri gem "image_size", "~> 2.0" gem "kramdown", "~> 2.0", require: false gem "pdf-reader", "~> 2.12" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ef5c93ad..83f9d359 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "bundler/setup" +require "debug" require "rspec" require "rspec/wait" require "pathname" From 6312a9de160150e2ef99ea03026b75a3c22c921d Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Thu, 20 Mar 2025 16:42:32 +1100 Subject: [PATCH 03/12] Add mobile support to Page Note that when mobile is true, *both* mobile emulation and touch support are enabled. The spec checks whether mobile support has been enabled by checking for touch support with JavaScript. --- lib/ferrum/page.rb | 17 +++++++++++++++-- spec/page_spec.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index 9abcf918..842dc184 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -157,9 +157,22 @@ def set_viewport(width:, height:, scale_factor: 0, mobile: false) deviceScaleFactor: scale_factor, mobile: mobile ) + + options = if mobile + { + enabled: true, + maxTouchPoints: 1 + } + else + { + enabled: false + } + end + + command("Emulation.setTouchEmulationEnabled", **options) 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..354d2faa 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 From a0719697d2e19dd289b2e300dadeade76ba4040e Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Fri, 21 Mar 2025 10:20:26 +1100 Subject: [PATCH 04/12] Add documentation for mobile option --- lib/ferrum/browser.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ferrum/browser.rb b/lib/ferrum/browser.rb index 9db21a42..fd1a6acd 100644 --- a/lib/ferrum/browser.rb +++ b/lib/ferrum/browser.rb @@ -126,6 +126,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 From 33b16bda43d03cc7342f23861dfd421f7bf64939 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Fri, 21 Mar 2025 10:35:59 +1100 Subject: [PATCH 05/12] Add mobile to browser options This way we can use Cuprite to register a driver that in turn enables mobile emulation in the browser. --- lib/ferrum/browser/options.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ferrum/browser/options.rb b/lib/ferrum/browser/options.rb index 987f9442..2f2e9624 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, :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) @@ -31,6 +31,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] From d950aad343d1c58340a2b276b452dbdd1fcdd7df Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Fri, 21 Mar 2025 11:25:48 +1100 Subject: [PATCH 06/12] Override the viewport size when mobile: true This is a bit hacky; ideally, a caller wouldn't both specify a non-mobile size *and* mobile: true. --- lib/ferrum/page.rb | 57 ++++++++++++++++++++++++++++------------------ spec/page_spec.rb | 7 ++++++ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index 842dc184..c3fe13e8 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -136,7 +136,11 @@ def close_connection end # - # Overrides device screen dimensions and emulates viewport according to parameters + # Overrides device screen dimensions and emulates viewport according to parameters. + # + # Note that passing mobile: true will cause set_viewport to ignore the passed + # height and width values, and instead use 390 x 844, which is the viewport size + # of an iPhone 14. # # Read more [here](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride). # @@ -149,27 +153,36 @@ def close_connection # @param [Boolean] mobile whether to emulate mobile device # def set_viewport(width:, height:, scale_factor: 0, mobile: false) - command( - "Emulation.setDeviceMetricsOverride", - slowmoable: true, - width: width, - height: height, - deviceScaleFactor: scale_factor, - mobile: mobile - ) - - options = if mobile - { - enabled: true, - maxTouchPoints: 1 - } - else - { - enabled: false - } - end - - command("Emulation.setTouchEmulationEnabled", **options) + if mobile + command( + "Emulation.setTouchEmulationEnabled", + enabled: true, + maxTouchPoints: 1 + ) + + command( + "Emulation.setDeviceMetricsOverride", + deviceScaleFactor: 3.0, + height: 844, + mobile: true, + slowmoable: true, + width: 390 + ) + else + command( + "Emulation.setTouchEmulationEnabled", + enabled: false + ) + + command( + "Emulation.setDeviceMetricsOverride", + deviceScaleFactor: scale_factor, + height: height, + mobile: false, + slowmoable: true, + width: width + ) + end end def resize(width: nil, height: nil, fullscreen: false, mobile: false) diff --git a/spec/page_spec.rb b/spec/page_spec.rb index 354d2faa..15f0671e 100644 --- a/spec/page_spec.rb +++ b/spec/page_spec.rb @@ -277,6 +277,13 @@ def is_mobile? page.reload end.to change { is_mobile? }.to(true) end + + it "resizes to the size of an iPhone 14 viewport, taking into account scale factor" do + expect do + page.resize(width: 0, height: 0, mobile: true) + page.reload + end.to change { body_size }.to(width: 980, height: 2120) + end end end end From 6753f84118d156bf5cba8582c58567f6748c3597 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Fri, 21 Mar 2025 11:52:58 +1100 Subject: [PATCH 07/12] Update README Add details of resize and mobile behavior to the README. Note that the behaviour on height or width of 0 was pre-existing; I just documented it and added a spec. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7875200e..ad8461d0 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,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 @@ -1088,6 +1088,13 @@ 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) ``` From 024dcc81a0241e1cb83aed607d86e1276db56351 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Mon, 24 Mar 2025 10:34:00 +1100 Subject: [PATCH 08/12] Fix Rubocop offenses --- spec/page_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/page_spec.rb b/spec/page_spec.rb index 15f0671e..e22c7640 100644 --- a/spec/page_spec.rb +++ b/spec/page_spec.rb @@ -260,13 +260,13 @@ def is_mobile? 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] } + 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] } + expect { page.resize(width: 0, height: 1000) }.not_to(change { body_size[:width] }) end end From 3dd7cc779d6e279a35e6619df04320a265f5a074 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Mon, 24 Mar 2025 12:54:11 +1100 Subject: [PATCH 09/12] Tweak README to include mobile example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad8461d0..5d0fa263 100644 --- a/README.md +++ b/README.md @@ -1096,7 +1096,7 @@ If `:mobile` is `true`: 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) ``` From 5524262e582d1c8251edf546181836bf69fa9c59 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Tue, 25 Mar 2025 17:51:46 +1100 Subject: [PATCH 10/12] Revert "Add debug Gem" This reverts commit 42184d615e0251c8e53e20578d0bba8ae663abc9. --- Gemfile | 1 - spec/spec_helper.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index cee4a7e2..a4edf99e 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,6 @@ source "https://rubygems.org" gem "byebug", "~> 11.0", platforms: %i[mri mingw x64_mingw] gem "chunky_png", "~> 1.3" -gem "debug", "~> 1.10", require: false, platform: :mri gem "image_size", "~> 2.0" gem "kramdown", "~> 2.0", require: false gem "pdf-reader", "~> 2.12" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 83f9d359..ef5c93ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "bundler/setup" -require "debug" require "rspec" require "rspec/wait" require "pathname" From 1560ecd887b92c1ef1b464ffccbb1f9258236230 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Tue, 25 Mar 2025 17:51:52 +1100 Subject: [PATCH 11/12] Revert "Add ruby-lsp as an optional dependency" This reverts commit dd15f1adbe7af10ee2edc8dce0e8f610885b0d21. --- Gemfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Gemfile b/Gemfile index a4edf99e..dc702f11 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,6 @@ gem "rspec", "~> 3.8" gem "rspec-wait" gem "rubocop", "~> 1.22" gem "rubocop-rake", require: false -gem "ruby-lsp", require: false gem "sinatra", "~> 3.2" gem "yard", "~> 0.9", require: false From bd578fbd7cb9e9d852f1b901d7df4f765ee07955 Mon Sep 17 00:00:00 2001 From: Duncan Bayne Date: Thu, 27 Mar 2025 14:24:01 +1100 Subject: [PATCH 12/12] Remove automatic resizing from Ferrum This should really live at the Cuprite level, emulating specific devices. --- lib/ferrum/page.rb | 31 +++++++++---------------------- spec/page_spec.rb | 7 ------- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index c3fe13e8..1960ede1 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -138,10 +138,6 @@ def close_connection # # Overrides device screen dimensions and emulates viewport according to parameters. # - # Note that passing mobile: true will cause set_viewport to ignore the passed - # height and width values, and instead use 390 x 844, which is the viewport size - # of an iPhone 14. - # # Read more [here](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setDeviceMetricsOverride). # # @param [Integer] width width value in pixels. 0 disables the override @@ -159,30 +155,21 @@ def set_viewport(width:, height:, scale_factor: 0, mobile: false) enabled: true, maxTouchPoints: 1 ) - - command( - "Emulation.setDeviceMetricsOverride", - deviceScaleFactor: 3.0, - height: 844, - mobile: true, - slowmoable: true, - width: 390 - ) else command( "Emulation.setTouchEmulationEnabled", enabled: false ) - - command( - "Emulation.setDeviceMetricsOverride", - deviceScaleFactor: scale_factor, - height: height, - mobile: false, - slowmoable: true, - width: width - ) end + + command( + "Emulation.setDeviceMetricsOverride", + deviceScaleFactor: scale_factor, + height: height, + mobile: mobile, + slowmoable: true, + width: width + ) end def resize(width: nil, height: nil, fullscreen: false, mobile: false) diff --git a/spec/page_spec.rb b/spec/page_spec.rb index e22c7640..ab521a7a 100644 --- a/spec/page_spec.rb +++ b/spec/page_spec.rb @@ -277,13 +277,6 @@ def is_mobile? page.reload end.to change { is_mobile? }.to(true) end - - it "resizes to the size of an iPhone 14 viewport, taking into account scale factor" do - expect do - page.resize(width: 0, height: 0, mobile: true) - page.reload - end.to change { body_size }.to(width: 980, height: 2120) - end end end end