Skip to content
Open
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ default:
fileNamePrefix: report
resultFilePerSuite: true
outputDir: %paths.base%/build/tests
screenshotExtension: Bex\Behat\ScreenshotExtension
```

Then you can run:
Expand All @@ -49,6 +50,7 @@ bin/behat -f cucumber_json
Only applicable when `resultFilePerSuite` is not enabled.
- `resultFilePerSuite` _(optional)_: The default behaviour is to generate a single report named `all.json`.
If this option is set to `true`, a report will be created per behat suite.
- `screenshotExtension` _(optional)_: The name of the extension to be used to take screenshots.

## Licence

Expand Down
9 changes: 6 additions & 3 deletions features/calculator.feature
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ Feature: Calculator example
"result": {
"duration": 12345,
"status": "passed"
}
},
"embeddings": []
},
{
"keyword": "When",
Expand All @@ -66,7 +67,8 @@ Feature: Calculator example
"result": {
"duration": 12345,
"status": "passed"
}
},
"embeddings": []
},
{
"keyword": "Then",
Expand All @@ -83,7 +85,8 @@ Feature: Calculator example
"result": {
"duration": 12345,
"status": "passed"
}
},
"embeddings": []
}
],
"type": "scenario"
Expand Down
36 changes: 24 additions & 12 deletions features/eat-cukes.feature
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::passing1()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -68,7 +69,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::eat()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -85,7 +87,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::hungry()"
}
},
"embeddings": []
}
],
"type": "scenario_outline"
Expand All @@ -112,7 +115,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::passing1()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -129,7 +133,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::eat()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -147,7 +152,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::hungry()"
}
},
"embeddings": []
}
],
"type": "scenario_outline"
Expand All @@ -174,7 +180,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::passing1()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -191,7 +198,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::eat()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -209,7 +217,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::hungry()"
}
},
"embeddings": []
}
],
"type": "scenario_outline"
Expand All @@ -236,7 +245,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::passing1()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -253,7 +263,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::eat()"
}
},
"embeddings": []
},
{
"result": {
Expand All @@ -270,7 +281,8 @@ Feature: Eat cukes Example
}
],
"location": "ExampleFeatureContext::hungry()"
}
},
"embeddings": []
}
],
"type": "scenario_outline"
Expand Down
19 changes: 19 additions & 0 deletions src/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function configure(ArrayNodeDefinition $builder): void
$builder->children()->scalarNode('fileNamePrefix')->defaultValue('');
$builder->children()->scalarNode('outputDir')->defaultValue('build/tests');
$builder->children()->scalarNode('fileName');
$builder->children()->scalarNode('screenshotExtension');
$builder->children()->booleanNode('resultFilePerSuite')->defaultFalse();
}

Expand All @@ -41,6 +42,24 @@ public function load(ContainerBuilder $container, array $config): void
$definition->addArgument($config['fileNamePrefix']);
$definition->addArgument($config['outputDir']);

// Integration with other Behat extensions that provide screenshot services.
$imageUploaderService = null;
if (!empty($config['screenshotExtension'])) {
$tags = $container->findTaggedServiceIds('screenshot.service');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not check the referenced package too deeply, but I imagine that this service tag is introduced by the other package?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foreach (array_keys($tags) as $id) {
// Check if the container has the definitions for the service.
if ($container->hasDefinition($id)) {
$service = $container->get($id);
// Check if the configuration for the screenshot extension matches the namespace of the service.
if (strpos(get_class($service), $config['screenshotExtension']) !== false) {
$imageUploaderService = $service;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code leads to the last of all matching services being selected as imageUploaderService, not the first. I assume this is intended behaviour?
If so, a short comment would be nice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was not intentional. I added a break.
If you think we should change this behaviour, please let me know.

break;
}
}
}
}
$definition->addArgument($imageUploaderService);

if (!empty($config['fileName'])) {
$definition->addMethodCall('setFileName', [$config['fileName']]);
}
Expand Down
39 changes: 38 additions & 1 deletion src/Formatter/Formatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,17 @@ class Formatter implements FormatterInterface
/** @var bool */
private $resultFilePerSuite = false;

public function __construct(string $fileNamePrefix, string $outputDir)
/**
* The screenshot service, if available.
*/
private $screenshotService;

public function __construct(string $fileNamePrefix, string $outputDir, $screenshotService = null)
{
$this->renderer = new JsonRenderer($this);
$this->printer = new FileOutputPrinter($fileNamePrefix, $outputDir);
$this->timer = new Timer();
$this->screenshotService = $screenshotService;
}

/** @inheritdoc */
Expand All @@ -74,6 +80,35 @@ public static function getSubscribedEvents(): array
];
}

/**
* Attaches screenshots to steps.
*/
private function attachScreenshots() {
if (!$this->screenshotService) {
return;
}

$steps = $this->currentScenario->getSteps();
if (empty($steps)) {
return;
}

$files = $this->screenshotService->getImages();
if (empty($files)) {
return;
}
array_reverse($files);

foreach ($steps as &$step) {
if ($step->getResultCode() !== 0) {
$file = array_pop($files);
if (!empty($file)) {
$step->addEmbedding($file);
}
}
}
}

/** @inheritdoc */
public function setFileName($fileName): void
{
Expand Down Expand Up @@ -225,6 +260,7 @@ public function onAfterScenarioTested(BehatEvent\AfterScenarioTested $event): vo
$this->currentFeature->addPassedScenario();
} else {
$this->currentFeature->addFailedScenario();
$this->attachScreenshots();
}

$this->currentScenario->setPassed($event->getTestResult()->isPassed());
Expand Down Expand Up @@ -271,6 +307,7 @@ public function onAfterOutlineTested(BehatEvent\AfterOutlineTested $event): void
$this->currentFeature->addPassedScenario();
} else {
$this->currentFeature->addFailedScenario();
$this->attachScreenshots();
}

$example->setPassed($scenarioPassed);
Expand Down
25 changes: 25 additions & 0 deletions src/Node/Step.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class Step
*/
private $output;

/**
* @var array
*/
private $embeddings = [];

/**
* @var ?Definition
*/
Expand Down Expand Up @@ -165,6 +170,26 @@ public function setLine(int $line): void
$this->line = $line;
}

/**
* @return array
*/
public function getEmbeddings()
{
return $this->embeddings;
}

/**
* @param string $url
*/
public function addEmbedding($url)
{
$this->embeddings[] = [
'mime_type' => 'image/url',
'name' => 'Screenshot',
'data' => base64_encode($url)
];
}

/**
* @return mixed
*/
Expand Down
1 change: 1 addition & 0 deletions src/Renderer/JsonRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ protected function processStep(Node\Step $step): array
'line' => $step->getLine(),
'match' => $step->getMatch(),
'result' => $step->getProcessedResult(),
'embeddings' => $step->getEmbeddings(),
];
}

Expand Down