Skip to content

Latest commit

ย 

History

290 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Behat extension to create screenshots

GitHub Issues GitHub Pull Requests Test codecov GitHub release (latest by date) LICENSE Renovate

Total Downloads

Vortex Ecosystem


Features

  • Captures a screenshot using the I save screenshot step.
  • Captures fullscreen screenshots with the I save fullscreen screenshot step.
  • Automatically captures a screenshot when a test fails.
  • Supports both HTML and PNG screenshots.
  • Supports Selenium and Headless drivers.
  • Configurable screenshot directory.
  • Optionally purges the screenshot directory at the start of each test run.
  • Adds additional information to screenshots.
  • Records an animated GIF of a scenario from its per-step screenshots.

Installation

composer require --dev drevops/behat-screenshot
Behat PHP Configuration file
^3.33.0 >=8.3 behat.php or behat.dist.php
^4.0@alpha >=8.3 behat.php or behat.dist.php

Behat 4 is still an alpha, so Composer keeps installing Behat 3 until your project opts in. To opt in, require Behat 4 together with the Mink extension release that supports it:

composer require --dev \
  behat/behat:^4.0@alpha \
  friends-of-behat/mink-extension:^3.0@alpha \
  drevops/behat-screenshot

Usage

Behat reads behat.php, or behat.dist.php when there is no behat.php.

Example behat.php with default parameters:

<?php

declare(strict_types=1);

use Behat\Config\Config;
use Behat\Config\Extension;
use Behat\Config\Profile;
use Behat\Config\Suite;
use DrevOps\BehatScreenshotExtension\Context\ScreenshotContext;
use DrevOps\BehatScreenshotExtension\ServiceContainer\BehatScreenshotExtension;

$suite = (new Suite('default'))
  ->withContexts(ScreenshotContext::class, 'FeatureContext');

$profile = (new Profile('default'))
  ->withSuite($suite)
  ->withExtension(new Extension(BehatScreenshotExtension::class));

return (new Config())->withProfile($profile);

or with parameters:

<?php

declare(strict_types=1);

use Behat\Config\Config;
use Behat\Config\Extension;
use Behat\Config\Profile;
use Behat\Config\Suite;
use DrevOps\BehatScreenshotExtension\Context\ScreenshotContext;
use DrevOps\BehatScreenshotExtension\ServiceContainer\BehatScreenshotExtension;

$suite = (new Suite('default'))
  ->withContexts(ScreenshotContext::class, 'FeatureContext');

$extension = new Extension(BehatScreenshotExtension::class, [
  'dir' => '%paths.base%/screenshots',
  'on_failed' => TRUE,
  'purge' => FALSE,
  'always_fullscreen' => FALSE,
]);

$profile = (new Profile('default'))
  ->withSuite($suite)
  ->withExtension($extension);

return (new Config())->withProfile($profile);

The extension is registered by its full class name. Behat 3 also accepts the short DrevOps\BehatScreenshotExtension key, but Behat 4 doesn't, so the full name works on both.

The options table below describes every option, and behat.dist.php sets all of them.

In your feature:

Given I am on "http://google.com"
Then I save screenshot

You can capture fullscreen screenshots:

Given I am on "http://google.com"
Then I save fullscreen screenshot

Fullscreen screenshots work by temporarily resizing the browser window to the full height of the page to capture everything in one screenshot.

You may optionally specify the size of the browser window in the screenshot step:

Then I save 1440 x 900 screenshot

or a filename. The .html or .png extension is appended to the name, which can contain filename tokens:

Then I save screenshot with name "my_screenshot"
# Or with fullscreen
Then I save fullscreen screenshot with name "my_screenshot"

To always capture fullscreen screenshots, even without explicitly using the fullscreen keyword, set the always_fullscreen configuration option to true:

$extension = new Extension(BehatScreenshotExtension::class, [
  'always_fullscreen' => TRUE,
]);

Capturing screenshots after every step

To automatically capture a screenshot after every step, you can either:

  1. Enable globally in configuration:
$extension = new Extension(BehatScreenshotExtension::class, [
  'on_every_step' => TRUE,
]);
  1. Enable per-scenario using the @screenshots tag:
@screenshots
Scenario: My scenario with automatic screenshots
  Given I am on "http://example.com"
  When I click "Login"
  Then I should see "Welcome"
  # Screenshots will be captured after each of these steps

The @screenshots tag is read at both the scenario and feature level. It enables per-step screenshots even when on_every_step is disabled, but it cannot disable them when on_every_step is enabled.

Note: When both on_every_step and on_failed are enabled, only one screenshot is captured for failed steps (the failed screenshot) to avoid duplicates.

Recording an animated GIF

To record an animated GIF of a scenario from its per-step screenshots, you can either:

  1. Enable globally in configuration:
