From cbb66415e5eb2f5eb87d54ddde382bb73a719130 Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Tue, 26 Sep 2023 17:46:04 +0200 Subject: [PATCH] feat: Supports capture area screenshot --- README.md | 13 +++++++-- lib/ferrum/page/screenshot.rb | 42 ++++++++++++++++++----------- spec/page/screenshot_spec.rb | 50 ++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bdfa442f..32ba80b0 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,12 @@ Saves screenshot on a disk or returns it as base64. * :format `String` "jpeg" | "png" * :quality `Integer` 0-100 works for jpeg only * :full `Boolean` whether you need full page screenshot or a viewport - * :selector `String` css selector for given element + * :selector `String` css selector for given element, optional + * :area `Hash` area for screenshot, optional + * :x `Integer` + * :y `Integer` + * :width `Integer` + * :height `Integer` * :scale `Float` zoom in/out * :background_color `Ferrum::RGBA.new(0, 0, 0, 0.0)` to have specific background color @@ -370,7 +375,11 @@ browser.screenshot(path: "google.png") # => 134660 # Save on the disk in JPG browser.screenshot(path: "google.jpg") # => 30902 # Save to Base64 the whole page not only viewport and reduce quality -browser.screenshot(full: true, quality: 60) # "iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAYAAAC6uhUNAAAAAXNSR0IArs4c6Q... +browser.screenshot(full: true, quality: 60, encoding: :base64) # "iVBORw0KGgoAAAANSUhEUgAABAAAAAMACAYAAAC6uhUNAAAAAXNSR0IArs4c6Q... +# Save on the disk with the selected element in PNG +browser.screenshot(path: "google.png", selector: 'textarea') # => 11340 +# Save to Base64 with an area of the page in PNG +browser.screenshot(path: "google.png", area: { x: 0, y: 0, width: 400, height: 300 }) # => 54239 # Save with specific background color browser.screenshot(background_color: Ferrum::RGBA.new(0, 0, 0, 0.0)) ``` diff --git a/lib/ferrum/page/screenshot.rb b/lib/ferrum/page/screenshot.rb index 580ed1b7..113bdf3d 100644 --- a/lib/ferrum/page/screenshot.rb +++ b/lib/ferrum/page/screenshot.rb @@ -5,6 +5,9 @@ module Ferrum class Page module Screenshot + PARTIAL_SCREENSHOT_ARGUMENTS_MESSAGE = "Ignoring :selector or :area in #screenshot since full: true was given at " + AREA_SCREENSHOT_ARGUMENT_MESSAGE = "Ignoring :area in #screenshot since selector: was given at " + DEFAULT_PDF_OPTIONS = { landscape: false, paper_width: 8.5, @@ -50,6 +53,9 @@ module Screenshot # @option opts [String] :selector # CSS selector for the given element. # + # @option opts [Hash] :area + # x, y, width, height to screenshot an area. + # # @option opts [Float] :scale # Zoom in/out. # @@ -198,7 +204,7 @@ def screenshot_options(path = nil, format: nil, scale: 1.0, **options) screenshot_options.merge!(quality: quality) if quality screenshot_options.merge!(format: format) - clip = area_options(options[:full], options[:selector], scale) + clip = area_options(options[:full], options[:selector], scale, options[:area]) screenshot_options.merge!(clip: clip) if clip screenshot_options @@ -214,29 +220,35 @@ def format_options(format, path, quality) [format, quality] end - def area_options(full, selector, scale) - message = "Ignoring :selector in #screenshot since full: true was given at #{caller(1..1).first}" - warn(message) if full && selector + def area_options(full, selector, scale, area = nil) + warn("#{PARTIAL_SCREENSHOT_ARGUMENTS_MESSAGE}#{caller(1..1).first}") if full && (selector || area) + warn("#{AREA_SCREENSHOT_ARGUMENT_MESSAGE}#{caller(1..1).first}") if selector && area clip = if full - width, height = document_size - { x: 0, y: 0, width: width, height: height, scale: scale } if width.positive? && height.positive? + full_window_area || viewport_area elsif selector - bounding_rect(selector).merge(scale: scale) + bounding_rect(selector) + elsif area + area + else + viewport_area end - if scale != 1 - unless clip - width, height = viewport_size - clip = { x: 0, y: 0, width: width, height: height } - end - - clip.merge!(scale: scale) - end + clip.merge!(scale: scale) clip end + def full_window_area + width, height = document_size + { x: 0, y: 0, width: width, height: height } if width.positive? && height.positive? + end + + def viewport_area + width, height = viewport_size + { x: 0, y: 0, width: width, height: height } + end + def bounding_rect(selector) rect = evaluate_async(%( const rect = document diff --git a/spec/page/screenshot_spec.rb b/spec/page/screenshot_spec.rb index 19829768..aebd8674 100644 --- a/spec/page/screenshot_spec.rb +++ b/spec/page/screenshot_spec.rb @@ -51,9 +51,9 @@ end end - it "ignores :selector in #save_screenshot if full: true" do + it "ignores :selector and :area in #save_screenshot if full: true" do browser.go_to("/ferrum/long_page") - expect(browser.page).to receive(:warn).with(/Ignoring :selector/) + expect(browser.page).to receive(:warn).with(/Ignoring :selector or :area/) create_screenshot(path: file, full: true, selector: "#penultimate") @@ -63,6 +63,24 @@ end end + it "ignores :area in #save_screenshot if selector is set" do + browser.go_to("/ferrum/long_page") + expect(browser.page).to receive(:warn).with(/Ignoring :area/) + + create_screenshot(path: file, selector: "#penultimate", area: { x: 0, y: 0, width: 200, height: 100 }) + + File.open(file, "rb") do |f| + size = browser.evaluate <<-JS + function() { + var ele = document.getElementById("penultimate"); + var rect = ele.getBoundingClientRect(); + return [rect.width, rect.height]; + }(); + JS + expect(ImageSize.new(f.read).size).to eq(size.map { |s| s * device_pixel_ratio }) + end + end + it "resets element positions after" do browser.go_to("ferrum/long_page") el = browser.at_css("#middleish") @@ -84,10 +102,10 @@ img.pixels.inject(0) { |i, p| p > 255 ? i + 1 : i } } - browser.screenshot(path: file) + create_screenshot(path: file) before = black_pixels_count[file] - browser.screenshot(path: file, scale: scale) + create_screenshot(path: file, scale: scale) after = black_pixels_count[file] expect(after.to_f / before).to eq(scale**2) @@ -199,6 +217,30 @@ def create_screenshot(**options) end end + context "with area screenshot" do + it "supports screenshotting of an area" do + browser.go_to("/ferrum/custom_html_size") + expect(browser.viewport_size).to eq([1024, 768]) + + browser.screenshot(path: file, area: { x: 0, y: 0, width: 300, height: 200 }) + + File.open(file, "rb") do |f| + expect(ImageSize.new(f.read).size).to eq([300, 200].map { |s| s * device_pixel_ratio }) + end + expect(browser.viewport_size).to eq([1024, 768]) + end + + it "keeps current viewport" do + browser.go_to + browser.set_viewport(width: 800, height: 200) + + browser.screenshot(path: file, area: { x: 0, y: 0, width: 300, height: 200 }) + + expect(File.exist?(file)).to be(true) + expect(browser.viewport_size).to eq([800, 200]) + end + end + context "with :background_color option" do it "supports screenshotting page with the specific background color" do file = "#{PROJECT_ROOT}/spec/tmp/screenshot.jpeg"