Skip to content

Commit

Permalink
[#37] Allowed configurable filename (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
tannguyen04 committed Mar 13, 2024
1 parent e8b851a commit 77008ff
Show file tree
Hide file tree
Showing 12 changed files with 1,033 additions and 43 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,35 @@ You may optionally specify size of browser window in the screenshot step:
Remove all files from the screenshots directory on each test run. Useful during debugging of tests.
Can be overridden with `BEHAT_SCREENSHOT_PURGE` environment variable set to `1` or `true`.

- `filenamePattern:` `file-pattern` (default `{datetime:u}.{feature_file}.feature_{step_line}.{ext}`)

File name pattern for successful.

- `filenamePatternFailed:` `file-pattern` (default `{datetime:u}.{fail_prefix}{feature_file}.feature_{step_line}.{ext}`)

File name pattern for failed.

### Supported tokens

| Token | Substituted with | Example value(s) |
|--------------------|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `{ext}` | The extension of the file captured | `html` or `png` |
| `{fail_prefix}` | The value of fail_prefix from configuration | `failed_`, `error_` (do include the `_` suffix, if required) |
| `{url}` | Full URL | `http_example_com_mypath_subpath__query__myquery_1_plus_2_plus_3_and_another1_4__fragment__somefragment` |
| `{url_origin}` | Scheme with domain | `http_example_com` |
| `{url_relative}` | Path + query + fragment | `mypath_subpath__query__myquery_1_plus_2_plus_3_and_another1_4__fragment__somefragment` |
| `{url_domain}` | Domain | `example_com` |
| `{url_path}` | Path | `mypath_subpath` |
| `{url_query}` | Query | `myquery_1_plus_2_plus_3_and_another1_4` |
| `{url_fragment}` | Fragment | `somefragment` |
| `{feature_file}` | The filename of the `.feature` file currently being executed, without extension | `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` and lower-cased. | `i_am_on_the_test_page` |
| `{datetime}` | Current date and time. defaults to `Ymd_His` format. | `20010310_171618` |
| `{datetime:U}` | Current date and time as microtime. Modifiers are from `date()`. | `1697490961192498` |


## Maintenance

### Local development setup
Expand Down
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
"DrevOps\\BehatScreenshot": "src/"
}
},
"autoload-dev": {
"psr-4": {
"DrevOps\\BehatScreenshot\\Tests\\": "tests/phpunit"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
Expand All @@ -53,6 +58,7 @@
"phpcbf"
],
"test": [
"if [ \"${XDEBUG_MODE}\" = 'coverage' ]; then phpunit; else phpunit --no-coverage; fi",
"behat"
]
}
Expand Down
37 changes: 37 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.3/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="false"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
failOnWarning="true"
displayDetailsOnTestsThatTriggerWarnings="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
>
<testsuites>
<testsuite name="default">
<directory>tests/phpunit</directory>
</testsuite>
</testsuites>

<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>

<coverage includeUncoveredFiles="true"
pathCoverage="false"
ignoreDeprecatedCodeUnits="true"
disableCodeCoverageIgnore="false">
<report>
<html outputDirectory=".coverage-html" lowUpperBound="50" highLowerBound="90"/>
<cobertura outputFile="cobertura.xml"/>
</report>
</coverage>
</phpunit>
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ class ScreenshotContextInitializer implements ContextInitializer
/**
* ScreenshotContextInitializer constructor.
*
* @param string $dir Screenshot dir.
* @param bool $fail Screenshot when fail.
* @param string $failPrefix File name prefix for a failed test.
* @param bool $purge Purge dir before start script.
* @param bool $needsPurging Check if need to actually purge.
* @param string $dir Screenshot dir.
* @param bool $fail Screenshot when fail.
* @param string $failPrefix File name prefix for a failed test.
* @param bool $purge Purge dir before start script.
* @param string $filenamePattern File name pattern.
* @param string $filenamePatternFailed File name pattern failed.
* @param bool $needsPurging Check if need to actually purge.
*/
public function __construct(protected string $dir, protected bool $fail, private readonly string $failPrefix, protected bool $purge, protected bool $needsPurging = true)
public function __construct(protected string $dir, protected bool $fail, private readonly string $failPrefix, protected bool $purge, protected string $filenamePattern, protected string $filenamePatternFailed, protected bool $needsPurging = true)
{
}

