| Current Path : /home/rtorresani/www/vendor/codeception/codeception/src/Codeception/Reporter/ |
| Current File : //home/rtorresani/www/vendor/codeception/codeception/src/Codeception/Reporter/HtmlReporter.php |
<?php
namespace Codeception\Reporter;
use Codeception\Event\FailEvent;
use Codeception\Event\PrintResultEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Lib\Console\Output;
use Codeception\Step;
use Codeception\Step\Meta;
use Codeception\Subscriber\Shared\StaticEventsTrait;
use Codeception\Test\Descriptor;
use Codeception\Test\Interfaces\ScenarioDriven;
use Codeception\Test\Test;
use Codeception\TestInterface;
use Codeception\Util\PathResolver;
use SebastianBergmann\Template\Template;
use SebastianBergmann\Timer\Timer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function trigger_error;
class HtmlReporter implements EventSubscriberInterface
{
use StaticEventsTrait;
/**
* @var array<string, string>
*/
protected static array $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::RESULT_PRINT_AFTER => 'afterResult',
Events::TEST_SUCCESS => 'testSuccess',
Events::TEST_FAIL => 'testFailure',
Events::TEST_ERROR => 'testError',
Events::TEST_INCOMPLETE => 'testIncomplete',
Events::TEST_SKIPPED => 'testSkipped',
Events::TEST_USELESS => 'testUseless',
Events::TEST_WARNING => 'testWarning',
];
protected int $id = 0;
protected string $scenarios = '';
protected string $templatePath;
private string $reportFile;
private Timer $timer;
public function __construct(array $options, private Output $output)
{
$this->reportFile = $options['html'];
if (!codecept_is_path_absolute($this->reportFile)) {
$this->reportFile = codecept_output_dir($this->reportFile);
}
codecept_debug(sprintf("Printing HTML report to %s", $this->reportFile));
$this->templatePath = sprintf(
'%s%stemplate%s',
__DIR__,
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR
);
$this->timer = new Timer();
$this->timer->start();
}
public function beforeSuite(SuiteEvent $event): void
{
$suite = $event->getSuite();
if (!$suite->getName()) {
return;
}
$suiteTemplate = new Template(
$this->templatePath . 'suite.html'
);
$suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]);
$this->scenarios .= $suiteTemplate->render();
}
public function testSuccess(TestEvent $event): void
{
$this->printTestEvent($event, 'scenarioSuccess');
}
public function testError(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioFailed');
}
public function testFailure(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioFailed');
}
public function testWarning(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioSuccess');
}
public function testSkipped(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioSkipped');
}
public function testIncomplete(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioIncomplete');
}
public function testUseless(FailEvent $event): void
{
$this->printTestEvent($event, 'scenarioUseless');
}
public function printTestEvent(TestEvent $event, string $scenarioStatus): void
{
$failure = '';
$test = $event->getTest();
if ($event instanceof FailEvent) {
$failTemplate = new Template(
$this->templatePath . 'fail.html'
);
$failTemplate->setVar(['fail' => nl2br($event->getFail()->getMessage())]);
$failure = $failTemplate->render() . PHP_EOL;
}
$steps = [];
if ($test instanceof ScenarioDriven) {
$steps = $test->getScenario()->getSteps();
}
$stepsBuffer = '';
$subStepsRendered = [];
foreach ($steps as $step) {
$metaStep = $step->getMetaStep();
if ($metaStep) {
$key = $this->getMetaStepKey($metaStep);
$subStepsRendered[$key][] = $this->renderStep($step);
}
}
foreach ($steps as $step) {
$metaStep = $step->getMetaStep();
if ($metaStep) {
$key = $this->getMetaStepKey($metaStep);
if (! empty($subStepsRendered[$key])) {
$subStepsBuffer = implode('', $subStepsRendered[$key]);
unset($subStepsRendered[$key]);
$stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
}
} else {
$stepsBuffer .= $this->renderStep($step);
}
}
$png = '';
$html = '';
if ($test instanceof TestInterface) {
$reports = $test->getMetadata()->getReports();
if (isset($reports['png'])) {
$localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir());
$png = "<tr><td class='error'><div class='screenshot'><img src='$localPath' alt='failure screenshot'></div></td></tr>";
}
if (isset($reports['html'])) {
$localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir());
$html = "<tr><td class='error'>See <a href='$localPath' target='_blank'>HTML snapshot</a> of a failed page</td></tr>";
}
}
$toggle = $stepsBuffer ? '<span class="toggle">+</span>' : '';
$testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test)), ENT_QUOTES | ENT_SUBSTITUTE);
$testString = preg_replace('~^([\s\w\\\]+):\s~', '<span class="quiet">$1 »</span> ', $testString);
$scenarioTemplate = new Template(
$this->templatePath . 'scenario.html'
);
$scenarioTemplate->setVar(
[
'id' => ++$this->id,
'name' => $testString,
'scenarioStatus' => $scenarioStatus,
'steps' => $stepsBuffer,
'toggle' => $toggle,
'failure' => $failure,
'png' => $png,
'html' => $html,
'time' => round($event->getTime(), 2),
]
);
$this->scenarios .= $scenarioTemplate->render();
}
public function printTestResult(Test $test, float $time, string $scenarioStatus): void
{
//keep this method for backwards compatibility, remove in Codeception 6.0
trigger_error(
__METHOD__ . ' is deprecated, please use printTestEvent instead',
E_USER_DEPRECATED,
);
$steps = [];
if ($test instanceof ScenarioDriven) {
$steps = $test->getScenario()->getSteps();
}
$stepsBuffer = '';
$subStepsRendered = [];
foreach ($steps as $step) {
$metaStep = $step->getMetaStep();
if ($metaStep) {
$key = $this->getMetaStepKey($metaStep);
$subStepsRendered[$key][] = $this->renderStep($step);
}
}
foreach ($steps as $step) {
$metaStep = $step->getMetaStep();
if ($metaStep) {
$key = $this->getMetaStepKey($metaStep);
if (! empty($subStepsRendered[$key])) {
$subStepsBuffer = implode('', $subStepsRendered[$key]);
unset($subStepsRendered[$key]);
$stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
}
} else {
$stepsBuffer .= $this->renderStep($step);
}
}
$scenarioTemplate = new Template(
$this->templatePath . 'scenario.html'
);
$png = '';
$html = '';
if ($test instanceof TestInterface) {
$reports = $test->getMetadata()->getReports();
if (isset($reports['png'])) {
$localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir());
$png = "<tr><td class='error'><div class='screenshot'><img src='$localPath' alt='failure screenshot'></div></td></tr>";
}
if (isset($reports['html'])) {
$localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir());
$html = "<tr><td class='error'>See <a href='$localPath' target='_blank'>HTML snapshot</a> of a failed page</td></tr>";
}
}
$toggle = $stepsBuffer ? '<span class="toggle">+</span>' : '';
$testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test)), ENT_QUOTES | ENT_SUBSTITUTE);
$testString = preg_replace('~^([\s\w\\\]+):\s~', '<span class="quiet">$1 »</span> ', $testString);
$scenarioTemplate->setVar(
[
'id' => ++$this->id,
'name' => $testString,
'scenarioStatus' => $scenarioStatus,
'steps' => $stepsBuffer,
'toggle' => $toggle,
'failure' => '',
'png' => $png,
'html' => $html,
'time' => round($time, 2)
]
);
$this->scenarios .= $scenarioTemplate->render();
}
private function getMetaStepKey(Meta $metaStep): string
{
$key = '';
$filePath = $metaStep->getFilePath();
if ($filePath !== null) {
$key = $filePath;
$lineNumber = $metaStep->getLineNumber();
if ($lineNumber !== null) {
$key .= ':' . $lineNumber;
}
}
return $key . $metaStep->getAction();
}
protected function renderStep(Step $step): string
{
$stepTemplate = new Template($this->templatePath . 'step.html');
$stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']);
return $stepTemplate->render();
}
protected function renderSubsteps(Meta $metaStep, string $substepsBuffer): string
{
$metaTemplate = new Template($this->templatePath . 'substeps.html');
$metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]);
return $metaTemplate->render();
}
public function afterResult(PrintResultEvent $event): void
{
$timeTaken = $this->timer->stop()->asString();
$result = $event->getResult();
$scenarioHeaderTemplate = new Template(
$this->templatePath . 'scenario_header.html'
);
$status = $result->wasSuccessfulIgnoringWarnings()
? '<span style="color: green">OK</span>'
: '<span style="color: #e74c3c">FAILED</span>';
$scenarioHeaderTemplate->setVar(
[
'name' => 'Codeception Results',
'status' => $status,
'time' => $timeTaken
]
);
$header = $scenarioHeaderTemplate->render();
$scenariosTemplate = new Template(
$this->templatePath . 'scenarios.html'
);
$scenariosTemplate->setVar(
[
'header' => $header,
'scenarios' => $this->scenarios,
'successfulScenarios' => $result->successfulCount(),
'failedScenarios' => $result->failureCount(),
'skippedScenarios' => $result->skippedCount(),
'incompleteScenarios' => $result->incompleteCount(),
'uselessScenarios' => $result->uselessCount(),
]
);
file_put_contents($this->reportFile, $scenariosTemplate->render());
$this->output->message(
"- <bold>HTML</bold> report generated in <comment>file://%s</comment>",
$this->reportFile
)->writeln();
}
}