Skip to content

Commit

Permalink
feat: Supports capture area screenshot (#410)
Browse files Browse the repository at this point in the history
  • Loading branch information
erickgskovik authored Nov 9, 2023
1 parent 5b70e5a commit 2f9e94f
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 21 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,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

Expand All @@ -371,7 +376,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))
```
Expand Down
42 changes: 27 additions & 15 deletions lib/ferrum/page/screenshot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
#
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
50 changes: 46 additions & 4 deletions spec/page/screenshot_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 2f9e94f

Please sign in to comment.