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,110 @@
<?php
namespace Codeception\Extension;
use Codeception\Event\FailEvent;
use Codeception\Events;
use Codeception\Extension;
use Codeception\Subscriber\Console;
/**
* DotReporter provides less verbose output for test execution.
* Like PHPUnit printer it prints dots "." for successful testes and "F" for failures.
*
* ![](https://cloud.githubusercontent.com/assets/220264/26132800/4d23f336-3aab-11e7-81ba-2896a4c623d2.png)
*
* ```bash
* ..........
* ..........
* ..........
* ..........
* ..........
* ..........
* ..........
* ..........
*
* Time: 2.07 seconds, Memory: 20.00MB
*
* OK (80 tests, 124 assertions)
* ```
*
*
* Enable this reporter with `--ext option`
*
* ```
* codecept run --ext DotReporter
* ```
*
* Failures and Errors are printed by a standard Codeception reporter.
* Use this extension as an example for building custom reporters.
*/
class DotReporter extends Extension
{
/**
* @var Console
*/
protected $standardReporter;
protected $errors = [];
protected $failures = [];
protected $width = 10;
protected $currentPos = 0;
public function _initialize()
{
$this->options['silent'] = false; // turn on printing for this extension
$this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else
$this->standardReporter = new Console($this->options);
$this->width = $this->standardReporter->detectWidth();
}
// we are listening for events
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::TEST_SUCCESS => 'success',
Events::TEST_FAIL => 'fail',
Events::TEST_ERROR => 'error',
Events::TEST_SKIPPED => 'skipped',
Events::TEST_FAIL_PRINT => 'printFailed'
];
public function beforeSuite()
{
$this->writeln("");
}
public function success()
{
$this->printChar('.');
}
public function fail(FailEvent $e)
{
$this->printChar("<error>F</error>");
}
public function error(FailEvent $e)
{
$this->printChar('<error>E</error>');
}
public function skipped()
{
$this->printChar('S');
}
protected function printChar($char)
{
if ($this->currentPos >= $this->width) {
$this->writeln('');
$this->currentPos = 0;
}
$this->write($char);
$this->currentPos++;
}
public function printFailed(FailEvent $event)
{
$this->standardReporter->printFail($event);
}
}

123
vendor/codeception/base/ext/Logger.php vendored Normal file
View File

@@ -0,0 +1,123 @@
<?php
namespace Codeception\Extension;
use Codeception\Event\FailEvent;
use Codeception\Event\StepEvent;
use Codeception\Event\SuiteEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Exception\ConfigurationException;
use Codeception\Extension;
use Codeception\Test\Descriptor;
use Monolog\Handler\RotatingFileHandler;
/**
* Log suites/tests/steps using Monolog library.
* Monolog should be installed additionally by Composer.
*
* ```
* composer require monolog/monolog
* ```
*
* Steps are logged into `tests/_output/codeception.log`
*
* To enable this module add to your `codeception.yml`:
*
* ``` yaml
* extensions:
* enabled: [Codeception\Extension\Logger]
* ```
*
* #### Config
*
* * `max_files` (default: 3) - how many log files to keep
*
*/
class Logger extends Extension
{
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::TEST_BEFORE => 'beforeTest',
Events::TEST_AFTER => 'afterTest',
Events::TEST_END => 'endTest',
Events::STEP_BEFORE => 'beforeStep',
Events::TEST_FAIL => 'testFail',
Events::TEST_ERROR => 'testError',
Events::TEST_INCOMPLETE => 'testIncomplete',
Events::TEST_SKIPPED => 'testSkipped',
];
protected $logHandler;
/**
* @var \Monolog\Logger
*/
protected $logger;
protected $path;
protected $config = ['max_files' => 3];
public function _initialize()
{
if (!class_exists('\Monolog\Logger')) {
throw new ConfigurationException("Logger extension requires Monolog library to be installed");
}
$this->path = $this->getLogDir();
// internal log
$logHandler = new RotatingFileHandler($this->path . 'codeception.log', $this->config['max_files']);
$this->logger = new \Monolog\Logger('Codeception');
$this->logger->pushHandler($logHandler);
}
public function beforeSuite(SuiteEvent $e)
{
$suite = str_replace('\\', '_', $e->getSuite()->getName());
$this->logHandler = new RotatingFileHandler($this->path . $suite, $this->config['max_files']);
}
public function beforeTest(TestEvent $e)
{
$this->logger = new \Monolog\Logger(Descriptor::getTestFileName($e->getTest()));
$this->logger->pushHandler($this->logHandler);
$this->logger->info('------------------------------------');
$this->logger->info("STARTED: " . ucfirst(Descriptor::getTestAsString($e->getTest())));
}
public function afterTest(TestEvent $e)
{
}
public function endTest(TestEvent $e)
{
$this->logger->info("PASSED");
}
public function testFail(FailEvent $e)
{
$this->logger->alert($e->getFail()->getMessage());
$this->logger->info("# FAILED #");
}
public function testError(FailEvent $e)
{
$this->logger->alert($e->getFail()->getMessage());
$this->logger->info("# ERROR #");
}
public function testSkipped(FailEvent $e)
{
$this->logger->info("# Skipped #");
}
public function testIncomplete(FailEvent $e)
{
$this->logger->info("# Incomplete #");
}
public function beforeStep(StepEvent $e)
{
$this->logger->info((string) $e->getStep());
}
}

