Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions integration_test/cases/browser/fullpage_screenshot_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Wallaby.Integration.Browser.FullpageScreenshotTest do
use Wallaby.Integration.SessionCase, async: false

import Wallaby.SettingsTestHelpers

alias Wallaby.TestSupport.TestWorkspace

setup %{session: session} do
page =
session
|> visit("/long_page.html")

screenshots_path = TestWorkspace.generate_temporary_path()
ensure_setting_is_reset(:wallaby, :screenshot_dir)
Application.put_env(:wallaby, :screenshot_dir, screenshots_path)

{:ok, page: page, screenshots_path: screenshots_path}
end

test "fullpage screenshot captures content beyond the viewport", %{page: page} do
assert [viewport_path] =
page
|> take_screenshot(name: "viewport")
|> Map.get(:screenshots)

assert [fullpage_path] =
page
|> take_screenshot(name: "fullpage", full_page: true)
|> Map.get(:screenshots)

viewport_size = File.stat!(viewport_path).size
fullpage_size = File.stat!(fullpage_path).size

assert fullpage_size > viewport_size
end

test "full_page option defaults to false", %{page: page, screenshots_path: screenshots_path} do
assert [path] =
page
|> take_screenshot(name: "default")
|> Map.get(:screenshots)

assert path |> Path.expand() |> File.exists?()
assert Path.dirname(Path.expand(path)) == Path.expand(screenshots_path)
end

test "full_page can be combined with other options", %{page: page} do
import ExUnit.CaptureIO

output =
capture_io(fn ->
page
|> take_screenshot(name: "fullpage_logged", full_page: true, log: true)
end)

assert output =~ "Screenshot taken, find it at"
assert output =~ "fullpage_logged.png"
end
end
13 changes: 13 additions & 0 deletions integration_test/support/pages/long_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Long Page</title>
</head>
<body>
<h1>Long Page</h1>
<div style="height: 5000px;">
<p>This content extends well beyond the viewport.</p>
</div>
<p id="bottom">Bottom of the page</p>
</body>
</html>
19 changes: 16 additions & 3 deletions lib/wallaby/browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,27 @@ defmodule Wallaby.Browser do

Pass `[{:name, "some_name"}]` to specify the file name. Defaults to a timestamp.
Pass `[{:log, true}]` to log the location of the screenshot to stdout. Defaults to false.
Pass `[{:full_page, true}]` to capture the entire page, not just the viewport. Defaults to false.

## Full Page Screenshots

When `full_page: true` is specified, the entire document is captured including content
outside the viewport. Supported drivers:
- ChromeDriver (Chrome)
- GeckoDriver 0.16.0+ (Firefox via Selenium)
"""
@type take_screenshot_opt :: {:name, String.t()} | {:log, boolean}
@type take_screenshot_opt :: {:name, String.t()} | {:log, boolean} | {:full_page, boolean}
@spec take_screenshot(parent, [take_screenshot_opt]) :: parent

def take_screenshot(%{driver: driver} = screenshotable, opts \\ []) do
image_data =
screenshotable
|> driver.take_screenshot
if opts[:full_page] do
screenshotable
|> driver.take_fullpage_screenshot()
else
screenshotable
|> driver.take_screenshot()
end

name =
opts
Expand Down
7 changes: 7 additions & 0 deletions lib/wallaby/chrome.ex
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,13 @@ defmodule Wallaby.Chrome do
def element_location(element), do: delegate(:element_location, element)
@doc false
def take_screenshot(session_or_element), do: delegate(:take_screenshot, session_or_element)
@doc false
def take_fullpage_screenshot(session_or_element) do
check_logs!(session_or_element, fn ->
WebdriverClient.take_fullpage_screenshot(session_or_element)
end)
end

@doc false
defdelegate log(session_or_element), to: WebdriverClient

Expand Down
6 changes: 6 additions & 0 deletions lib/wallaby/driver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ defmodule Wallaby.Driver do
"""
@callback take_screenshot(Session.t() | Element.t()) :: binary | {:error, reason}

@doc """
Invoked to take a fullpage screenshot of the session.
This uses browser-specific APIs to capture the entire page, not just the viewport.
"""
@callback take_fullpage_screenshot(Session.t()) :: binary | {:error, reason}

@doc """
Invoked to get the handle for the currently focused window.
"""
Expand Down
3 changes: 3 additions & 0 deletions lib/wallaby/selenium.ex
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ defmodule Wallaby.Selenium do
@doc false
defdelegate take_screenshot(session_or_element), to: WebdriverClient

@doc false
defdelegate take_fullpage_screenshot(session_or_element), to: WebdriverClient

@doc false
def cookies(%Session{} = session) do
WebdriverClient.cookies(session)
Expand Down
37 changes: 37 additions & 0 deletions lib/wallaby/webdriver_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,43 @@
end
end

defp execute_cdp(session, command, params \\ %{}) do

Check warning on line 406 in lib/wallaby/webdriver_client.ex

View workflow job for this annotation

GitHub Actions / Unit Tests (1.12.x/24.x)

default values for the optional arguments in execute_cdp/3 are never used

Check warning on line 406 in lib/wallaby/webdriver_client.ex

View workflow job for this annotation

GitHub Actions / ChromeDriver (1.12.x/24.x)

default values for the optional arguments in execute_cdp/3 are never used

Check warning on line 406 in lib/wallaby/webdriver_client.ex

View workflow job for this annotation

GitHub Actions / Selenium v3 (1.12.x/24.x)

default values for the optional arguments in execute_cdp/3 are never used
request_params = %{
cmd: command,
params: params
}

with {:ok, resp} <- request(:post, "#{session.session_url}/goog/cdp/execute", request_params) do
Map.fetch(resp, "value")
end
end

@doc """
Takes a fullpage screenshot of the entire document.
Uses the Moz-specific endpoint for Firefox, and Chrome DevTools Protocol for all other browsers.
"""
@spec take_fullpage_screenshot(Session.t()) :: binary | {:error, any}
# Firefox: uses GeckoDriver's Moz-specific endpoint
def take_fullpage_screenshot(%Session{capabilities: %{browserName: "firefox"}} = session) do
with {:ok, resp} <- request(:get, "#{session.session_url}/moz/screenshot/full"),
{:ok, value} <- Map.fetch(resp, "value") do
:base64.decode(value)
end
end

# Chrome and other Chromium-based browsers: uses Chrome DevTools Protocol
def take_fullpage_screenshot(session) do
params = %{
format: "png",
captureBeyondViewport: true
}

with {:ok, result} <- execute_cdp(session, "Page.captureScreenshot", params),
{:ok, data} <- Map.fetch(result, "data") do
:base64.decode(data)
end
end

@doc """
Gets the cookies for a session.
"""
Expand Down
Loading