This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,303 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\PHPUnit\ResultPrinter as CodeceptionResultPrinter;
use Codeception\Step;
use Codeception\Step\Meta;
use Codeception\Test\Descriptor;
use Codeception\Test\Interfaces\ScenarioDriven;
use Codeception\TestInterface;
use Codeception\Util\PathResolver;
class HTML extends CodeceptionResultPrinter
{
/**
* @var boolean
*/
protected $printsHTML = true;
/**
* @var integer
*/
protected $id = 0;
/**
* @var string
*/
protected $scenarios = '';
/**
* @var string
*/
protected $templatePath;
/**
* @var int
*/
protected $timeTaken = 0;
protected $failures = [];
/**
* Constructor.
*
* @param mixed $out
* @throws InvalidArgumentException
*/
public function __construct($out = null)
{
parent::__construct($out);
$this->templatePath = sprintf(
'%s%stemplate%s',
__DIR__,
DIRECTORY_SEPARATOR,
DIRECTORY_SEPARATOR
);
}
/**
* Handler for 'start class' event.
*
* @param string $name
*/
protected function startClass($name)
{
}
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
$steps = [];
$success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
if ($success) {
$this->successful++;
}
if ($test instanceof ScenarioDriven) {
$steps = $test->getScenario()->getSteps();
}
$this->timeTaken += $time;
switch ($this->testStatus) {
case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE:
$scenarioStatus = 'scenarioFailed';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED:
$scenarioStatus = 'scenarioSkipped';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE:
$scenarioStatus = 'scenarioIncomplete';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR:
$scenarioStatus = 'scenarioFailed';
break;
default:
$scenarioStatus = 'scenarioSuccess';
}
$stepsBuffer = '';
$subStepsBuffer = '';
$subStepsRendered = [];
foreach ($steps as $step) {
if ($step->getMetaStep()) {
$subStepsRendered[$step->getMetaStep()->getAction()][] = $this->renderStep($step);
}
}
foreach ($steps as $step) {
if ($step->getMetaStep()) {
if (! empty($subStepsRendered[$step->getMetaStep()->getAction()])) {
$subStepsBuffer = implode('', $subStepsRendered[$step->getMetaStep()->getAction()]);
unset($subStepsRendered[$step->getMetaStep()->getAction()]);
$stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer);
}
} else {
$stepsBuffer .= $this->renderStep($step);
}
}
$scenarioTemplate = new \Text_Template(
$this->templatePath . 'scenario.html'
);
$failures = '';
$name = Descriptor::getTestSignatureUnique($test);
if (isset($this->failures[$name])) {
$failTemplate = new \Text_Template(
$this->templatePath . 'fail.html'
);
foreach ($this->failures[$name] as $failure) {
$failTemplate->setVar(['fail' => nl2br($failure)]);
$failures .= $failTemplate->render() . PHP_EOL;
}
$this->failures[$name] = [];
}
$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)));
$testString = preg_replace('~^([\s\w\\\]+):\s~', '<span class="quiet">$1 &raquo;</span> ', $testString);
$scenarioTemplate->setVar(
[
'id' => ++$this->id,
'name' => $testString,
'scenarioStatus' => $scenarioStatus,
'steps' => $stepsBuffer,
'toggle' => $toggle,
'failure' => $failures,
'png' => $png,
'html' => $html,
'time' => round($time, 2)
]
);
$this->scenarios .= $scenarioTemplate->render();
}
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite)
{
$suiteTemplate = new \Text_Template(
$this->templatePath . 'suite.html'
);
if (!$suite->getName()) {
return;
}
$suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]);
$this->scenarios .= $suiteTemplate->render();
}
/**
* Handler for 'end run' event.
*/
protected function endRun()
{
$scenarioHeaderTemplate = new \Text_Template(
$this->templatePath . 'scenario_header.html'
);
$status = !$this->failed
? '<span style="color: green">OK</span>'
: '<span style="color: #e74c3c">FAILED</span>';
$scenarioHeaderTemplate->setVar(
[
'name' => 'Codeception Results',
'status' => $status,
'time' => round($this->timeTaken, 1)
]
);
$header = $scenarioHeaderTemplate->render();
$scenariosTemplate = new \Text_Template(
$this->templatePath . 'scenarios.html'
);
$scenariosTemplate->setVar(
[
'header' => $header,
'scenarios' => $this->scenarios,
'successfulScenarios' => $this->successful,
'failedScenarios' => $this->failed,
'skippedScenarios' => $this->skipped,
'incompleteScenarios' => $this->incomplete
]
);
$this->write($scenariosTemplate->render());
}
/**
* An error occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \Exception $e
* @param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
parent::addError($test, $e, $time);
}
/**
* A failure occurred.
*
* @param \PHPUnit\Framework\Test $test
* @param \PHPUnit\Framework\AssertionFailedError $e
* @param float $time
*/
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time)
{
$this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e);
parent::addFailure($test, $e, $time);
}
/**
* Starts test.
*
* @param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test)
{
$name = Descriptor::getTestSignatureUnique($test);
if (isset($this->failures[$name])) {
// test failed in before hook
return;
}
// start test and mark initialize as passed
parent::startTest($test);
}
/**
* @param $step
* @return string
*/
protected function renderStep(Step $step)
{
$stepTemplate = new \Text_Template($this->templatePath . 'step.html');
$stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']);
return $stepTemplate->render();
}
/**
* @param $metaStep
* @param $substepsBuffer
* @return string
*/
protected function renderSubsteps(Meta $metaStep, $substepsBuffer)
{
$metaTemplate = new \Text_Template($this->templatePath . 'substeps.html');
$metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]);
return $metaTemplate->render();
}
private function cleanMessage($exception)
{
$msg = $exception->getMessage();
$msg = str_replace(['<info>','</info>','<bold>','</bold>'], ['','','',''], $msg);
return htmlentities($msg);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\Lib\Console\Output;
use Codeception\PHPUnit\ConsolePrinter;
use Codeception\PHPUnit\ResultPrinter;
use Codeception\Test\Descriptor;
class Report extends ResultPrinter implements ConsolePrinter
{
/**
* @param \PHPUnit\Framework\Test $test
* @param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
$name = Descriptor::getTestAsString($test);
$success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED);
if ($success) {
$this->successful++;
}
if ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE) {
$status = "\033[41;37mFAIL\033[0m";
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED) {
$status = 'Skipped';
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) {
$status = 'Incomplete';
} elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR) {
$status = 'ERROR';
} else {
$status = 'Ok';
}
if (strlen($name) > 75) {
$name = substr($name, 0, 70);
}
$line = $name . str_repeat('.', 75 - strlen($name));
$line .= $status;
$this->write($line . "\n");
}
protected function endRun()
{
$this->write("\nCodeception Results\n");
$this->write(sprintf(
"Successful: %s. Failed: %s. Incomplete: %s. Skipped: %s",
$this->successful,
$this->failed,
$this->skipped,
$this->incomplete
) . "\n");
}
public function printResult(\PHPUnit\Framework\TestResult $result)
{
}
public function write($buffer)
{
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Codeception\PHPUnit\ResultPrinter;
use Codeception\Event\FailEvent;
use Codeception\Events;
use Codeception\Test\Unit;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
class UI extends \PHPUnit\TextUI\ResultPrinter
{
/**
* @var EventDispatcher
*/
protected $dispatcher;
public function __construct(EventDispatcher $dispatcher, $options, $out = null)
{
parent::__construct($out, $options['verbosity'] > OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never');
$this->dispatcher = $dispatcher;
}
protected function printDefect(\PHPUnit\Framework\TestFailure $defect, $count)
{
$this->write("\n---------\n");
$this->dispatcher->dispatch(
Events::TEST_FAIL_PRINT,
new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count)
);
}
/**
* @param \PHPUnit\Framework\TestFailure $defect
*/
protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect)
{
$this->write($defect->getExceptionAsString());
$this->writeNewLine();
$stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException(), false);
foreach ($stackTrace as $i => $frame) {
if (!isset($frame['file'])) {
continue;
}
$this->write(
sprintf(
"#%d %s(%s)",
$i + 1,
$frame['file'],
isset($frame['line']) ? $frame['line'] : '?'
)
);
$this->writeNewLine();
}
}
public function startTest(\PHPUnit\Framework\Test $test)
{
if ($test instanceof Unit) {
parent::startTest($test);
}
}
public function endTest(\PHPUnit\Framework\Test $test, $time)
{
if ($test instanceof \PHPUnit\Framework\TestCase or $test instanceof \Codeception\Test\Test) {
$this->numAssertions += $test->getNumAssertions();
}
$this->lastTestFailed = false;
}
public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time)
{
$this->lastTestFailed = true;
}
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time)
{
$this->lastTestFailed = true;
}
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time)
{
$this->lastTestFailed = true;
}
}