221
vendor/codeception/base/ext/README.md vendored Normal file
View File

@@ -0,0 +1,221 @@
# Official Extensions
## DotReporter
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/DotReporter.php)
DotReporter provides less verbose output for test execution.
Like PHPUnit printer it prints dots "." for successful testes and "F" for failures.
![](https://cloud.githubusercontent.com/assets/220264/26132800/4d23f336-3aab-11e7-81ba-2896a4c623d2.png)
```bash
..........
..........
..........
..........
..........
..........
..........
..........
Time: 2.07 seconds, Memory: 20.00MB
OK (80 tests, 124 assertions)
```
Enable this reporter with `--ext option`
```
codecept run --ext DotReporter
```
Failures and Errors are printed by a standard Codeception reporter.
Use this extension as an example for building custom reporters.
## Logger
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/Logger.php)
Log suites/tests/steps using Monolog library.
Monolog should be installed additionally by Composer.
```
composer require monolog/monolog
```
Steps are logged into `tests/_output/codeception.log`
To enable this module add to your `codeception.yml`:
``` yaml
extensions:
enabled: [Codeception\Extension\Logger]
```
#### Config
* `max_files` (default: 3) - how many log files to keep
## Recorder
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/Recorder.php)
Saves a screenshot of each step in acceptance tests and shows them as a slideshow on one HTML page (here's an [example](http://codeception.com/images/recorder.gif))
Activated only for suites with WebDriver module enabled.
The screenshots are saved to `tests/_output/record_*` directories, open `index.html` to see them as a slideshow.
#### Installation
Add this to the list of enabled extensions in `codeception.yml` or `acceptance.suite.yml`:
``` yaml
extensions:
enabled:
- Codeception\Extension\Recorder
```
#### Configuration
* `delete_successful` (default: true) - delete screenshots for successfully passed tests (i.e. log only failed and errored tests).
* `module` (default: WebDriver) - which module for screenshots to use. Set `AngularJS` if you want to use it with AngularJS module. Generally, the module should implement `Codeception\Lib\Interfaces\ScreenshotSaver` interface.
* `ignore_steps` (default: []) - array of step names that should not be recorded, * wildcards supported
#### Examples:
``` yaml
extensions:
enabled:
- Codeception\Extension\Recorder:
module: AngularJS # enable for Angular
delete_successful: false # keep screenshots of successful tests
ignore_steps: [have, grab*]
```
## RunBefore
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/RunBefore.php)
Extension for execution of some processes before running tests.
Processes can be independent and dependent.
Independent processes run independently of each other.
Dependent processes run sequentially one by one.
Can be configured in suite config:
```yaml
# acceptance.suite.yml
extensions:
enabled:
- Codeception\Extension\RunBefore:
- independent_process_1
-
- dependent_process_1_1
- dependent_process_1_2
- independent_process_2
-
- dependent_process_2_1
- dependent_process_2_2
```
HINT: you can use different configurations per environment.
## RunFailed
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/RunFailed.php)
Saves failed tests into tests/log/failed in order to rerun failed tests.
To rerun failed tests just run the `failed` group:
```
php codecept run -g failed
```
To change failed group name add:
```
--override "extensions: config: Codeception\Extension\RunFailed: fail-group: another_group1"
```
Remember: if you run tests and they generated custom-named fail group, to run this group, you should add override too
Starting from Codeception 2.1 **this extension is enabled by default**.
``` yaml
extensions:
enabled: [Codeception\Extension\RunFailed]
```
On each execution failed tests are logged and saved into `tests/_output/failed` file.
## RunProcess
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/RunProcess.php)
Extension to start and stop processes per suite.
Can be used to start/stop selenium server, chromedriver, phantomjs, mailcatcher, etc.
Can be configured in suite config:
```yaml
# acceptance.suite.yml
extensions:
enabled:
- Codeception\Extension\RunProcess:
- chromedriver
```
Multiple parameters can be passed as array:
```yaml
# acceptance.suite.yml
extensions:
enabled:
- Codeception\Extension\RunProcess:
- php -S 127.0.0.1:8000 -t tests/data/app
- java -jar ~/selenium-server.jar
```
In the end of a suite all launched processes will be stopped.
To wait for the process to be launched use `sleep` option.
In this case you need configuration to be specified as object:
```yaml
extensions:
enabled:
- Codeception\Extension\RunProcess:
0: java -jar ~/selenium-server.jar
1: mailcatcher
sleep: 5 # wait 5 seconds for processes to boot
```
HINT: you can use different configurations per environment.
## SimpleReporter
[See Source](https://github.com/Codeception/Codeception/blob/2.4/ext/SimpleReporter.php)
This extension demonstrates how you can implement console output of your own.
Recommended to be used for development purposes only.

438
vendor/codeception/base/ext/Recorder.php vendored Normal file
View File

@@ -0,0 +1,438 @@
<?php
namespace Codeception\Extension;
use Codeception\Event\StepEvent;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Exception\ExtensionException;
use Codeception\Lib\Interfaces\ScreenshotSaver;
use Codeception\Module\WebDriver;
use Codeception\Step;
use Codeception\Step\Comment as CommentStep;
use Codeception\Test\Descriptor;
use Codeception\Util\FileSystem;
use Codeception\Util\Template;
/**
* Saves a screenshot of each step in acceptance tests and shows them as a slideshow on one HTML page (here's an [example](http://codeception.com/images/recorder.gif))
* Activated only for suites with WebDriver module enabled.
*
* The screenshots are saved to `tests/_output/record_*` directories, open `index.html` to see them as a slideshow.
*
* #### Installation
*
* Add this to the list of enabled extensions in `codeception.yml` or `acceptance.suite.yml`:
*
* ``` yaml
* extensions:
* enabled:
* - Codeception\Extension\Recorder
* ```
*
* #### Configuration
*
* * `delete_successful` (default: true) - delete screenshots for successfully passed tests (i.e. log only failed and errored tests).
* * `module` (default: WebDriver) - which module for screenshots to use. Set `AngularJS` if you want to use it with AngularJS module. Generally, the module should implement `Codeception\Lib\Interfaces\ScreenshotSaver` interface.
* * `ignore_steps` (default: []) - array of step names that should not be recorded, * wildcards supported
*
*
* #### Examples:
*
* ``` yaml
* extensions:
* enabled:
* - Codeception\Extension\Recorder:
* module: AngularJS # enable for Angular
* delete_successful: false # keep screenshots of successful tests
* ignore_steps: [have, grab*]
* ```
*
*/
class Recorder extends \Codeception\Extension
{
protected $config = [
'delete_successful' => true,
'module' => 'WebDriver',
'template' => null,
'animate_slides' => true,
'ignore_steps' => [],
];
protected $template = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recorder Result</title>
<!-- Bootstrap Core CSS -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<style>
html,
body {
height: 100%;
}
.carousel,
.item,
.active {
height: 100%;
}
.navbar {
margin-bottom: 0px !important;
}
.carousel-caption {
background: rgba(0,0,0,0.8);
padding-bottom: 50px !important;
}
.carousel-caption.error {
background: #c0392b !important;
}
.carousel-inner {
height: 100%;
}
.fill {
width: 100%;
height: 100%;
text-align: center;
overflow-y: scroll;
background-position: top;
-webkit-background-size: cover;
-moz-background-size: cover;
background-size: cover;
-o-background-size: cover;
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="#">{{feature}}
<small>{{test}}</small>
</a>
</div>
</nav>
<header id="steps" class="carousel{{carousel_class}}">
<!-- Indicators -->
<ol class="carousel-indicators">
{{indicators}}
</ol>
<!-- Wrapper for Slides -->
<div class="carousel-inner">
{{slides}}
</div>
<!-- Controls -->
<a class="left carousel-control" href="#steps" data-slide="prev">
<span class="icon-prev"></span>
</a>
<a class="right carousel-control" href="#steps" data-slide="next">
<span class="icon-next"></span>
</a>
</header>
<!-- jQuery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- Script to Activate the Carousel -->
<script>
$('.carousel').carousel({
wrap: true,
interval: false
})
$(document).bind('keyup', function(e) {
if(e.keyCode==39){
jQuery('a.carousel-control.right').trigger('click');
}
else if(e.keyCode==37){
jQuery('a.carousel-control.left').trigger('click');
}
});
</script>
</body>
</html>
EOF;
protected $indicatorTemplate = <<<EOF
<li data-target="#steps" data-slide-to="{{step}}" {{isActive}}></li>
EOF;
protected $indexTemplate = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recorder Results Index</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="#">Recorded Tests
</a>
</div>
</nav>
<div class="container">
<h1>Record #{{seed}}</h1>
<ul>
{{records}}
</ul>
</div>
</body>
</html>
EOF;
protected $slidesTemplate = <<<EOF
<div class="item {{isActive}}">
<div class="fill">
<img src="{{image}}">
</div>
<div class="carousel-caption {{isError}}">
<h2>{{caption}}</h2>
<small>scroll up and down to see the full page</small>
</div>
</div>
EOF;
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::SUITE_AFTER => 'afterSuite',
Events::TEST_BEFORE => 'before',
Events::TEST_ERROR => 'persist',
Events::TEST_FAIL => 'persist',
Events::TEST_SUCCESS => 'cleanup',
Events::STEP_AFTER => 'afterStep',
];
/**
* @var WebDriver
*/
protected $webDriverModule;
protected $dir;
protected $slides = [];
protected $stepNum = 0;
protected $seed;
protected $recordedTests = [];
protected $errors = [];
protected $errorMessages = [];
public function beforeSuite()
{
$this->webDriverModule = null;
if (!$this->hasModule($this->config['module'])) {
$this->writeln("Recorder is disabled, no available modules");
return;
}
$this->seed = uniqid();
$this->webDriverModule = $this->getModule($this->config['module']);
if (!$this->webDriverModule instanceof ScreenshotSaver) {
throw new ExtensionException(
$this,
'You should pass module which implements Codeception\Lib\Interfaces\ScreenshotSaver interface'
);
}
$this->writeln(sprintf(
"⏺ <bold>Recording</bold> ⏺ step-by-step screenshots will be saved to <info>%s</info>",
codecept_output_dir()
));
$this->writeln("Directory Format: <debug>record_{$this->seed}_{testname}</debug> ----");
}
public function afterSuite()
{
if (!$this->webDriverModule or !$this->dir) {
return;
}
$links = '';
if (count($this->slides)) {
foreach ($this->recordedTests as $link => $url) {
$links .= "<li><a href='$url'>$link</a></li>\n";
}
$indexHTML = (new Template($this->indexTemplate))
->place('seed', $this->seed)
->place('records', $links)
->produce();
file_put_contents(codecept_output_dir() . 'records.html', $indexHTML);
$this->writeln("⏺ Records saved into: <info>file://" . codecept_output_dir() . 'records.html</info>');
}
foreach ($this->errors as $testPath => $screenshotPath) {
while (count($this->errorMessages[$testPath])) {
$this->writeln(array_pop($this->errorMessages[$testPath]));
}
if ($screenshotPath !== null) {
$this->writeln("⏺ Screenshot saved into: <info>file://{$screenshotPath}</info>");
}
}
}
public function before(TestEvent $e)
{
if (!$this->webDriverModule) {
return;
}
$this->dir = null;
$this->stepNum = 0;
$this->slides = [];
$this->errors = [];
$this->errorMessages = [];
$testName = preg_replace('~\W~', '_', Descriptor::getTestAsString($e->getTest()));
$this->dir = codecept_output_dir() . "record_{$this->seed}_$testName";
@mkdir($this->dir);
}
public function cleanup(TestEvent $e)
{
if (!$this->webDriverModule or !$this->dir) {
return;
}
if (!$this->config['delete_successful']) {
$this->persist($e);
return;
}
// deleting successfully executed tests
FileSystem::deleteDir($this->dir);
}
public function persist(TestEvent $e)
{
if (!$this->webDriverModule) {
return;
}
$indicatorHtml = '';
$slideHtml = '';
$testName = preg_replace('~\W~', '_', Descriptor::getTestAsString($e->getTest()));
$testPath = codecept_relative_path(Descriptor::getTestFullName($e->getTest()));
$dir = codecept_output_dir() . "record_{$this->seed}_$testName";
if ($this->dir !== $dir) {
$screenshotPath = "{$dir}/error.png";
@mkdir($dir);
$this->errors = [];
$this->recordedTests = [];
$this->slides = [];
$this->errorMessages[$testPath] = [
"⏺ An error has occurred in <info>{$testName}</info> before any steps could've executed",
];
try {
$this->webDriverModule->webDriver->takeScreenshot($screenshotPath);
$this->errors[$testPath] = $screenshotPath;
} catch (\Exception $exception) {
$this->errors[$testPath] = null;
FileSystem::deleteDir($dir);
}
return;
}
if (!array_key_exists($testPath, $this->errors)) {
foreach ($this->slides as $i => $step) {
$indicatorHtml .= (new Template($this->indicatorTemplate))
->place('step', (int)$i)
->place('isActive', (int)$i ? '' : 'class="active"')
->produce();
$slideHtml .= (new Template($this->slidesTemplate))
->place('image', $i)
->place('caption', $step->getHtml('#3498db'))
->place('isActive', (int)$i ? '' : 'active')
->place('isError', $step->hasFailed() ? 'error' : '')
->produce();
}
$html = (new Template($this->template))
->place('indicators', $indicatorHtml)
->place('slides', $slideHtml)
->place('feature', ucfirst($e->getTest()->getFeature()))
->place('test', Descriptor::getTestSignature($e->getTest()))
->place('carousel_class', $this->config['animate_slides'] ? ' slide' : '')
->produce();
$indexFile = $this->dir . DIRECTORY_SEPARATOR . 'index.html';
file_put_contents($indexFile, $html);
$testName = Descriptor::getTestSignature($e->getTest()). ' - '.ucfirst($e->getTest()->getFeature());
$this->recordedTests[$testName] = substr($indexFile, strlen(codecept_output_dir()));
}
}
public function afterStep(StepEvent $e)
{
if (!$this->webDriverModule or !$this->dir) {
return;
}
if ($e->getStep() instanceof CommentStep) {
return;
}
if ($this->isStepIgnored($e->getStep())) {
return;
}
$filename = str_pad($this->stepNum, 3, "0", STR_PAD_LEFT) . '.png';
try {
$this->webDriverModule->webDriver->takeScreenshot($this->dir . DIRECTORY_SEPARATOR . $filename);
} catch (\Exception $exception) {
$testPath = codecept_relative_path(Descriptor::getTestFullName($e->getTest()));
$this->errors[$testPath] = null;
if (array_key_exists($testPath, $this->errorMessages)) {
$this->errorMessages[$testPath] = array_merge(
$this->errorMessages[$testPath],
["⏺ Unable to capture a screenshot for <info>{$testPath}/{$e->getStep()->getAction()}</info>"]
);
} else {
$this->errorMessages[$testPath] = [
"⏺ Unable to capture a screenshot for <info>{$testPath}/{$e->getStep()->getAction()}</info>",
];
}
return;
}
$this->stepNum++;
$this->slides[$filename] = $e->getStep();
}
/**
* @param Step $step
* @return bool
*/
protected function isStepIgnored($step)
{
foreach ($this->config['ignore_steps'] as $stepPattern) {
$stepRegexp = '/^' . str_replace('*', '.*?', $stepPattern) . '$/i';
if (preg_match($stepRegexp, $step->getAction())) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Codeception\Extension;
use Codeception\Events;
use Codeception\Exception\ExtensionException;
use Codeception\Extension;
use Symfony\Component\Process\Process;
/**
* Extension for execution of some processes before running tests.
*
* Processes can be independent and dependent.
* Independent processes run independently of each other.
* Dependent processes run sequentially one by one.
*
* Can be configured in suite config:
*
* ```yaml
* # acceptance.suite.yml
* extensions:
* enabled:
* - Codeception\Extension\RunBefore:
* - independent_process_1
* -
* - dependent_process_1_1
* - dependent_process_1_2
* - independent_process_2
* -
* - dependent_process_2_1
* - dependent_process_2_2
* ```
*
* HINT: you can use different configurations per environment.
*/
class RunBefore extends Extension
{
protected $config = [];
protected static $events = [
Events::SUITE_BEFORE => 'runBefore'
];
/** @var array[] */
private $processes = [];
public function _initialize()
{
if (!class_exists('Symfony\Component\Process\Process')) {
throw new ExtensionException($this, 'symfony/process package is required');
}
}
public function runBefore()
{
$this->runProcesses();
$this->processMonitoring();
}
private function runProcesses()
{
foreach ($this->config as $item) {
if (is_array($item)) {
$currentCommand = array_shift($item);
$followingCommands = $item;
} else {
$currentCommand = $item;
$followingCommands = [];
}
$process = $this->runProcess($currentCommand);
$this->addProcessToMonitoring($process, $followingCommands);
}
}
/**
* @param string $command
* @return Process
*/
private function runProcess($command)
{
$this->output->debug('[RunBefore] Starting ' . $command);
$process = new Process($command, $this->getRootDir());
$process->start();
return $process;
}
/**
* @param string[] $followingCommands
*/
private function addProcessToMonitoring(Process $process, array $followingCommands)
{
$this->processes[] = [
'instance' => $process,
'following' => $followingCommands
];
}
/**
* @param int $index
*/
private function removeProcessFromMonitoring($index)
{
unset($this->processes[$index]);
}
private function processMonitoring()
{
while (count($this->processes) !== 0) {
$this->checkProcesses();
sleep(1);
}
}
private function checkProcesses()
{
foreach ($this->processes as $index => $process) {
if (!$this->isRunning($process['instance'])) {
$this->output->debug('[RunBefore] Completing ' . $process['instance']->getCommandLine());
$this->runFollowingCommand($process['following']);
$this->removeProcessFromMonitoring($index);
}
}
}
/**
* @param string[] $followingCommands
*/
private function runFollowingCommand(array $followingCommands)
{
if (count($followingCommands) > 0) {
$process = $this->runProcess(array_shift($followingCommands));
$this->addProcessToMonitoring($process, $followingCommands);
}
}
private function isRunning(Process $process)
{
if ($process->isRunning()) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Codeception\Extension;
use Codeception\Event\PrintResultEvent;
use Codeception\Events;
use Codeception\Extension;
use Codeception\Test\Descriptor;
/**
* Saves failed tests into tests/log/failed in order to rerun failed tests.
*
* To rerun failed tests just run the `failed` group:
*
* ```
* php codecept run -g failed
* ```
*
* To change failed group name add:
* ```
* --override "extensions: config: Codeception\Extension\RunFailed: fail-group: another_group1"
* ```
* Remember: if you run tests and they generated custom-named fail group, to run this group, you should add override too
*
* Starting from Codeception 2.1 **this extension is enabled by default**.
*
* ``` yaml
* extensions:
* enabled: [Codeception\Extension\RunFailed]
* ```
*
* On each execution failed tests are logged and saved into `tests/_output/failed` file.
*/
class RunFailed extends Extension
{
public static $events = [
Events::RESULT_PRINT_AFTER => 'saveFailed'
];
/** @var string filename/groupname for failed tests */
protected $group = 'failed';
public function _initialize()
{
if (array_key_exists('fail-group', $this->config) && $this->config['fail-group']) {
$this->group = $this->config['fail-group'];
}
$logPath = str_replace($this->getRootDir(), '', $this->getLogDir()); // get local path to logs
$this->_reconfigure(['groups' => [$this->group => $logPath . $this->group]]);
}
public function saveFailed(PrintResultEvent $e)
{
$file = $this->getLogDir() . $this->group;
$result = $e->getResult();
if ($result->wasSuccessful()) {
if (is_file($file)) {
unlink($file);
}
return;
}
$output = [];
foreach ($result->failures() as $fail) {
$output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest()));
}
foreach ($result->errors() as $fail) {
$output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest()));
}
file_put_contents($file, implode("\n", $output));
}
protected function localizePath($path)
{
$root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR;
if (substr($path, 0, strlen($root)) == $root) {
return substr($path, strlen($root));
}
return $path;
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace Codeception\Extension;
use Codeception\Events;
use Codeception\Exception\ExtensionException;
use Codeception\Extension;
use Symfony\Component\Process\Process;
/**
* Extension to start and stop processes per suite.
* Can be used to start/stop selenium server, chromedriver, phantomjs, mailcatcher, etc.
*
* Can be configured in suite config:
*
* ```yaml
* # acceptance.suite.yml
* extensions:
* enabled:
* - Codeception\Extension\RunProcess:
* - chromedriver
* ```
*
* Multiple parameters can be passed as array:
*
* ```yaml
* # acceptance.suite.yml
*
* extensions:
* enabled:
* - Codeception\Extension\RunProcess:
* - php -S 127.0.0.1:8000 -t tests/data/app
* - java -jar ~/selenium-server.jar
* ```
*
* In the end of a suite all launched processes will be stopped.
*
* To wait for the process to be launched use `sleep` option.
* In this case you need configuration to be specified as object:
*
* ```yaml
* extensions:
* enabled:
* - Codeception\Extension\RunProcess:
* 0: java -jar ~/selenium-server.jar
* 1: mailcatcher
* sleep: 5 # wait 5 seconds for processes to boot
* ```
*
* HINT: you can use different configurations per environment.
*/
class RunProcess extends Extension
{
public $config = ['sleep' => 0];
static $events = [
Events::SUITE_BEFORE => 'runProcess',
Events::SUITE_AFTER => 'stopProcess'
];
protected $processes = [];
public function _initialize()
{
if (!class_exists('Symfony\Component\Process\Process')) {
throw new ExtensionException($this, 'symfony/process package is required');
}
}
public function runProcess()
{
$this->processes = [];
foreach ($this->config as $key => $command) {
if (!$command) {
continue;
}
if (!is_int($key)) {
continue; // configuration options
}
$process = new Process($command, $this->getRootDir(), null, null, null);
$process->start();
$this->processes[] = $process;
$this->output->debug('[RunProcess] Starting '.$command);
}
sleep($this->config['sleep']);
}
public function __destruct()
{
$this->stopProcess();
}
public function stopProcess()
{
foreach (array_reverse($this->processes) as $process) {
/** @var $process Process **/
if (!$process->isRunning()) {
continue;
}
$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
$process->stop();
}
$this->processes = [];
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Codeception\Extension;
use Codeception\Event\TestEvent;
use Codeception\Events;
use Codeception\Extension;
use Codeception\Test\Descriptor;
/**
* This extension demonstrates how you can implement console output of your own.
* Recommended to be used for development purposes only.
*/
class SimpleReporter extends Extension
{
public function _initialize()
{
$this->options['silent'] = false; // turn on printing for this extension
$this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else
}
// we are listening for events
public static $events = [
Events::SUITE_BEFORE => 'beforeSuite',
Events::TEST_END => 'after',
Events::TEST_SUCCESS => 'success',
Events::TEST_FAIL => 'fail',
Events::TEST_ERROR => 'error',
];
public function beforeSuite()
{
$this->writeln("");
}
public function success()
{
$this->write('[+] ');
}
public function fail()
{
$this->write('[-] ');
}
public function error()
{
$this->write('[E] ');
}
// we are printing test status and time taken
public function after(TestEvent $e)
{
$seconds_input = $e->getTime();
// stack overflow: http://stackoverflow.com/questions/16825240/how-to-convert-microtime-to-hhmmssuu
$seconds = (int)($milliseconds = (int)($seconds_input * 1000)) / 1000;
$time = ($seconds % 60) . (($milliseconds === 0) ? '' : '.' . $milliseconds);
$this->write(Descriptor::getTestSignature($e->getTest()));
$this->writeln(' (' . $time . 's)');
}
}