$extension = new Extension(BehatScreenshotExtension::class, [
  'animation' => [
    'enabled' => TRUE,
    'frame_delay' => 500,
  ],
]);
  1. Enable per-scenario using the @screenshots:animated tag:
@javascript @screenshots:animated
Scenario: My scenario recorded as an animated GIF
  Given I am on "http://example.com"
  When I click "Login"
  Then I should see "Welcome"
  # An animated GIF is written when the scenario finishes.

When animation is enabled, a screenshot is captured after every passed step, and the frames are combined into a single GIF when the scenario finishes. frame_delay sets the delay between frames in milliseconds.

The GIF is named {datetime:U}.{feature_file}.feature_<scenario line>.gif, which is the default filename_pattern with the scenario line in place of {step_line}, so it lands beside the scenario's per-step screenshots. That name is fixed: a custom filename_pattern changes the per-step filenames only.

The @screenshots:animated tag is read at both the scenario and feature level. Animation requires the gd PHP extension and a driver that can capture screenshots (such as a real browser via @javascript); without GD, the animated GIF is skipped while the per-step screenshots are still written.

Each frame keeps the size it was captured at. Frames smaller than the tallest or widest frame of the scenario are shown at the top-left of the animation against a white background, rather than being scaled up or stretched.

Skipping animation for a scenario

Animation captures a screenshot after every passed step, so its cost grows with the number of steps in a scenario. To keep animation on across the suite while excluding individual scenarios, use the @screenshots:animated:skip tag:

@javascript @screenshots:animated:skip
Scenario: Long workflow that should not be recorded
  Given I am on "http://example.com"
  # No animated GIF is written, and no per-step screenshots are captured for it.

To turn animation off for a whole run without editing feature files or configuration - on a CI job that only needs the per-step screenshots, for example - set the BEHAT_SCREENSHOT_ANIMATION_SKIP environment variable to a truthy value:

BEHAT_SCREENSHOT_ANIMATION_SKIP=1 vendor/bin/behat

This overrides everything below it, including @screenshots:animated tags, so no scenario in the run is animated.

Tags are resolved from the most specific scope down, so a scenario tag decides on its own, a feature tag applies only when the scenario carries neither tag, and animation.enabled applies only when neither scope is tagged:

BEHAT_SCREENSHOT_ANIMATION_SKIP Scenario tag Feature tag animation.enabled Animated
1 @screenshots:animated - false No
unset @screenshots:animated:skip - true No
unset @screenshots:animated @screenshots:animated:skip false Yes
unset @screenshots:animated:skip @screenshots:animated true No
unset - @screenshots:animated:skip true No
unset - - true Yes

A scenario or feature carrying both tags at once is not animated - the skip tag wins within a scope.

Skipping animation does not disable the per-step screenshots requested by on_every_step or the @screenshots tag; those are captured independently. It removes only the captures that animation itself requires.

Limiting frame size

With always_fullscreen: true every frame is as tall as the page it captured, so one long page - an admin listing, a search result set - produces very large frames and a correspondingly large GIF. Cap them with max_width and max_height:

$extension = new Extension(BehatScreenshotExtension::class, [
  'animation' => [
    'enabled' => TRUE,
    'max_height' => 2000,
  ],
]);

Frames larger than the cap are cropped to it before being encoded, keeping the top-left of the page; frames already within it are untouched. Each axis is capped on its own, so a max_height alone never changes a frame's width - the retained area keeps its captured resolution and every frame in the animation still shares the same width. Both caps default to 0, which leaves the frame size unbounded. The per-step PNG screenshots are always written at full size, so capping affects the animation only.

Options

Name Default value Description
dir %paths.base%/screenshots Path to directory to save screenshots. Directory structure will be created if the directory does not exist. Override with BEHAT_SCREENSHOT_DIR env var.
on_failed true Capture screenshot on failed test.
on_every_step false Automatically capture screenshots after every step. Can be enabled globally via config or per-scenario using the @screenshots tag. Only captures on passed steps to avoid duplicates with on_failed.
animation.enabled false Build an animated GIF per scenario from the per-step screenshots, automatically enabling per-step capture. Can be enabled per-scenario with the @screenshots:animated tag and disabled per-scenario with the @screenshots:animated:skip tag (both read at scenario or feature level, and both taking precedence over this setting). Disable for the whole run with the BEHAT_SCREENSHOT_ANIMATION_SKIP env var, which overrides both the tags and this setting. Requires the gd PHP extension.
animation.frame_delay 500 Delay between animated GIF frames, in milliseconds.
animation.max_width 0 Maximum animated GIF frame width, in pixels. Wider frames are cropped to it, keeping the left-hand side. 0 leaves the width unbounded.
animation.max_height 0 Maximum animated GIF frame height, in pixels. Taller frames are cropped to it, keeping the top of the page at full resolution and full width. 0 leaves the height unbounded. Useful with always_fullscreen, where frame height follows the page height.
purge false Remove all files from the screenshots directory at the start of each test run. Override with BEHAT_SCREENSHOT_PURGE env var. Useful during debugging of tests.
always_fullscreen false Always use fullscreen screenshot capture for all screenshot steps, including regular screenshot steps. When enabled, all I save screenshot steps will behave like I save fullscreen screenshot.
info_types none List of additional information types to show on screenshots: url, feature, step, datetime. Rendered in the order listed. No information is added unless this option is set.
failed_prefix failed_ Prefix failed screenshots with failed_ string. Useful to distinguish failed and intended screenshots.
filename_pattern {datetime:U}.{feature_file}.feature_{step_line}.{ext} Filename pattern for successful assertions.
filename_pattern_failed {datetime:U}.{failed_prefix}{feature_file}.feature_{step_line}.{ext} Filename pattern for failed assertions.