Expand All @@ -38,7 +40,7 @@ public function initializeContext(Context $context): void
{
if ($context instanceof ScreenshotAwareContext) {
$dir = $this->resolveScreenshotDir();
$context->setScreenshotParameters($dir, $this->fail, $this->failPrefix);
$context->setScreenshotParameters($dir, $this->fail, $this->failPrefix, $this->filenamePattern, $this->filenamePatternFailed);
if ($this->shouldPurge() && $this->needsPurging) {
$this->purgeFilesInDir($dir);
$this->needsPurging = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ interface ScreenshotAwareContext extends Context
/**
* Set context parameters.
*
* @param string $dir Directory to store screenshots.
* @param bool $fail Create screenshots on fail.
* @param string $failPrefix File name prefix for a failed test.
* @param string $dir Directory to store screenshots.
* @param bool $fail Create screenshots on fail.
* @param string $failPrefix File name prefix for a failed test.
* @param string $filenamePattern File name pattern.
* @param string $filenamePatternFailed File name pattern failed.
*
* @return $this
*/
public function setScreenshotParameters(string $dir, bool $fail, string $failPrefix): static;
public function setScreenshotParameters(string $dir, bool $fail, string $failPrefix, string $filenamePattern, string $filenamePatternFailed): static;
}
106 changes: 77 additions & 29 deletions src/DrevOps/BehatScreenshotExtension/Context/ScreenshotContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,61 @@
use Behat\Mink\Exception\DriverException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Behat\MinkExtension\Context\RawMinkContext;
use DrevOps\BehatScreenshotExtension\Tokenizer;
use Symfony\Component\Filesystem\Filesystem;

/**
* Class ScreenshotContext.
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class ScreenshotContext extends RawMinkContext implements SnippetAcceptingContext, ScreenshotAwareContext
{

/**
* Screenshot step filename.
*
* @var string
*/
protected $featureFile;

/**
* Screenshot step line.
*
* @var int
*/
protected $stepLine;
protected string $stepLine;

/**
* Makes screenshot when fail.
*/
private bool $fail = false;
protected bool $fail = false;

/**
* Screenshot directory name.
*/
private string $dir = '';
protected string $dir = '';

/**
* Prefix for failed screenshot files.
*/
private string $failPrefix = '';
protected string $failPrefix = '';

/**
* Before step scope.
*/
protected BeforeStepScope $beforeStepScope;

/**
* Filename pattern.
*/
protected string $filenamePattern;

/**
* Filename pattern failed.
*/
protected string $filenamePatternFailed;

/**
* {@inheritdoc}
*/
public function setScreenshotParameters(string $dir, bool $fail, string $failPrefix): static
public function setScreenshotParameters(string $dir, bool $fail, string $failPrefix, string $filenamePattern, string $filenamePatternFailed): static
{
$this->dir = $dir;
$this->fail = $fail;
$this->failPrefix = $failPrefix;
$this->filenamePattern = $filenamePattern;
$this->filenamePatternFailed = $filenamePatternFailed;

return $this;
}
Expand Down Expand Up @@ -108,15 +118,17 @@ public function beforeStepInit(BeforeStepScope $scope): void
if (!$featureFile) {
throw new \RuntimeException('Feature file not found.');
}
$this->featureFile = $featureFile;
$this->stepLine = $scope->getStep()->getLine();
$this->beforeStepScope = $scope;
}

/**
* After scope event handler to print last response on error.
*
* @param AfterStepScope $event After scope event.
*
* @throws DriverException
* @throws UnsupportedDriverActionException
*
* @AfterStep
*/
public function printLastResponseOnError(AfterStepScope $event): void
Expand All @@ -135,15 +147,16 @@ public function printLastResponseOnError(AfterStepScope $event): void
* test.
* @param string|null $filename File name.
*
* @throws DriverException
* @throws UnsupportedDriverActionException
*
* @When save screenshot
* @When I save screenshot
*/
public function iSaveScreenshot($fail = false, $filename = null): void
public function iSaveScreenshot(bool $fail = false, string $filename = null): void
{
$driver = $this->getSession()->getDriver();

$fileName = $this->makeFileName('html', $fail ? $this->failPrefix : '', $filename);

$fileName = $this->makeFileName('html', $filename, $fail);
try {
$data = $driver->getContent();
} catch (DriverException) {
Expand All @@ -161,7 +174,7 @@ public function iSaveScreenshot($fail = false, $filename = null): void
$data = $driver->getScreenshot();
// Preserve filename, but change the extension - this is to group
// content and screenshot files together by name.
$fileName = substr($fileName, 0, -1 * strlen('html')).'png';
$fileName = $this->makeFileName('png', $filename, $fail);
$this->saveScreenshotData($fileName, $data);
} catch (UnsupportedDriverActionException) {
// Nothing to do here - drivers without support for screenshots
Expand All @@ -174,6 +187,9 @@ public function iSaveScreenshot($fail = false, $filename = null): void
*
* @param string $filename File name.
*
* @throws DriverException
* @throws UnsupportedDriverActionException
*
* @When I save screenshot with name :filename
*/
public function iSaveScreenshotWithName(string $filename): void
Expand All @@ -184,8 +200,11 @@ public function iSaveScreenshotWithName(string $filename): void
/**
* Save screenshot with specific dimensions.
*
* @param int $width Width to resize browser to.
* @param int $height Height to resize browser to.
* @param string|int $width Width to resize browser to.
* @param string|int $height Height to resize browser to.
*
* @throws DriverException
* @throws UnsupportedDriverActionException
*
* @When save :width x :height screenshot
* @When I save :width x :height screenshot
Expand Down Expand Up @@ -231,17 +250,46 @@ protected function prepareDir(string $dir): void
* Format: microseconds.featurefilename_linenumber.ext
*
* @param string $ext File extension without dot.
* @param string $prefix Optional file name prefix for a filed test.
* @param string|null $filename Optional file name.
* @param bool $fail Make filename for fail case.
*
* @return string Unique file name.
*
* @throws \Exception
*/
protected function makeFileName(string $ext, string $prefix = '', string $filename = null): string
protected function makeFileName(string $ext, string $filename = null, bool $fail = false): string
{
if (!empty($filename)) {
return sprintf('%s.%s', $filename, $ext);
if ($fail) {
$filename = $this->filenamePatternFailed;
} elseif (empty($filename)) {
$filename = $this->filenamePattern;
}

return sprintf('%01.2f.%s%s_%s.%s', microtime(true), $prefix, basename($this->featureFile), $this->stepLine, $ext);
// Make sure {ext} token is on filename.
if (!str_ends_with($filename, '.{ext}')) {
$filename .= '.{ext}';
}

$feature = $this->beforeStepScope->getFeature();
$step = $this->beforeStepScope->getStep();


try {
$url = $this->getSession()->getCurrentUrl();
} catch (\Exception) {
$url = null;
}

$data = [
'ext' => $ext,
'step_name' => $step->getText(),
'step_line' => $step->getLine(),
'feature_file' => $feature->getFile(),
'url' => $url,
'time' => time(),
'fail_prefix' => $this->failPrefix,
];

return Tokenizer::replaceTokens($filename, $data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ public function configure(ArrayNodeDefinition $builder): void
->scalarNode('dir')->cannotBeEmpty()->defaultValue('%paths.base%/screenshots')->end()
->scalarNode('fail')->cannotBeEmpty()->defaultValue(true)->end()
->scalarNode('fail_prefix')->cannotBeEmpty()->defaultValue('failed_')->end()
->scalarNode('purge')->cannotBeEmpty()->defaultValue(false)->end();
->scalarNode('purge')->cannotBeEmpty()->defaultValue(false)->end()
->scalarNode('filenamePattern')
->cannotBeEmpty()
->defaultValue('{datetime:U}.{feature_file}.feature_{step_line}.{ext}')->end()
->scalarNode('filenamePatternFailed')
->cannotBeEmpty()
->defaultValue('{datetime:U}.{fail_prefix}{feature_file}.feature_{step_line}.{ext}')->end();
}

/**
Expand All @@ -72,6 +78,8 @@ public function load(ContainerBuilder $container, array $config): void
$config['fail'],
$config['fail_prefix'],
$config['purge'],
$config['filenamePattern'],
$config['filenamePatternFailed'],
]);
$definition->addTag(ContextExtension::INITIALIZER_TAG, ['priority' => 0]);
$container->setDefinition('drevops_screenshot.screenshot_context_initializer', $definition);
Expand Down

0 comments on commit 77008ff

Please sign in to comment.