diff --git a/src/Codeception/Reporter/HtmlReporter.php b/src/Codeception/Reporter/HtmlReporter.php
index 131a369eba..ba00c7227d 100644
--- a/src/Codeception/Reporter/HtmlReporter.php
+++ b/src/Codeception/Reporter/HtmlReporter.php
@@ -20,6 +20,8 @@
use SebastianBergmann\Timer\Timer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use function trigger_error;
+
class HtmlReporter implements EventSubscriberInterface
{
use StaticEventsTrait;
@@ -45,8 +47,6 @@ class HtmlReporter implements EventSubscriberInterface
protected string $templatePath;
- protected array $failures = [];
-
private string $reportFile;
private Timer $timer;
@@ -88,41 +88,52 @@ public function beforeSuite(SuiteEvent $event): void
public function testSuccess(TestEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioSuccess');
+ $this->printTestEvent($event, 'scenarioSuccess');
}
public function testError(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioFailed');
+ $this->printTestEvent($event, 'scenarioFailed');
}
public function testFailure(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioFailed');
+ $this->printTestEvent($event, 'scenarioFailed');
}
public function testWarning(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioSuccess');
+ $this->printTestEvent($event, 'scenarioSuccess');
}
public function testSkipped(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioSkipped');
+ $this->printTestEvent($event, 'scenarioSkipped');
}
public function testIncomplete(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioIncomplete');
+ $this->printTestEvent($event, 'scenarioIncomplete');
}
public function testUseless(FailEvent $event): void
{
- $this->printTestResult($event->getTest(), $event->getTime(), 'scenarioUseless');
+ $this->printTestEvent($event, 'scenarioUseless');
}
- public function printTestResult(Test $test, float $time, string $scenarioStatus): void
+ 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) {
@@ -154,23 +165,88 @@ public function printTestResult(Test $test, float $time, string $scenarioStatus)
}
}
+ $png = '';
+ $html = '';
+ if ($test instanceof TestInterface) {
+ $reports = $test->getMetadata()->getReports();
+ if (isset($reports['png'])) {
+ $localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir());
+ $png = "
|
";
+ }
+ if (isset($reports['html'])) {
+ $localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir());
+ $html = "See HTML snapshot of a failed page |
";
+ }
+ }
+
+ $toggle = $stepsBuffer ? '+' : '';
+
+ $testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test)), ENT_QUOTES | ENT_SUBSTITUTE);
+ $testString = preg_replace('~^([\s\w\\\]+):\s~', '$1 » ', $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),
+ ]
+ );
- $failures = '';
- $name = Descriptor::getTestSignatureUnique($test);
- if (isset($this->failures[$name])) {
- $failTemplate = new Template(
- $this->templatePath . 'fail.html'
- );
- foreach ($this->failures[$name] as $failure) {
- $failTemplate->setVar(['fail' => nl2br($failure)]);
- $failures .= $failTemplate->render() . PHP_EOL;
+ $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);
}
- $this->failures[$name] = [];
}
+ $scenarioTemplate = new Template(
+ $this->templatePath . 'scenario.html'
+ );
+
$png = '';
$html = '';
if ($test instanceof TestInterface) {
@@ -197,7 +273,7 @@ public function printTestResult(Test $test, float $time, string $scenarioStatus)
'scenarioStatus' => $scenarioStatus,
'steps' => $stepsBuffer,
'toggle' => $toggle,
- 'failure' => $failures,
+ 'failure' => '',
'png' => $png,
'html' => $html,
'time' => round($time, 2)
diff --git a/src/Codeception/Suite.php b/src/Codeception/Suite.php
index ef4458fb0c..5d30c5fa62 100644
--- a/src/Codeception/Suite.php
+++ b/src/Codeception/Suite.php
@@ -111,7 +111,7 @@ public function run(ResultAggregator $result): void
$this->dispatcher->dispatch($failEvent, Events::TEST_INCOMPLETE);
}
- $this->endTest($test, $result, 0);
+ $this->dispatcher->dispatch(new TestEvent($test, 0), Events::TEST_END);
continue;
}
}
@@ -216,12 +216,6 @@ protected function fire(string $eventType, TestEvent $event): void
$this->dispatcher->dispatch($event, $eventType);
}
- private function endTest(Test $test, ResultAggregator $result, float $time): void
- {
- $this->dispatcher->dispatch(new TestEvent($test, $time), Events::TEST_END);
- $result->addSuccessful($test);
- }
-
public function addTest(Test $test): void
{
$this->tests [] = $test;
diff --git a/src/Codeception/Test/Test.php b/src/Codeception/Test/Test.php
index c082c48b5b..65ed7d9b9e 100644
--- a/src/Codeception/Test/Test.php
+++ b/src/Codeception/Test/Test.php
@@ -206,6 +206,7 @@ final public function realRun(ResultAggregator $result): void
}
if ($eventType === Events::TEST_SUCCESS) {
+ $result->addSuccessful($this);
$this->fire($eventType, new TestEvent($this, $time));
} else {
$this->fire($eventType, new FailEvent($this, $e, $time));
@@ -223,7 +224,6 @@ final public function realRun(ResultAggregator $result): void
$this->fire(Events::TEST_AFTER, new TestEvent($this, $time));
$this->eventDispatcher->dispatch(new TestEvent($this, $time), Events::TEST_END);
- $result->addSuccessful($this, $time);
}
public function getResultAggregator(): ResultAggregator
diff --git a/tests/cli/RunCest.php b/tests/cli/RunCest.php
index 31e40248b1..e33144482f 100755
--- a/tests/cli/RunCest.php
+++ b/tests/cli/RunCest.php
@@ -779,6 +779,9 @@ public function runHtmlWithPhpBrowserCheckReport(CliGuy $I)
$I->seeInShellOutput('Response: ' . $expectedReportAbsFilename);
$I->seeFileFound('report.html', $expectedRelReportPath);
$I->seeInThisFile("See HTML snapshot of a failed page");
+ $I->seeInThisFile('Failed asserting that on page /');
+ $I->seeInThisFile('0 | ');
+ $I->seeInThisFile('0 | ');
}
private function htmlReportRegexCheckProvider(): array