Filename tokens

In the URL tokens, every character other than a letter, digit, underscore or hyphen is replaced with an underscore, and consecutive replacements collapse into one. The URL examples below are for a page at http://example.com/mypath/subpath?myquery=1#somefragment.

Token Substituted with Example value(s)
{ext} The extension of the file captured html or png
{failed_prefix} The value of failed_prefix from configuration failed_, error_ (do include the _ suffix, if required)
{url} Full URL http_example_com_mypath_subpath_myquery_1_somefragment
{url_origin} Scheme with domain http_example_com
{url_relative} Path + query + fragment mypath_subpath_myquery_1_somefragment
{url_domain} Domain example_com
{url_path} Path mypath_subpath
{url_query} Query myquery_1
{url_fragment} Fragment somefragment
{feature_file} The filename of the .feature file currently being executed, without extension my_example.feature -> my_example
{feature} Alias of {feature_file} my_example.feature -> my_example
{step_line} Step line number 1, 10, 100
{step_line:%03d} Step line number with leading zeros. Modifiers are from sprintf(). 001, 010, 100
{step_name} Step name without Given/When/Then, with spaces replaced by underscores and double quotes removed I_am_on_the_test_page
{datetime} Current date and time. Defaults to the Ymd_His format. 20010310_171618
{datetime:U} Current date and time as a Unix timestamp. Modifiers are from date(). 1697490961

Set the BEHAT_SCREENSHOT_TOKEN_HOST environment variable to substitute the host of the current URL before the URL tokens expand. Filenames then stay the same across environments that serve the site under different hosts, such as a local machine and CI.

BEHAT_SCREENSHOT_TOKEN_HOST=example.com vendor/bin/behat

Auto-purge

By default, the purge option is disabled, so screenshots from previous test runs stay in the directory. This is useful when you want to keep the screenshots for debugging purposes.

To clear the directory at the start of each test run, enable the purge option in the configuration.

$extension = new Extension(BehatScreenshotExtension::class, [
  'purge' => TRUE,
]);

Alternatively, you can use the BEHAT_SCREENSHOT_PURGE environment variable to enable the auto-purge feature for a specific test run.

BEHAT_SCREENSHOT_PURGE=1 vendor/bin/behat

Additional information on screenshots

The info_types option controls which built-in information is added to screenshots, and nothing is added unless it is set. The order of the types is the order of the information displayed on the screenshot.

$extension = new Extension(BehatScreenshotExtension::class, [
  'info_types' => ['url', 'feature', 'step', 'datetime'],
]);

With all four types enabled, the information is prepended to the captured HTML:

Current URL: http://example.com<br />
Feature: My feature<br />
Step: I save screenshot (line 8)<br />
Datetime: 2025-01-19 00:01:10<hr/>
<!DOCTYPE html>
<html>
...
</html>

Custom entries can be added from your own context class with appendInfo(). They are rendered alongside the entries produced by info_types, and are rendered whether or not info_types is set.

#[\Behat\Hook\BeforeScenario]
public function beforeScenarioAddInfo(BeforeScenarioScope $scope): void {
  $environment = $scope->getEnvironment();
  if ($environment instanceof InitializedContextEnvironment) {
    foreach ($environment->getContexts() as $context) {
      if ($context instanceof ScreenshotContext) {
        $context->appendInfo('Custom info', 'My custom info');
      }
    }
  }
}

Contributing

See CONTRIBUTING.md for local setup, linting, unit and BDD tests, running the suites on Behat 4, the animated GIF assembly profiler, and adding a configuration option.


This repository was created using the Scaffold project template

About

๐Ÿ“ธ Behat extension to create screenshots

Topics

Resources

Contributing

Security policy

Stars

23 stars

Watchers

4 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages