Skip to content
Merged
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
32 changes: 16 additions & 16 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Ignore files for distribution archives.

/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/behat.php export-ignore
/docker-compose.override.default.yml export-ignore
/docker-compose.yml export-ignore
/docs export-ignore
/gherkinlint.json export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/tests export-ignore
/.claude export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/CLAUDE.md export-ignore
/behat.php export-ignore
/docs export-ignore
/gherkinlint.json export-ignore
/logo.png export-ignore
/phpcs.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/tests export-ignore
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,19 @@ In the URL tokens, every character other than a letter, digit, underscore or hyp
| `{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 | `I_am_on_the_test_page` |
| `{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.

```shell
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.
Expand Down Expand Up @@ -336,11 +343,10 @@ $extension = new Extension(BehatScreenshotExtension::class, [
With all four types enabled, the information is prepended to the captured HTML:

```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/>
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>
...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,9 @@ public function captureScreenshot(array $config = []): void {
$filename_html = $this->makeFilename('html', $timestamp, $filename, $is_failed);
$this->writeScreenshotContent($filename_html, $content);

// Drivers that do not support screenshots, including the Goutte driver
// shipped with Behat, throw an exception. For such drivers, the screenshot
// is stored as an HTML page (without referenced assets).
// A driver without screenshot support throws instead of capturing, leaving
// the HTML file written above as the only record of the page, without its
// referenced assets.
try {
$content = $is_fullscreen ? $this->getScreenshotFullscreen() : $this->getScreenshot();
}
Expand Down
18 changes: 10 additions & 8 deletions tests/behat/bootstrap/BehatCliTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,8 @@ class FeatureContextTest extends MinkContext implements Context {
public function __construct($parameters) {
$this->screenshotInitParams($parameters);

// Set the screenshot token host to override any real host.
// Override any real host in the screenshot token.
putenv('BEHAT_SCREENSHOT_TOKEN_HOST=example.com');
// Set the JavaScript override base URL.
$this->javascriptBaseUrl = getenv('BEHAT_JAVASCRIPT_BASE_URL') ?: 'http://host.docker.internal:8888';
}

Expand All @@ -146,11 +145,14 @@ public function __construct($parameters) {
#[BeforeScenario('@javascript&&~@skip-base-url-rewrite')]
public function beforeScenarioUpdateBaseUrl(BeforeScenarioScope $scope): void {
$environment = $scope->getEnvironment();
if ($environment instanceof InitializedContextEnvironment) {
foreach ($environment->getContexts() as $context) {
if ($context instanceof RawMinkContext) {
$context->setMinkParameter('base_url', $this->javascriptBaseUrl);
}

if (!$environment instanceof InitializedContextEnvironment) {
return;
}

foreach ($environment->getContexts() as $context) {
if ($context instanceof RawMinkContext) {
$context->setMinkParameter('base_url', $this->javascriptBaseUrl);
}
}
}
Expand Down Expand Up @@ -334,7 +336,7 @@ protected static function behatCliPrintFileContents(string $filename, string $ti
*
* @return string|false
* Value of the BEHAT_CLI_DEBUG environment variable, or FALSE when it is
* not set. Debug output is enabled when the value is a non-empty string.
* not set.
*/
protected static function behatCliIsDebug(): string|false {
return getenv('BEHAT_CLI_DEBUG');
Expand Down
19 changes: 0 additions & 19 deletions tests/phpunit/Profile/AnimationAssemblyProfileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

namespace DrevOps\BehatScreenshotExtension\Tests\Profile;

use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\FeatureNode;
use Behat\Gherkin\Node\ScenarioInterface;
use Behat\Testwork\Environment\Environment;
use DrevOps\BehatScreenshotExtension\Tests\Traits\BehatScopeTrait;
use DrevOps\BehatScreenshotExtension\Tests\Traits\GifParserTrait;
use DrevOps\BehatScreenshotExtension\Tests\Traits\ScreenshotConfigTrait;
Expand Down Expand Up @@ -282,19 +278,4 @@ protected function writeReport(string $report): void {
fwrite(STDERR, $report);
}

/**
* Create a before scenario scope.
*
* @return \Behat\Behat\Hook\Scope\BeforeScenarioScope
* Before scenario scope.
*/
protected function createBeforeScenarioScope(): BeforeScenarioScope {
$feature_node = $this->createStub(FeatureNode::class);
$feature_node->method('getTags')->willReturn([]);
$scenario = $this->createStub(ScenarioInterface::class);
$scenario->method('getTags')->willReturn([]);

return new BeforeScenarioScope($this->createStub(Environment::class), $feature_node, $scenario);
}

}
12 changes: 6 additions & 6 deletions tests/phpunit/Traits/BehatScopeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ protected function createBeforeScenarioScope(array $scenario_tags = [], array $f
* After step scope.
*/
protected function createAfterStepScope(bool $passed = TRUE): AfterStepScope {
$result = $this->createMock(StepResult::class);
$result->method('isPassed')->willReturn($passed);
$step_result = $this->createStub(StepResult::class);
$step_result->method('isPassed')->willReturn($passed);

return new AfterStepScope($this->createMock(Environment::class), $this->createMock(FeatureNode::class), $this->createMock(StepNode::class), $result);
return new AfterStepScope($this->createStub(Environment::class), $this->createStub(FeatureNode::class), $this->createStub(StepNode::class), $step_result);
}

/**
Expand All @@ -69,12 +69,12 @@ protected function createAfterStepScope(bool $passed = TRUE): AfterStepScope {
* After scenario scope.
*/
protected function createAfterScenarioScope(?string $feature_file = NULL, int $scenario_line = 0): AfterScenarioScope {
$feature_node = $this->createMock(FeatureNode::class);
$feature_node = $this->createStub(FeatureNode::class);
$feature_node->method('getFile')->willReturn($feature_file);
$scenario = $this->createMock(ScenarioInterface::class);
$scenario = $this->createStub(ScenarioInterface::class);
$scenario->method('getLine')->willReturn($scenario_line);

return new AfterScenarioScope($this->createMock(Environment::class), $feature_node, $scenario, $this->createMock(TestResult::class));
return new AfterScenarioScope($this->createStub(Environment::class), $feature_node, $scenario, $this->createStub(TestResult::class));
}

}
2 changes: 1 addition & 1 deletion tests/phpunit/Unit/ScreenshotContextAnimationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public function testAfterScenarioAnimateSkipsWhenNoFrames(): void {
}

public function testAfterScenarioAnimateReleasesEncoderWhenRenderFails(): void {
$encoder = $this->createMock(AnimatedGifEncoder::class);
$encoder = $this->createStub(AnimatedGifEncoder::class);
$encoder->method('count')->willReturn(1);
$encoder->method('render')->willThrowException(new \RuntimeException('render failed'));

Expand Down
8 changes: 4 additions & 4 deletions tests/phpunit/Unit/ScreenshotContextInfoTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ public static function dataProviderRenderInfoCompilesConfiguredInfoTypes(): arra
}

public function testRenderInfoMarksUrlNotAvailableOnException(): void {
$env = $this->createMock(Environment::class);
$feature_node = $this->createMock(FeatureNode::class);
$step_node = $this->createMock(StepNode::class);
$env = $this->createStub(Environment::class);
$feature_node = $this->createStub(FeatureNode::class);
$step_node = $this->createStub(StepNode::class);
$scope = new BeforeStepScope($env, $feature_node, $step_node);

$session = $this->createMock(Session::class);
$session = $this->createStub(Session::class);
$session->method('getCurrentUrl')->willThrowException(new \Exception('URL not available'));

$screenshot_context = $this->createPartialMock(ScreenshotContext::class, ['getSession']);
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/Unit/ScreenshotContextResizeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testGetScreenshotFullscreenWithResizeResizesThenRestoresWindow()
]);

$session = $this->createMock(Session::class);
$driver = $this->createMock(Selenium2Driver::class);
$driver = $this->createStub(Selenium2Driver::class);

$session->method('evaluateScript')
->willReturnOnConsecutiveCalls(
Expand Down Expand Up @@ -81,7 +81,7 @@ public function testGetScreenshotFullscreenWithResizeSkipsResizeOnInvalidDimensi
]);

$session = $this->createMock(Session::class);
$driver = $this->createMock(Selenium2Driver::class);
$driver = $this->createStub(Selenium2Driver::class);

$session->method('evaluateScript')
->willReturnOnConsecutiveCalls(
Expand Down
37 changes: 18 additions & 19 deletions tests/phpunit/Unit/ScreenshotContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,11 @@ public static function dataProviderIsTaggedMatchesTagWithOrWithoutPrefix(): arra
}

public function testBeforeScenarioInitPropagatesDriverStartException(): void {
$env = $this->createMock(Environment::class);
$feature_node = $this->createMock(FeatureNode::class);
$scenario = $this->createMock(ScenarioInterface::class);
$scenario->method('hasTag')->with('javascript')->willReturn(TRUE);
$session = $this->createMock(Session::class);
$driver = $this->createMock(Selenium2Driver::class);
$env = $this->createStub(Environment::class);
$feature_node = $this->createStub(FeatureNode::class);
$scenario = $this->createStub(ScenarioInterface::class);
$session = $this->createStub(Session::class);
$driver = $this->createStub(Selenium2Driver::class);
$driver->method('start')->willThrowException(new \RuntimeException('Test Exception.'));
$session->method('getDriver')->willReturn($driver);

Expand All @@ -223,9 +222,9 @@ public function testBeforeScenarioInitPropagatesDriverStartException(): void {
}

public function testBeforeStepInitStoresScopeForLaterRetrieval(): void {
$env = $this->createMock(Environment::class);
$feature_node = $this->createMock(FeatureNode::class);
$step_node = $this->createMock(StepNode::class);
$env = $this->createStub(Environment::class);
$feature_node = $this->createStub(FeatureNode::class);
$step_node = $this->createStub(StepNode::class);

$feature_node->method('getFile')->willReturn(TRUE);
$screenshot_context = new ScreenshotContext();
Expand Down Expand Up @@ -276,8 +275,8 @@ public static function dataProviderAfterStepHooksCaptureScreenshotFromStepResult

public function testIsaveSizedScreenshotIgnoresUnsupportedResize(): void {
$screenshot_context = $this->createPartialMock(ScreenshotContext::class, ['getSession', 'captureScreenshot']);
$session = $this->createMock(Session::class);
$exception = new UnsupportedDriverActionException('Not supported', $this->createMock(Selenium2Driver::class));
$session = $this->createStub(Session::class);
$exception = new UnsupportedDriverActionException('Not supported', $this->createStub(Selenium2Driver::class));
$session->method('resizeWindow')->willThrowException($exception);
$screenshot_context->method('getSession')->willReturn($session);
$screenshot_context->expects($this->once())->method('captureScreenshot');
Expand Down Expand Up @@ -455,7 +454,7 @@ public function testMakeFilenameReplacesTokensInPatterns(
'getBeforeStepScope',
'getSession',
]);
$session = $this->createMock(Session::class);
$session = $this->createStub(Session::class);

if ($url instanceof \Exception) {
$session->method('getCurrentUrl')->willThrowException($url);
Expand All @@ -465,9 +464,9 @@ public function testMakeFilenameReplacesTokensInPatterns(
}

$screenshot_context->method('getSession')->willReturn($session);
$env = $this->createMock(Environment::class);
$feature_node = $this->createMock(FeatureNode::class);
$step_node = $this->createMock(StepNode::class);
$env = $this->createStub(Environment::class);
$feature_node = $this->createStub(FeatureNode::class);
$step_node = $this->createStub(StepNode::class);
$step_node->method('getText')->willReturn($step_text);
$step_node->method('getLine')->willReturn($step_line);
$feature_node->method('getFile')->willReturn($feature_file);
Expand Down Expand Up @@ -559,15 +558,15 @@ public static function dataProviderMakeFilenameReplacesTokensInPatterns(): array
public function testMakeFilenameReplacesUrlHostFromEnvironment(): void {
$this->setEnvironmentVariable(ScreenshotContext::ENV_TOKEN_HOST, 'example.org');

$env = $this->createMock(Environment::class);
$feature_node = $this->createMock(FeatureNode::class);
$env = $this->createStub(Environment::class);
$feature_node = $this->createStub(FeatureNode::class);
$feature_node->method('getFile')->willReturn('test-feature-file');
$step_node = $this->createMock(StepNode::class);
$step_node = $this->createStub(StepNode::class);
$step_node->method('getText')->willReturn('test-step');
$step_node->method('getLine')->willReturn(123);
$scope = new BeforeStepScope($env, $feature_node, $step_node);

$session = $this->createMock(Session::class);
$session = $this->createStub(Session::class);
$session->method('getCurrentUrl')->willReturn('http://localhost:8080/test-page');

$screenshot_context = $this->createPartialMock(ScreenshotContext::class, ['getSession', 'getBeforeStepScope']);
Expand Down
5 changes: 5 additions & 0 deletions tests/phpunit/Unit/TokenizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ public static function dataProviderReplaceTokensResolvesKnownTokensAndKeepsUnkno
$data,
'20240312_045703.foo-failed_foo-file.feature_6_Foo_step_name.png',
],
'feature token alias' => [
'{feature}.{feature_file}.{ext}',
$data,
'foo-file.foo-file.png',
],
'url token' => [
'{url}.{ext}',
$data,
Expand Down