View File

@@ -0,0 +1,5 @@
<tr >
<td class="error">
{fail}
</td>
</tr>

View File

@@ -0,0 +1,22 @@
<tr class="scenarioRow {scenarioStatus}">
<td>
<p class="{scenarioStatus}" onclick="showHide('{id}', this)">{toggle}
{name} <span style="color: #34495e; font-size: 70%;">{time}s</span></p>
</td>
</tr>
<tr class="scenarioRow {scenarioStatus}">
<td>
<table border="0" width="100%" class="{scenarioStatus} scenarioStepsTable" id="stepContainer{id}">
{steps}
{failure}
{png}
{html}
</table>
</td>
</tr>

View File

@@ -0,0 +1,2 @@
<h1>{name} <small>{status} ({time}s)</small></h1>

View File

@@ -0,0 +1,250 @@
<html>
<head>
<title>Test results</title>
<meta charset='utf-8'>
<link href='http://fonts.googleapis.com/css?family=Varela+Round&v2' rel='stylesheet' type='text/css'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.layout {
margin: 0 auto;
max-width: 1000px;
}
body { font-family: arial, serif; margin: 0; padding: 0; background: #ecf0f1; font-size: 20px; }
h1,h2,h3 { font-family: arial, serif; color: #7f8c8d; }
h1 { font-size: 2.5em; }
h2 { font-size: 1.3em; }
h3 { font-size: 1em; color: #84BBDD; margin: 0.5em 0; }
table { border: none; margin: 0; padding: 0; font-size: 0.9em;}
.scenarioStepsTable .stepName { padding: 5px; }
.scenarioStepsTable td {
background: #fff;
}
.quiet {
color: #333;
font-size: 0.8em;
}
.screenshot {
max-height: 400px;
overflow-y: scroll;
display: block;
}
.screenshot img {
zoom: 0.5;
}
@media (max-width: 1200px) {
#toolbar-filter {
display: none !important;
}
}
.scenarioStepsTable .nostyle {
background: none;
border: none;
}
p {
cursor: pointer;
}
.scenarioRow>td>p {
padding: 5px;
}
.scenarioStepsTable .failedStep {
padding: 10px;
background: #ecf0f1;
border: 2px solid #e74c3c;
border-radius: 0px;
color: #e74c3c;
}
.scenarioStepsTable .error {
background: #999;
padding: 10px;
color: #fff;
border-radius: 0px;
}
.scenarioStepsTable .error a {
color: #eef;
}
.scenarioStepsTable.substeps td {
background: #bdc3c7;
}
.header { font-size: large; font-weight: bold; }
p.scenarioSuccess {
background: rgb(157,213,58); /* Old browsers */
}
.scenario { color: black; }
p.scenarioFailed, p.scenarioError { color: black;
background: #e74c3c
}
table.scenarioFailed tr:last-child { font-weight: bold; }
td.scenarioSuccess { color: green }
td.scenarioFailed { color: red }
.scenarioSkipped { color: teal; }
.scenarioIncomplete { color: gray; }
.scenarioStepsTable { margin-left: 10px; display: none; color: #333; }
#stepContainerSummary {
background: white;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
padding: 20px;
}
.toggle {
background: rgba(255,255,255,0.5);
border-radius: 10px;
display: inline-block;
width: 20px;
height: 20px;
text-align: center;
margin: auto;
color: #666
}
ul#toolbar-filter {
display: block;
position: fixed;
top: 20px;
left: 0px;
padding: 0px;
}
ul#toolbar-filter li {
list-style: none;
text-align: center;
padding: 20px;
background-color: #3498db;
}
ul#toolbar-filter li a, ul#toolbar-filter li a:hover, ul#toolbar-filter li a:visited {
color: #34495e;
text-decoration: none;
}
ul#toolbar-filter li.disabled {
background-color: #bdc3c7;
}
</style>
<script type="text/javascript">
var showAll = true;
function showHide(nodeId, linkObj)
{
var subObj = document.getElementById('stepContainer' + nodeId);
var toggle = linkObj.childNodes[0];
if (toggle.innerHTML != '-') {
toggle.innerHTML = '-';
subObj.style.display='block';
subObj.style.width = '100%';
} else {
toggle.innerHTML = '+';
subObj.style.display='none';
}
}
function showAllScenarios() {
var toolbar = document.getElementById('toolbar-filter');
for (var i = 0; i < toolbar.children.length; i++) {
toolbar.children[i].className = '';
}
var trs = document.getElementsByTagName('tr');
for(var z = 0; z < trs.length; z++) {
trs[z].style.display = '';
}
showAll = true;
}
function toggleScenarios(name, linkObj) {
var links = document.getElementById('toolbar-filter').children;
var rows = document.getElementsByClassName('scenarioRow');
if (showAll) {
for (var i = 0; i < links.length; i++) {
links[i].className = 'disabled';
}
for (var i = 0; i < rows.length; i++) {
rows[i].style.display = 'none';
}
}
showAll = false;
if (linkObj.className == '') {
linkObj.className = 'disabled';
for (var i = 0; i < rows.length; i++) {
if (rows[i].classList.contains(name)) {
rows[i].style.display = 'none';
}
}
return;
}
if (linkObj.className == 'disabled') {
linkObj.className = '';
for (var i = 0; i < rows.length; i++) {
if (rows[i].classList.contains(name)) {
rows[i].style.display = 'table-row';
}
}
return;
}
}
</script>
</head>
<body>
<ul id="toolbar-filter">
<li> <a href="#" title="Show all" onClick="showAllScenarios()">◯</a></li>
<li> <a href="#" title="Successful" onClick="toggleScenarios('scenarioSuccess', this.parentElement)"><strong>✔</strong> {successfulScenarios}</a></li>
<li> <a href="#" title="Failed" onClick="toggleScenarios('scenarioFailed', this.parentElement)"><strong>✗</strong> {failedScenarios}</a></li>
<li> <a href="#" title="Skipped" onClick="toggleScenarios('scenarioSkipped', this.parentElement)"><strong>S</strong> {skippedScenarios}</a></li>
<li> <a href="#" title="Incomplete" onClick="toggleScenarios('scenarioIncomplete', this.parentElement)"><strong>I</strong> {incompleteScenarios}</a></li>
</ul>
<div class="layout">
{header}
<table border="0" style="width: 100%;">
{scenarios}
<tr>
<td>
<h2>Summary</h2>
<div id="stepContainerSummary">
<table border="0">
<tr>
<td width="250" class="scenarioSuccess">Successful scenarios:</td>
<td class="scenarioSuccessValue"><strong>{successfulScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioFailed">Failed scenarios:</td>
<td class="scenarioFailedValue"><strong>{failedScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioSkipped">Skipped scenarios:</td>
<td class="scenarioSkippedValue"><strong>{skippedScenarios}</strong></td>
</tr>
<tr>
<td class="scenarioIncomplete">Incomplete scenarios:</td>
<td class="scenarioIncompleteValue"><strong>{incompleteScenarios}</strong></td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@@ -0,0 +1,4 @@
<tr>
<td class="stepName {error}">&nbsp;&nbsp;&nbsp;&nbsp;{action}</td>
</tr>

View File

@@ -0,0 +1,12 @@
<tr>
<td class="stepName {error}" ><p onclick="showHide('{id}', this)"><span class="toggle">+</span> {metaStep}</p>
</td>
</tr>
<tr>
<td class="nostyle">
<table border="0" width="100%" class="substeps scenarioStepsTable" id="stepContainer{id}">
{steps}
</table>
</td>
</tr>

View File

@@ -0,0 +1,5 @@
<tr>
<td>
<h3>{suite} Tests</h3>
</td>
</tr>