├── src └── Codeception │ ├── Lib │ ├── Interfaces │ │ ├── ORM.php │ │ ├── API.php │ │ ├── DoctrineProvider.php │ │ ├── RequiresPackage.php │ │ ├── ConflictsWithModule.php │ │ ├── MultiSession.php │ │ ├── ActiveRecord.php │ │ ├── DataMapper.php │ │ ├── DependsOnModule.php │ │ ├── ScreenshotSaver.php │ │ ├── PartedModule.php │ │ ├── PageSourceSaver.php │ │ ├── ElementLocator.php │ │ ├── Remote.php │ │ ├── Queue.php │ │ ├── SessionSnapshot.php │ │ └── Db.php │ ├── README.md │ ├── Generator │ │ ├── Shared │ │ │ └── Classname.php │ │ ├── Feature.php │ │ ├── Cept.php │ │ ├── Helper.php │ │ ├── Cest.php │ │ ├── Group.php │ │ ├── Test.php │ │ ├── StepObject.php │ │ ├── Snapshot.php │ │ └── PageObject.php │ ├── Connector │ │ ├── Lumen │ │ │ └── DummyKernel.php │ │ ├── Yii2 │ │ │ ├── TestMailer.php │ │ │ ├── FixturesStore.php │ │ │ ├── Logger.php │ │ │ └── ConnectionWatcher.php │ │ ├── ZendExpressive │ │ │ └── ResponseCollector.php │ │ └── Universal.php │ ├── Actor │ │ └── Shared │ │ │ ├── Retry.php │ │ │ ├── Friend.php │ │ │ ├── Comment.php │ │ │ └── Pause.php │ ├── Framework.php │ ├── Notification.php │ ├── Console │ │ ├── Colorizer.php │ │ ├── DiffFactory.php │ │ └── MessageFactory.php │ ├── Driver │ │ ├── MySql.php │ │ ├── Beanstalk.php │ │ ├── SqlSrv.php │ │ ├── Sqlite.php │ │ └── Iron.php │ └── Friend.php │ ├── Exception │ ├── ParseException.php │ ├── InjectionException.php │ ├── ConfigurationException.php │ ├── ConnectionException.php │ ├── ExternalUrlException.php │ ├── TestRuntimeException.php │ ├── ContentNotFound.php │ ├── ConditionalAssertionFailed.php │ ├── RemoteException.php │ ├── MalformedLocatorException.php │ ├── ExtensionException.php │ ├── ElementNotFound.php │ ├── TestParseException.php │ ├── ModuleRequireException.php │ ├── ModuleException.php │ ├── ModuleConfigException.php │ └── ModuleConflictException.php │ ├── Step │ ├── Action.php │ ├── Assertion.php │ ├── Condition.php │ ├── README.md │ ├── GeneratedStep.php │ ├── Skip.php │ ├── Incomplete.php │ ├── Argument │ │ ├── FormattedOutput.php │ │ └── PasswordArgument.php │ ├── Executor.php │ ├── Comment.php │ ├── Meta.php │ ├── TryTo.php │ ├── ConditionalAssertion.php │ └── Retry.php │ ├── Test │ ├── Interfaces │ │ ├── Dependent.php │ │ ├── Plain.php │ │ ├── StrictCoverage.php │ │ ├── Descriptive.php │ │ ├── Reported.php │ │ └── ScenarioDriven.php │ ├── Loader │ │ ├── LoaderInterface.php │ │ ├── Cept.php │ │ └── Unit.php │ ├── Feature │ │ ├── AssertionCounter.php │ │ ├── MetadataCollector.php │ │ ├── ErrorLogger.php │ │ ├── IgnoreIfMetadataBlocked.php │ │ ├── ScenarioLoader.php │ │ └── CodeCoverage.php │ └── Cept.php │ ├── Subscriber │ ├── Shared │ │ └── StaticEvents.php │ ├── README.md │ ├── FailFast.php │ ├── Bootstrap.php │ ├── PrepareTest.php │ ├── Dependencies.php │ ├── BeforeAfterTest.php │ ├── GracefulTermination.php │ ├── AutoRebuild.php │ └── Module.php │ ├── Util │ ├── README.md │ ├── Soap.php │ ├── sq.php │ ├── Fixtures.php │ ├── Stub.php │ ├── Shared │ │ └── Namespaces.php │ ├── Debug.php │ ├── Xml.php │ ├── ReflectionHelper.php │ ├── Template.php │ ├── FileSystem.php │ └── XmlStructure.php │ ├── TestInterface.php │ ├── CustomCommandInterface.php │ ├── README.md │ ├── Event │ ├── FailEvent.php │ ├── StepEvent.php │ ├── TestEvent.php │ ├── PrintResultEvent.php │ └── SuiteEvent.php │ ├── Command │ ├── Shared │ │ ├── Style.php │ │ ├── FileSystem.php │ │ └── Config.php │ ├── Clean.php │ ├── CompletionFallback.php │ ├── GenerateHelper.php │ ├── GenerateGroup.php │ ├── GenerateCept.php │ ├── Init.php │ ├── GenerateTest.php │ ├── GenerateEnvironment.php │ ├── GenerateFeature.php │ ├── GenerateCest.php │ ├── GherkinSteps.php │ ├── Bootstrap.php │ ├── GenerateSnapshot.php │ ├── GeneratePageObject.php │ ├── GenerateStepObject.php │ └── Completion.php │ ├── GroupObject.php │ ├── Coverage │ └── Subscriber │ │ ├── Local.php │ │ └── RemoteServer.php │ ├── Actor.php │ ├── Suite.php │ ├── Module │ └── README.md │ └── Example.php ├── codecept.bat ├── nitpick.json ├── package ├── stub.php └── bin ├── ruleset.xml ├── .github └── FUNDING.yml ├── LICENSE ├── Dockerfile ├── ext ├── SimpleReporter.php └── RunFailed.php ├── codecept ├── PruneTest.php ├── composer.json └── shim.php /src/Codeception/Lib/Interfaces/ORM.php: -------------------------------------------------------------------------------- 1 | message = "Remote Application Error:\n" . $this->message; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Shared/Classname.php: -------------------------------------------------------------------------------- 1 | errorMessage 11 | * ] 12 | * @return mixed 13 | */ 14 | public function _depends(); 15 | } 16 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/ScreenshotSaver.php: -------------------------------------------------------------------------------- 1 | getModule('{{MODULE_NAME}}')->_saveScreenshot(codecept_output_dir().'screenshot_1.png'); 11 | * ``` 12 | * @api 13 | * @param $filename 14 | */ 15 | public function _saveScreenshot($filename); 16 | } 17 | -------------------------------------------------------------------------------- /src/Codeception/README.md: -------------------------------------------------------------------------------- 1 | # Codeception Core 2 | 3 | The most important classes are defined in root of Codeception. 4 | 5 | * `Codecept` - starts event dispatcher, loads subscribers, starts SuiteManager 6 | * `SuiteManager` - starts modules, starts test runner 7 | * `TestLoader` - loads tests from files 8 | * `Configuration` - loads YAML configuration 9 | * `Events` - defines all Codeception events 10 | * `TestCase` - applies Codeception feature to `PHPUnit\Framework\TestCase` class. -------------------------------------------------------------------------------- /src/Codeception/Step/Skip.php: -------------------------------------------------------------------------------- 1 | getAction()); 12 | } 13 | 14 | public function __toString() 15 | { 16 | return $this->getAction(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Codeception/Util/Soap.php: -------------------------------------------------------------------------------- 1 | message = $extension . "\n\n" . $this->message; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Codeception/Step/Incomplete.php: -------------------------------------------------------------------------------- 1 | getAction()); 12 | } 13 | 14 | public function __toString() 15 | { 16 | return $this->getAction(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Lumen/DummyKernel.php: -------------------------------------------------------------------------------- 1 | message = "Couldn't parse test '$fileName' on line $line"; 10 | } else { 11 | $this->message = "Couldn't parse test '$fileName'"; 12 | } 13 | if ($errors) { 14 | $this->message .= "\n$errors"; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Codeception/Exception/ModuleRequireException.php: -------------------------------------------------------------------------------- 1 | message = "[$module] module requirements not met --\n \n" . $this->message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/AssertionCounter.php: -------------------------------------------------------------------------------- 1 | numAssertions; 11 | } 12 | 13 | protected function assertionCounterStart() 14 | { 15 | \PHPUnit\Framework\Assert::resetCount(); 16 | } 17 | 18 | protected function assertionCounterEnd() 19 | { 20 | $this->numAssertions = \PHPUnit\Framework\Assert::getCount(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Codeception/Exception/ModuleException.php: -------------------------------------------------------------------------------- 1 | module = $module; 15 | parent::__construct($message); 16 | $this->message = "$module: {$this->message}"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Codeception/Exception/ModuleConfigException.php: -------------------------------------------------------------------------------- 1 | message = $module . " module is not configured!\n \n" . $this->message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Codeception coding standard. Inherits from PSR-2. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Codeception/Step/Argument/FormattedOutput.php: -------------------------------------------------------------------------------- 1 | 'stopOnFail', 14 | ]; 15 | 16 | public function stopOnFail(SuiteEvent $e) 17 | { 18 | $e->getResult()->stopOnError(true); 19 | $e->getResult()->stopOnFailure(true); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Actor/Shared/Retry.php: -------------------------------------------------------------------------------- 1 | retryXXX() methods; 15 | * 16 | * @param $num 17 | * @param int $interval 18 | */ 19 | public function retry($num, $interval = 200) 20 | { 21 | $this->retryNum = $num; 22 | $this->retryInterval = $interval; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Codeception/Step/Executor.php: -------------------------------------------------------------------------------- 1 | callable = $callable; 17 | } 18 | 19 | public function run(ModuleContainer $container = null) 20 | { 21 | $callable = $this->callable; 22 | 23 | return $callable(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Feature.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | } 24 | 25 | public function produce() 26 | { 27 | return (new Template($this->template)) 28 | ->place('name', $this->name) 29 | ->produce(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Yii2/TestMailer.php: -------------------------------------------------------------------------------- 1 | callback, $message); 18 | return true; 19 | } 20 | 21 | protected function saveMessage($message) 22 | { 23 | call_user_func($this->callback, $message); 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Codeception/Step/Argument/PasswordArgument.php: -------------------------------------------------------------------------------- 1 | password = $password; 15 | } 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function getOutput() 21 | { 22 | return '******'; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function __toString() 29 | { 30 | return $this->password; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/PartedModule.php: -------------------------------------------------------------------------------- 1 | tests[] = $cept; 23 | } 24 | 25 | public function getTests() 26 | { 27 | return $this->tests; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Codeception/Event/FailEvent.php: -------------------------------------------------------------------------------- 1 | fail = $e; 20 | $this->count = $count; 21 | } 22 | 23 | public function getCount() 24 | { 25 | return $this->count; 26 | } 27 | 28 | public function getFail() 29 | { 30 | return $this->fail; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/MetadataCollector.php: -------------------------------------------------------------------------------- 1 | metadata = $metadata; 16 | } 17 | 18 | public function getMetadata() 19 | { 20 | return $this->metadata; 21 | } 22 | 23 | public function getName() 24 | { 25 | return $this->getMetadata()->getName(); 26 | } 27 | 28 | public function getFileName() 29 | { 30 | return $this->getMetadata()->getFilename(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: codeception 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/Codeception/Command/Shared/Style.php: -------------------------------------------------------------------------------- 1 | getFormatter()->setStyle('notice', new OutputFormatterStyle('white', 'green', ['bold'])); 12 | $output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold'])); 13 | $output->getFormatter()->setStyle('warning', new OutputFormatterStyle(null, 'yellow', ['bold'])); 14 | $output->getFormatter()->setStyle('debug', new OutputFormatterStyle('cyan')); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Yii2/FixturesStore.php: -------------------------------------------------------------------------------- 1 | data = $data; 22 | } 23 | 24 | public function fixtures() 25 | { 26 | return $this->data; 27 | } 28 | 29 | public function globalFixtures() 30 | { 31 | return [ 32 | InitDbFixture::className() 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/ErrorLogger.php: -------------------------------------------------------------------------------- 1 | getTestResultObject()->addError($this, $exception, $time); 21 | } 22 | if ($status === CodeceptionTest::STATUS_FAIL) { 23 | $this->getTestResultObject()->addFailure($this, $exception, $time); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Codeception/Event/StepEvent.php: -------------------------------------------------------------------------------- 1 | test = $test; 23 | $this->step = $step; 24 | } 25 | 26 | public function getStep() 27 | { 28 | return $this->step; 29 | } 30 | 31 | /** 32 | * @return TestInterface 33 | */ 34 | public function getTest() 35 | { 36 | return $this->test; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Codeception/GroupObject.php: -------------------------------------------------------------------------------- 1 | '_before', 25 | Events::TEST_AFTER . '.' . static::$group => '_after', 26 | ]; 27 | } 28 | return array_merge($events, $inheritedEvents); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/Bootstrap.php: -------------------------------------------------------------------------------- 1 | 'loadBootstrap', 16 | ]; 17 | 18 | public function loadBootstrap(SuiteEvent $e) 19 | { 20 | $settings = $e->getSettings(); 21 | 22 | if (!isset($settings['bootstrap'])) { 23 | return; 24 | } 25 | 26 | Configuration::loadBootstrap($settings['bootstrap'], $settings['path']); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Codeception/Event/TestEvent.php: -------------------------------------------------------------------------------- 1 | test = $test; 21 | $this->time = $time; 22 | } 23 | 24 | /** 25 | * @return float 26 | */ 27 | public function getTime() 28 | { 29 | return $this->time; 30 | } 31 | 32 | /** 33 | * @return \Codeception\TestInterface 34 | */ 35 | public function getTest() 36 | { 37 | return $this->test; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/ZendExpressive/ResponseCollector.php: -------------------------------------------------------------------------------- 1 | response = $response; 17 | } 18 | 19 | public function getResponse() 20 | { 21 | if ($this->response === null) { 22 | throw new \LogicException('Response wasn\'t emitted yet'); 23 | } 24 | return $this->response; 25 | } 26 | 27 | public function clearResponse() 28 | { 29 | $this->response = null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Actor/Shared/Friend.php: -------------------------------------------------------------------------------- 1 | friends[$name])) { 24 | $actor = $actorClass === null ? $this : new $actorClass($this->getScenario()); 25 | $this->friends[$name] = new LibFriend($name, $actor, $this->getScenario()->current('modules')); 26 | } 27 | return $this->friends[$name]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Framework.php: -------------------------------------------------------------------------------- 1 | internalDomains = null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Notification.php: -------------------------------------------------------------------------------- 1 | $message $location"; 22 | } 23 | return $message; 24 | } 25 | 26 | public static function all() 27 | { 28 | $messages = self::$messages; 29 | self::$messages = []; 30 | return $messages; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Codeception/Event/PrintResultEvent.php: -------------------------------------------------------------------------------- 1 | result = $result; 21 | $this->printer = $printer; 22 | } 23 | 24 | /** 25 | * @return \PHPUnit\Util\Printer 26 | */ 27 | public function getPrinter() 28 | { 29 | return $this->printer; 30 | } 31 | 32 | /** 33 | * @return \PHPUnit\Framework\TestResult 34 | */ 35 | public function getResult() 36 | { 37 | return $this->result; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Codeception/Step/Comment.php: -------------------------------------------------------------------------------- 1 | getAction(); 12 | } 13 | 14 | public function toString($maxLength) 15 | { 16 | return mb_strcut($this->__toString(), 0, $maxLength, 'utf-8'); 17 | } 18 | 19 | public function getHtml($highlightColor = '#732E81') 20 | { 21 | return '' . $this->getAction() . ''; 22 | } 23 | 24 | public function getPhpCode($maxLength) 25 | { 26 | return '// ' . $this->getAction(); 27 | } 28 | 29 | public function run(ModuleContainer $container = null) 30 | { 31 | // don't do anything, let's rest 32 | } 33 | 34 | public function getPrefix() 35 | { 36 | return ''; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Codeception/Util/sq.php: -------------------------------------------------------------------------------- 1 | getModule('{{MODULE_NAME}}')->_savePageSource(codecept_output_dir().'page.html'); 11 | * ``` 12 | * @api 13 | * @param $filename 14 | */ 15 | public function _savePageSource($filename); 16 | 17 | /** 18 | * Saves current page's HTML into a temprary file. 19 | * Use this method in debug mode within an interactive pause to get a source code of current page. 20 | * 21 | * ```php 22 | * makeHtmlSnapshot('edit_page'); 24 | * // saved to: tests/_output/debug/edit_page.html 25 | * $I->makeHtmlSnapshot(); 26 | * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.html 27 | * ``` 28 | * 29 | * @param null $name 30 | */ 31 | public function makeHtmlSnapshot($name = null); 32 | } 33 | -------------------------------------------------------------------------------- /src/Codeception/Util/Fixtures.php: -------------------------------------------------------------------------------- 1 | 'davert']); 10 | * Fixtures::get('user1'); 11 | * Fixtures::exists('user1'); 12 | * 13 | * ?> 14 | * ``` 15 | * 16 | */ 17 | class Fixtures 18 | { 19 | protected static $fixtures = []; 20 | 21 | public static function add($name, $data) 22 | { 23 | self::$fixtures[$name] = $data; 24 | } 25 | 26 | public static function get($name) 27 | { 28 | if (!self::exists($name)) { 29 | throw new \RuntimeException("$name not found in fixtures"); 30 | } 31 | 32 | return self::$fixtures[$name]; 33 | } 34 | 35 | public static function cleanup() 36 | { 37 | self::$fixtures = []; 38 | } 39 | 40 | public static function exists($name) 41 | { 42 | return isset(self::$fixtures[$name]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Console/Colorizer.php: -------------------------------------------------------------------------------- 1 | $line"; 26 | break; 27 | case '-': 28 | $line = "$line"; 29 | break; 30 | } 31 | 32 | $colorizedMessage .= $line . "\n"; 33 | } 34 | 35 | return trim($colorizedMessage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Yii2/Logger.php: -------------------------------------------------------------------------------- 1 | __toString(); 29 | } 30 | 31 | Debug::debug("[$category] " . \yii\helpers\VarDumper::export($message)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Console/DiffFactory.php: -------------------------------------------------------------------------------- 1 | getDiff($failure->getExpectedAsString(), $failure->getActualAsString()); 19 | if (!$diff) { 20 | return null; 21 | } 22 | 23 | return $diff; 24 | } 25 | 26 | /** 27 | * @param string $expected 28 | * @param string $actual 29 | * @return string 30 | */ 31 | private function getDiff($expected = '', $actual = '') 32 | { 33 | if (!$actual && !$expected) { 34 | return ''; 35 | } 36 | 37 | $differ = new Differ(''); 38 | 39 | return $differ->diff($expected, $actual); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Codeception/Exception/ModuleConflictException.php: -------------------------------------------------------------------------------- 1 | message = "$module module conflicts with $conflicted\n\n--\n" 17 | . "This usually happens when you enable two modules with the same actions but with different backends.\n" 18 | . "For instance, you can't run PhpBrowser, WebDriver, Laravel5 modules in one suite,\n" 19 | . "as they implement similar methods but use different drivers to execute them.\n" 20 | . "You can load a part of module (like: ORM) to avoid conflict.\n" 21 | . $additional; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/ElementLocator.php: -------------------------------------------------------------------------------- 1 | getModule('{{MODULE_NAME}}')->_findElements('.items'); 18 | * $els = $this->getModule('{{MODULE_NAME}}')->_findElements(['name' => 'username']); 19 | * 20 | * $editLinks = $this->getModule('{{MODULE_NAME}}')->_findElements(['link' => 'Edit']); 21 | * // now you can iterate over $editLinks and check that all them have valid hrefs 22 | * ``` 23 | * 24 | * WebDriver module returns `Facebook\WebDriver\Remote\RemoteWebElement` instances 25 | * PhpBrowser and Framework modules return `Symfony\Component\DomCrawler\Crawler` instances 26 | * 27 | * @api 28 | * @param $locator 29 | * @return array of interactive elements 30 | */ 31 | public function _findElements($locator); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011 Michael Bodnarchuk and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/Remote.php: -------------------------------------------------------------------------------- 1 | amOnSubdomain('user'); 17 | * $I->amOnPage('/'); 18 | * // moves to http://user.mysite.com/ 19 | * ?> 20 | * ``` 21 | * 22 | * @param $subdomain 23 | * 24 | * @return mixed 25 | */ 26 | public function amOnSubdomain($subdomain); 27 | 28 | /** 29 | * Open web page at the given absolute URL and sets its hostname as the base host. 30 | * 31 | * ``` php 32 | * amOnUrl('http://codeception.com'); 34 | * $I->amOnPage('/quickstart'); // moves to http://codeception.com/quickstart 35 | * ?> 36 | * ``` 37 | */ 38 | public function amOnUrl($url); 39 | 40 | public function _getUrl(); 41 | } 42 | -------------------------------------------------------------------------------- /src/Codeception/Util/Stub.php: -------------------------------------------------------------------------------- 1 | wantTo('perform actions and see result'); 14 | 15 | EOF; 16 | 17 | protected $settings; 18 | 19 | public function __construct($settings) 20 | { 21 | $this->settings = $settings; 22 | } 23 | 24 | public function produce() 25 | { 26 | $actor = $this->settings['actor']; 27 | if (!$actor) { 28 | throw new ConfigurationException("Cept can't be created for suite without an actor. Add `actor: SomeTester` to suite config"); 29 | } 30 | $use = ''; 31 | if (! empty($this->settings['namespace'])) { 32 | $namespace = rtrim($this->settings['namespace'], '\\'); 33 | $use = "use {$namespace}\\$actor;"; 34 | } 35 | 36 | return (new Template($this->template)) 37 | ->place('actor', $actor) 38 | ->place('use', $use) 39 | ->produce(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Helper.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 30 | $this->name = $name; 31 | } 32 | 33 | public function produce() 34 | { 35 | return (new Template($this->template)) 36 | ->place('namespace', $this->getNamespaceHeader($this->namespace . '\\Helper\\' . $this->name)) 37 | ->place('name', $this->getShortClassName($this->name)) 38 | ->produce(); 39 | } 40 | 41 | public function getHelperName() 42 | { 43 | return rtrim('\\' . $this->namespace, '\\') . '\\Helper\\' . $this->name; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/IgnoreIfMetadataBlocked.php: -------------------------------------------------------------------------------- 1 | getMetadata()->isBlocked()) { 23 | return; 24 | } 25 | 26 | $this->ignore(true); 27 | 28 | if ($this->getMetadata()->getSkip() !== null) { 29 | $this->getTestResultObject()->addFailure($this, new \PHPUnit\Framework\SkippedTestError((string)$this->getMetadata()->getSkip()), 0); 30 | return; 31 | } 32 | if ($this->getMetadata()->getIncomplete() !== null) { 33 | $this->getTestResultObject()->addFailure($this, new \PHPUnit\Framework\IncompleteTestError((string)$this->getMetadata()->getIncomplete()), 0); 34 | return; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Codeception/Event/SuiteEvent.php: -------------------------------------------------------------------------------- 1 | suite = $suite; 30 | $this->result = $result; 31 | $this->settings = $settings; 32 | } 33 | 34 | /** 35 | * @return Suite 36 | */ 37 | public function getSuite() 38 | { 39 | return $this->suite; 40 | } 41 | 42 | /** 43 | * @return \PHPUnit\Framework\TestResult 44 | */ 45 | public function getResult() 46 | { 47 | return $this->result; 48 | } 49 | 50 | public function getSettings() 51 | { 52 | return $this->settings; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Actor/Shared/Comment.php: -------------------------------------------------------------------------------- 1 | comment('I expect to ' . $prediction); 14 | } 15 | 16 | public function expect($prediction) 17 | { 18 | return $this->comment('I expect ' . $prediction); 19 | } 20 | 21 | public function amGoingTo($argumentation) 22 | { 23 | return $this->comment('I am going to ' . $argumentation); 24 | } 25 | 26 | public function am($role) 27 | { 28 | $role = trim($role); 29 | 30 | if (stripos('aeiou', $role[0]) !== false) { 31 | return $this->comment('As an ' . $role); 32 | } 33 | 34 | return $this->comment('As a ' . $role); 35 | } 36 | 37 | public function lookForwardTo($achieveValue) 38 | { 39 | return $this->comment('So that I ' . $achieveValue); 40 | } 41 | 42 | public function comment($description) 43 | { 44 | $this->getScenario()->comment($description); 45 | return $this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Codeception/Step/Meta.php: -------------------------------------------------------------------------------- 1 | file = $file; 16 | $this->line = $line; 17 | } 18 | 19 | public function setPrefix($actor) 20 | { 21 | $this->prefix = $actor; 22 | } 23 | 24 | public function getArgumentsAsString($maxLength = 200) 25 | { 26 | $argumentBackup = $this->arguments; 27 | $lastArgAsString = ''; 28 | $lastArg = end($this->arguments); 29 | if (is_string($lastArg) && strpos($lastArg, "\n") !== false) { 30 | $lastArgAsString = "\r\n " . str_replace("\n", "\n ", $lastArg); 31 | array_pop($this->arguments); 32 | } 33 | $result = parent::getArgumentsAsString($maxLength) . $lastArgAsString; 34 | $this->arguments = $argumentBackup; 35 | return $result; 36 | } 37 | 38 | public function setFailed($failed) 39 | { 40 | $this->failed = $failed; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/PrepareTest.php: -------------------------------------------------------------------------------- 1 | 'prepare', 18 | ]; 19 | 20 | protected $modules = []; 21 | 22 | public function prepare(TestEvent $event) 23 | { 24 | $test = $event->getTest(); 25 | /** @var $di Di **/ 26 | $prepareMethods = $test->getMetadata()->getParam('prepare'); 27 | 28 | if (!$prepareMethods) { 29 | return; 30 | } 31 | $di = $test->getMetadata()->getService('di'); 32 | 33 | foreach ($prepareMethods as $method) { 34 | 35 | /** @var $module \Codeception\Module **/ 36 | if ($test instanceof Cest) { 37 | $di->injectDependencies($test->getTestClass(), $method); 38 | } 39 | if ($test instanceof Unit) { 40 | $di->injectDependencies($test, $method); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/ScenarioLoader.php: -------------------------------------------------------------------------------- 1 | scenario = new Scenario($this); 23 | } 24 | 25 | /** 26 | * @return Scenario 27 | */ 28 | public function getScenario() 29 | { 30 | return $this->scenario; 31 | } 32 | 33 | public function getFeature() 34 | { 35 | return $this->getScenario()->getFeature(); 36 | } 37 | 38 | public function getScenarioText($format = 'text') 39 | { 40 | $code = $this->getSourceCode(); 41 | $this->getParser()->parseFeature($code); 42 | $this->getParser()->parseSteps($code); 43 | if ($format == 'html') { 44 | return $this->getScenario()->getHtml(); 45 | } 46 | return $this->getScenario()->getText(); 47 | } 48 | 49 | /** 50 | * @return Parser 51 | */ 52 | abstract protected function getParser(); 53 | abstract public function getSourceCode(); 54 | } 55 | -------------------------------------------------------------------------------- /src/Codeception/Coverage/Subscriber/Local.php: -------------------------------------------------------------------------------- 1 | 'beforeSuite', 17 | Events::SUITE_AFTER => 'afterSuite', 18 | ]; 19 | 20 | /** 21 | * @var Remote 22 | */ 23 | protected $module; 24 | 25 | protected function isEnabled() 26 | { 27 | return $this->module === null and $this->settings['enabled']; 28 | } 29 | 30 | public function beforeSuite(SuiteEvent $e) 31 | { 32 | $this->applySettings($e->getSettings()); 33 | $this->module = $this->getServerConnectionModule($e->getSuite()->getModules()); 34 | if (!$this->isEnabled()) { 35 | return; 36 | } 37 | $this->applyFilter($e->getResult()); 38 | } 39 | 40 | public function afterSuite(SuiteEvent $e) 41 | { 42 | if (!$this->isEnabled()) { 43 | return; 44 | } 45 | $this->mergeToPrint($e->getResult()->getCodeCoverage()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/Queue.php: -------------------------------------------------------------------------------- 1 | scenario = $scenario; 21 | } 22 | 23 | /** 24 | * @return \Codeception\Scenario 25 | */ 26 | protected function getScenario() 27 | { 28 | return $this->scenario; 29 | } 30 | 31 | public function wantToTest($text) 32 | { 33 | $this->wantTo('test ' . $text); 34 | } 35 | 36 | public function wantTo($text) 37 | { 38 | $this->scenario->setFeature($text); 39 | } 40 | 41 | public function __call($method, $arguments) 42 | { 43 | $class = get_class($this); 44 | throw new \RuntimeException("Call to undefined method $class::$method"); 45 | } 46 | 47 | /** 48 | * Lazy-execution given anonymous function 49 | * @param $callable \Closure 50 | * @return $this 51 | */ 52 | public function execute($callable) 53 | { 54 | $this->scenario->addStep(new Executor($callable, [])); 55 | $callable(); 56 | return $this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Codeception/Util/Shared/Namespaces.php: -------------------------------------------------------------------------------- 1 | breakParts($class); 22 | return array_pop($namespaces); 23 | } 24 | 25 | protected function getNamespaceString($class) 26 | { 27 | $namespaces = $this->getNamespaces($class); 28 | return implode('\\', $namespaces); 29 | } 30 | 31 | protected function getNamespaceHeader($class) 32 | { 33 | $str = $this->getNamespaceString($class); 34 | if (!$str) { 35 | return ""; 36 | } 37 | return "namespace $str;\n"; 38 | } 39 | 40 | protected function getNamespaces($class) 41 | { 42 | $namespaces = $this->breakParts($class); 43 | array_pop($namespaces); 44 | $namespaces = array_filter($namespaces, 'strlen'); 45 | return $namespaces; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Codeception/Step/TryTo.php: -------------------------------------------------------------------------------- 1 | isTry = true; 13 | try { 14 | parent::run($container); 15 | } catch (\Exception $e) { 16 | codecept_debug("Failed to perform: {$e->getMessage()}, skipping..."); 17 | return false; 18 | } 19 | return true; 20 | } 21 | 22 | public static function getTemplate(Template $template) 23 | { 24 | $action = $template->getVar('action'); 25 | 26 | if ((strpos($action, 'have') === 0) || (strpos($action, 'am') === 0)) { 27 | return; // dont try on conditions 28 | } 29 | 30 | if (strpos($action, 'wait') === 0) { 31 | return; // dont try on waiters 32 | } 33 | 34 | if (strpos($action, 'grab') === 0) { 35 | return; // dont on grabbers 36 | } 37 | 38 | $conditionalDoc = "* [!] Test won't be stopped on fail. Error won't be logged \n " . $template->getVar('doc'); 39 | 40 | return $template 41 | ->place('doc', $conditionalDoc) 42 | ->place('action', 'tryTo' . ucfirst($action)) 43 | ->place('step', 'TryTo'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/Dependencies.php: -------------------------------------------------------------------------------- 1 | 'testStart', 17 | Events::TEST_SUCCESS => 'testSuccess' 18 | ]; 19 | 20 | protected $successfulTests = []; 21 | 22 | public function testStart(TestEvent $event) 23 | { 24 | $test = $event->getTest(); 25 | if (!$test instanceof Dependent) { 26 | return; 27 | } 28 | 29 | $testSignatures = $test->fetchDependencies(); 30 | foreach ($testSignatures as $signature) { 31 | if (!in_array($signature, $this->successfulTests)) { 32 | $test->getMetadata()->setSkip("This test depends on $signature to pass"); 33 | return; 34 | } 35 | } 36 | } 37 | 38 | public function testSuccess(TestEvent $event) 39 | { 40 | $test = $event->getTest(); 41 | if (!$test instanceof TestInterface) { 42 | return; 43 | } 44 | $this->successfulTests[] = Descriptor::getTestSignature($test); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Console/MessageFactory.php: -------------------------------------------------------------------------------- 1 | output = $output; 32 | $this->diffFactory = new DiffFactory(); 33 | $this->colorizer = new Colorizer(); 34 | } 35 | 36 | /** 37 | * @param string $text 38 | * @return Message 39 | */ 40 | public function message($text = '') 41 | { 42 | return new Message($text, $this->output); 43 | } 44 | 45 | /** 46 | * @param ComparisonFailure $failure 47 | * @return string 48 | */ 49 | public function prepareComparisonFailureMessage(ComparisonFailure $failure) 50 | { 51 | $diff = $this->diffFactory->createDiff($failure); 52 | if (!$diff) { 53 | return ''; 54 | } 55 | $diff = $this->colorizer->colorize($diff); 56 | 57 | return "\n- Expected | + Actual\n$diff"; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Codeception/Util/Debug.php: -------------------------------------------------------------------------------- 1 | debug($message); 37 | } 38 | 39 | public static function isEnabled() 40 | { 41 | return (bool) self::$output; 42 | } 43 | 44 | public static function confirm($question) 45 | { 46 | if (!self::$output) { 47 | return; 48 | } 49 | 50 | $questionHelper = new QuestionHelper(); 51 | return $questionHelper->ask(new ArgvInput(), self::$output, new ConfirmationQuestion($question)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Codeception/Test/Feature/CodeCoverage.php: -------------------------------------------------------------------------------- 1 | getTestResultObject()->getCodeCoverage(); 17 | if (!$codeCoverage) { 18 | return; 19 | } 20 | $codeCoverage->start(Descriptor::getTestSignature($this)); 21 | } 22 | 23 | public function codeCoverageEnd($status, $time) 24 | { 25 | $codeCoverage = $this->getTestResultObject()->getCodeCoverage(); 26 | if (!$codeCoverage) { 27 | return; 28 | } 29 | 30 | if ($this instanceof StrictCoverage) { 31 | $linesToBeCovered = $this->getLinesToBeCovered(); 32 | $linesToBeUsed = $this->getLinesToBeUsed(); 33 | } else { 34 | $linesToBeCovered = []; 35 | $linesToBeUsed = []; 36 | } 37 | 38 | try { 39 | $codeCoverage->stop(true, $linesToBeCovered, $linesToBeUsed); 40 | } catch (\PHP_CodeCoverage_Exception $cce) { 41 | if ($status === \Codeception\Test\Test::STATUS_OK) { 42 | $this->getTestResultObject()->addError($this, $cce, $time); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Codeception/Command/Clean.php: -------------------------------------------------------------------------------- 1 | cleanProjectsRecursively($output, $projectDir); 29 | $output->writeln("Done"); 30 | } 31 | 32 | private function cleanProjectsRecursively(OutputInterface $output, $projectDir) 33 | { 34 | $logDir = Configuration::logDir(); 35 | $output->writeln("Cleaning up output " . $logDir . "..."); 36 | FileSystem::doEmptyDir($logDir); 37 | 38 | $config = Configuration::config($projectDir); 39 | $subProjects = $config['include']; 40 | foreach ($subProjects as $subProject) { 41 | $subProjectDir = $projectDir . $subProject; 42 | $this->cleanProjectsRecursively($output, $subProjectDir); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3-cli 2 | 3 | MAINTAINER Tobias Munk tobias@diemeisterei.de 4 | 5 | # Install required system packages 6 | RUN apt-get update && \ 7 | apt-get -y install \ 8 | git \ 9 | zlib1g-dev \ 10 | libssl-dev \ 11 | libzip-dev \ 12 | unzip \ 13 | --no-install-recommends && \ 14 | apt-get clean && \ 15 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 16 | 17 | # Install php extensions 18 | RUN docker-php-ext-install \ 19 | bcmath \ 20 | zip 21 | 22 | # Install pecl extensions 23 | RUN pecl install \ 24 | mongodb \ 25 | apcu \ 26 | xdebug-2.7.2 && \ 27 | docker-php-ext-enable \ 28 | apcu.so \ 29 | mongodb.so \ 30 | xdebug 31 | 32 | # Configure php 33 | RUN echo "date.timezone = UTC" >> /usr/local/etc/php/php.ini 34 | 35 | # Install composer 36 | ENV COMPOSER_ALLOW_SUPERUSER=1 37 | RUN curl -sS https://getcomposer.org/installer | php -- \ 38 | --filename=composer \ 39 | --install-dir=/usr/local/bin 40 | RUN composer global require --prefer-dist --no-interaction --optimize-autoloader --apcu-autoloader \ 41 | "hirak/prestissimo" 42 | 43 | # Prepare application 44 | WORKDIR /repo 45 | 46 | # Install vendor 47 | COPY ./composer.json /repo/composer.json 48 | RUN composer install --prefer-dist --no-interaction --optimize-autoloader --apcu-autoloader 49 | 50 | # Add source-code 51 | COPY . /repo 52 | 53 | ENV PATH /repo:${PATH} 54 | ENTRYPOINT ["codecept"] 55 | 56 | # Prepare host-volume working directory 57 | RUN mkdir /project 58 | WORKDIR /project 59 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Driver/MySql.php: -------------------------------------------------------------------------------- 1 | dbh->exec('SET FOREIGN_KEY_CHECKS=0;'); 9 | $res = $this->dbh->query("SHOW FULL TABLES WHERE TABLE_TYPE LIKE '%TABLE';")->fetchAll(); 10 | foreach ($res as $row) { 11 | $this->dbh->exec('drop table `' . $row[0] . '`'); 12 | } 13 | $this->dbh->exec('SET FOREIGN_KEY_CHECKS=1;'); 14 | } 15 | 16 | protected function sqlQuery($query) 17 | { 18 | $this->dbh->exec('SET FOREIGN_KEY_CHECKS=0;'); 19 | parent::sqlQuery($query); 20 | $this->dbh->exec('SET FOREIGN_KEY_CHECKS=1;'); 21 | } 22 | 23 | public function getQuotedName($name) 24 | { 25 | return '`' . str_replace('.', '`.`', $name) . '`'; 26 | } 27 | 28 | /** 29 | * @param string $tableName 30 | * 31 | * @return array[string] 32 | */ 33 | public function getPrimaryKey($tableName) 34 | { 35 | if (!isset($this->primaryKeys[$tableName])) { 36 | $primaryKey = []; 37 | $stmt = $this->getDbh()->query( 38 | 'SHOW KEYS FROM ' . $this->getQuotedName($tableName) . ' WHERE Key_name = "PRIMARY"' 39 | ); 40 | $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); 41 | 42 | foreach ($columns as $column) { 43 | $primaryKey []= $column['Column_name']; 44 | } 45 | $this->primaryKeys[$tableName] = $primaryKey; 46 | } 47 | 48 | return $this->primaryKeys[$tableName]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Codeception/Command/CompletionFallback.php: -------------------------------------------------------------------------------- 1 | setName('_completion') 21 | ->setDescription('BASH completion hook.') 22 | ->setHelp(<<composer require stecman/symfony-console-completion 26 | 27 | END 28 | ); 29 | 30 | // Hide this command from listing if supported 31 | // Command::setHidden() was not available before Symfony 3.2.0 32 | if (method_exists($this, 'setHidden')) { 33 | $this->setHidden(true); 34 | } 35 | } 36 | protected function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $output->writeln("Install optional stecman/symfony-console-completion"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateHelper.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 26 | new InputArgument('name', InputArgument::REQUIRED, 'helper name'), 27 | ]); 28 | } 29 | 30 | public function getDescription() 31 | { 32 | return 'Generates new helper'; 33 | } 34 | 35 | public function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | $name = ucfirst($input->getArgument('name')); 38 | $config = $this->getGlobalConfig(); 39 | 40 | $path = $this->createDirectoryFor(Configuration::supportDir() . 'Helper', $name); 41 | $filename = $path . $this->getShortClassName($name) . '.php'; 42 | 43 | $res = $this->createFile($filename, (new Helper($name, $config['namespace']))->produce()); 44 | if ($res) { 45 | $output->writeln("Helper $filename created"); 46 | } else { 47 | $output->writeln("Error creating helper $filename"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Codeception/Util/Xml.php: -------------------------------------------------------------------------------- 1 | $val) { 18 | if (is_array($val)) { 19 | self::arrayToXml($xml, $node->$el, $val); 20 | } else { 21 | $node->appendChild($xml->createElement($el, $val)); 22 | } 23 | } 24 | return $xml; 25 | } 26 | 27 | /** 28 | * @static 29 | * 30 | * @param $xml 31 | * 32 | * @return \DOMDocument|\DOMNode 33 | */ 34 | public static function toXml($xml) 35 | { 36 | if ($xml instanceof XmlBuilder) { 37 | return $xml->getDom(); 38 | } 39 | if ($xml instanceof \DOMDocument) { 40 | return $xml; 41 | } 42 | $dom = new \DOMDocument(); 43 | $dom->preserveWhiteSpace = false; 44 | if ($xml instanceof \DOMNode) { 45 | $xml = $dom->importNode($xml, true); 46 | $dom->appendChild($xml); 47 | return $dom; 48 | } 49 | 50 | if (is_array($xml)) { 51 | return self::arrayToXml($dom, $dom, $xml); 52 | } 53 | if (!empty($xml)) { 54 | $dom->loadXML($xml); 55 | } 56 | return $dom; 57 | } 58 | 59 | public static function build() 60 | { 61 | return new XmlBuilder(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Codeception/Util/ReflectionHelper.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 25 | 26 | return $property->getValue($object); 27 | } 28 | 29 | /** 30 | * Invoke a private method of an object. 31 | * 32 | * @param object $object 33 | * @param string $method 34 | * @param array $args 35 | * @param string|null $class 36 | * @return mixed 37 | */ 38 | public static function invokePrivateMethod($object, $method, $args = [], $class = null) 39 | { 40 | if (is_null($class)) { 41 | $class = $object; 42 | } 43 | 44 | $method = new \ReflectionMethod($class, $method); 45 | $method->setAccessible(true); 46 | 47 | return $method->invokeArgs($object, $args); 48 | } 49 | 50 | /** 51 | * Returns class name without namespace 52 | * 53 | * (does not use reflection actually) 54 | * 55 | * @param $object 56 | * @return mixed 57 | */ 58 | public static function getClassShortName($object) 59 | { 60 | $path = explode('\\', get_class($object)); 61 | return array_pop($path); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/BeforeAfterTest.php: -------------------------------------------------------------------------------- 1 | 'beforeClass', 14 | Events::SUITE_AFTER => ['afterClass', 100] 15 | ]; 16 | 17 | protected $hooks = []; 18 | protected $startedTests = []; 19 | protected $unsuccessfulTests = []; 20 | 21 | public function beforeClass(SuiteEvent $e) 22 | { 23 | foreach ($e->getSuite()->tests() as $test) { 24 | /** @var $test \PHPUnit\Framework\Test * */ 25 | if ($test instanceof \PHPUnit\Framework\TestSuite\DataProvider) { 26 | $potentialTestClass = strstr($test->getName(), '::', true); 27 | $this->hooks[$potentialTestClass] = \PHPUnit\Util\Test::getHookMethods($potentialTestClass); 28 | } 29 | 30 | $testClass = get_class($test); 31 | $this->hooks[$testClass] = \PHPUnit\Util\Test::getHookMethods($testClass); 32 | } 33 | $this->runHooks('beforeClass'); 34 | } 35 | 36 | 37 | public function afterClass(SuiteEvent $e) 38 | { 39 | $this->runHooks('afterClass'); 40 | } 41 | 42 | protected function runHooks($hookName) 43 | { 44 | foreach ($this->hooks as $className => $hook) { 45 | foreach ($hook[$hookName] as $method) { 46 | if (is_callable([$className, $method])) { 47 | call_user_func([$className, $method]); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/SessionSnapshot.php: -------------------------------------------------------------------------------- 1 | loadSessionSnapshot('login')) return; 20 | * 21 | * // logging in 22 | * $I->amOnPage('/login'); 23 | * $I->fillField('name', 'jon'); 24 | * $I->fillField('password', '123345'); 25 | * $I->click('Login'); 26 | * 27 | * // saving snapshot 28 | * $I->saveSessionSnapshot('login'); 29 | * } 30 | * ?> 31 | * ``` 32 | * 33 | * @param $name 34 | * @return mixed 35 | */ 36 | public function saveSessionSnapshot($name); 37 | 38 | /** 39 | * Loads cookies from a saved snapshot. 40 | * Allows to reuse same session across tests without additional login. 41 | * 42 | * See [saveSessionSnapshot](#saveSessionSnapshot) 43 | * 44 | * @param $name 45 | * @return mixed 46 | */ 47 | public function loadSessionSnapshot($name); 48 | 49 | /** 50 | * Deletes session snapshot. 51 | * 52 | * See [saveSessionSnapshot](#saveSessionSnapshot) 53 | * 54 | * @param $name 55 | * @return mixed 56 | */ 57 | public function deleteSessionSnapshot($name); 58 | } 59 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/GracefulTermination.php: -------------------------------------------------------------------------------- 1 | suiteEvent = $event; 34 | } 35 | 36 | public function terminate() 37 | { 38 | if ($this->suiteEvent) { 39 | $this->suiteEvent->getResult()->stopOnError(true); 40 | $this->suiteEvent->getResult()->stopOnFailure(true); 41 | } 42 | throw new \RuntimeException( 43 | "\n\n---------------------------\nTESTS EXECUTION TERMINATED\n---------------------------\n" 44 | ); 45 | } 46 | 47 | public static function getSubscribedEvents() 48 | { 49 | if (!function_exists(self::SIGNAL_FUNC)) { 50 | return []; 51 | } 52 | return [Events::SUITE_BEFORE => 'handleSuite']; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Cest.php: -------------------------------------------------------------------------------- 1 | name = $this->removeSuffix($className, 'Cest'); 36 | $this->settings = $settings; 37 | } 38 | 39 | public function produce() 40 | { 41 | $actor = $this->settings['actor']; 42 | if (!$actor) { 43 | throw new ConfigurationException("Cept can't be created for suite without an actor. Add `actor: SomeTester` to suite config"); 44 | } 45 | 46 | if (array_key_exists('suite_namespace', $this->settings)) { 47 | $namespace = rtrim($this->settings['suite_namespace'], '\\'); 48 | } else { 49 | $namespace = rtrim($this->settings['namespace'], '\\'); 50 | } 51 | 52 | $ns = $this->getNamespaceHeader($namespace.'\\'.$this->name); 53 | 54 | if ($namespace) { 55 | $ns .= "use ".$this->settings['namespace'].'\\'.$actor.";"; 56 | } 57 | 58 | return (new Template($this->template)) 59 | ->place('name', $this->getShortClassName($this->name)) 60 | ->place('namespace', $ns) 61 | ->place('actor', $actor) 62 | ->produce(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Group.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 49 | $this->name = $name; 50 | $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Group\\' . $name); 51 | } 52 | 53 | public function produce() 54 | { 55 | $ns = $this->getNamespaceString($this->settings['namespace'] . '\\' . $this->name); 56 | return (new Template($this->template)) 57 | ->place('class', ucfirst($this->name)) 58 | ->place('name', $this->name) 59 | ->place('namespace', $this->namespace) 60 | ->place('groupName', strtolower($this->name)) 61 | ->produce(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateGroup.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 24 | new InputArgument('group', InputArgument::REQUIRED, 'Group class name'), 25 | ]); 26 | } 27 | 28 | public function getDescription() 29 | { 30 | return 'Generates Group subscriber'; 31 | } 32 | 33 | public function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $config = $this->getGlobalConfig(); 36 | $group = $input->getArgument('group'); 37 | 38 | $class = ucfirst($group); 39 | $path = $this->createDirectoryFor(Configuration::supportDir() . 'Group' . DIRECTORY_SEPARATOR, $class); 40 | 41 | $filename = $path . $class . '.php'; 42 | 43 | $gen = new GroupGenerator($config, $group); 44 | $res = $this->createFile($filename, $gen->produce()); 45 | 46 | if (!$res) { 47 | $output->writeln("Group $filename already exists"); 48 | return; 49 | } 50 | 51 | $output->writeln("Group extension was created in $filename"); 52 | $output->writeln( 53 | 'To use this group extension, include it to "extensions" option of global Codeception config.' 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package/bin: -------------------------------------------------------------------------------- 1 | add(new Codeception\Command\Build('build')); 10 | $app->add(new Codeception\Command\Run('run')); 11 | $app->add(new Codeception\Command\Init('init')); 12 | $app->add(new Codeception\Command\Console('console')); 13 | $app->add(new Codeception\Command\Bootstrap('bootstrap')); 14 | $app->add(new Codeception\Command\GenerateCept('generate:cept')); 15 | $app->add(new Codeception\Command\GenerateCest('generate:cest')); 16 | $app->add(new Codeception\Command\GenerateTest('generate:test')); 17 | $app->add(new Codeception\Command\GenerateSuite('generate:suite')); 18 | $app->add(new Codeception\Command\GenerateHelper('generate:helper')); 19 | $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios')); 20 | $app->add(new Codeception\Command\Clean('clean')); 21 | $app->add(new Codeception\Command\GenerateGroup('generate:groupobject')); 22 | $app->add(new Codeception\Command\GeneratePageObject('generate:pageobject')); 23 | $app->add(new Codeception\Command\GenerateStepObject('generate:stepobject')); 24 | $app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot')); 25 | $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); 26 | $app->add(new Codeception\Command\GenerateFeature('generate:feature')); 27 | $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); 28 | $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); 29 | $app->add(new Codeception\Command\DryRun('dry-run')); 30 | $app->add(new Codeception\Command\ConfigValidate('config:validate')); 31 | $app->registerCustomCommands(); 32 | 33 | // add only if within a phar archive. 34 | if ('phar:' === substr(__FILE__, 0, 5)) { 35 | $app->add(new Codeception\Command\SelfUpdate('self-update')); 36 | } 37 | 38 | $app->run(); 39 | -------------------------------------------------------------------------------- /src/Codeception/Step/ConditionalAssertion.php: -------------------------------------------------------------------------------- 1 | getMessage(), $e->getCode(), $e); 16 | } 17 | } 18 | 19 | public function getAction() 20 | { 21 | $action = 'can' . ucfirst($this->action); 22 | $action = preg_replace('/^canDont/', 'cant', $action); 23 | return $action; 24 | } 25 | 26 | public function getHumanizedAction() 27 | { 28 | return $this->humanize($this->action . ' ' . $this->getHumanizedArguments()); 29 | } 30 | 31 | public static function getTemplate(Template $template) 32 | { 33 | $action = $template->getVar('action'); 34 | 35 | if ((0 !== strpos($action, 'see')) && (0 !== strpos($action, 'dontSee'))) { 36 | return ''; 37 | } 38 | 39 | $conditionalDoc = "* [!] Conditional Assertion: Test won't be stopped on fail\n " . $template->getVar('doc'); 40 | 41 | $prefix = 'can'; 42 | if (strpos($action, 'dontSee') === 0) { 43 | $prefix = 'cant'; 44 | $action = str_replace('dont', '', $action); 45 | } 46 | 47 | return $template 48 | ->place('doc', $conditionalDoc) 49 | ->place('action', $prefix . ucfirst($action)) 50 | ->place('step', 'ConditionalAssertion'); 51 | } 52 | 53 | public function match($name) 54 | { 55 | return 0 === strpos($name, 'see') || 0 === strpos($name, 'dontSee'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ext/SimpleReporter.php: -------------------------------------------------------------------------------- 1 | options['silent'] = false; // turn on printing for this extension 18 | $this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else 19 | } 20 | 21 | // we are listening for events 22 | public static $events = [ 23 | Events::SUITE_BEFORE => 'beforeSuite', 24 | Events::TEST_END => 'after', 25 | Events::TEST_SUCCESS => 'success', 26 | Events::TEST_FAIL => 'fail', 27 | Events::TEST_ERROR => 'error', 28 | ]; 29 | 30 | public function beforeSuite() 31 | { 32 | $this->writeln(""); 33 | } 34 | 35 | public function success() 36 | { 37 | $this->write('[+] '); 38 | } 39 | 40 | public function fail() 41 | { 42 | $this->write('[-] '); 43 | } 44 | 45 | public function error() 46 | { 47 | $this->write('[E] '); 48 | } 49 | 50 | // we are printing test status and time taken 51 | public function after(TestEvent $e) 52 | { 53 | $seconds_input = $e->getTime(); 54 | // stack overflow: http://stackoverflow.com/questions/16825240/how-to-convert-microtime-to-hhmmssuu 55 | $seconds = (int)($milliseconds = (int)($seconds_input * 1000)) / 1000; 56 | $time = ($seconds % 60) . (($milliseconds === 0) ? '' : '.' . $milliseconds); 57 | 58 | $this->write(Descriptor::getTestSignature($e->getTest())); 59 | $this->writeln(' (' . $time . 's)'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Test.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 49 | $this->name = $this->removeSuffix($name, 'Test'); 50 | } 51 | 52 | public function produce() 53 | { 54 | $actor = $this->settings['actor']; 55 | if ($this->settings['namespace']) { 56 | $actor = $this->settings['namespace'] . '\\' . $actor; 57 | } 58 | 59 | $ns = $this->getNamespaceHeader($this->settings['namespace'] . '\\' . $this->name); 60 | 61 | $tester = ''; 62 | if ($this->settings['actor']) { 63 | $tester = (new Template($this->testerTemplate)) 64 | ->place('actorClass', $actor) 65 | ->place('actor', lcfirst(Configuration::config()['actor_suffix'])) 66 | ->produce(); 67 | } 68 | 69 | return (new Template($this->template)) 70 | ->place('namespace', $ns) 71 | ->place('name', $this->getShortClassName($this->name)) 72 | ->place('tester', $tester) 73 | ->produce(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateCept.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 26 | new InputArgument('suite', InputArgument::REQUIRED, 'suite to be tested'), 27 | new InputArgument('test', InputArgument::REQUIRED, 'test to be run'), 28 | ]); 29 | } 30 | 31 | public function getDescription() 32 | { 33 | return 'Generates empty Cept file in suite'; 34 | } 35 | 36 | public function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $suite = $input->getArgument('suite'); 39 | $filename = $input->getArgument('test'); 40 | 41 | $config = $this->getSuiteConfig($suite); 42 | $this->createDirectoryFor($config['path'], $filename); 43 | 44 | $filename = $this->completeSuffix($filename, 'Cept'); 45 | $gen = new Cept($config); 46 | 47 | $full_path = rtrim($config['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; 48 | $res = $this->createFile($full_path, $gen->produce()); 49 | if (!$res) { 50 | $output->writeln("Test $filename already exists"); 51 | return; 52 | } 53 | $output->writeln("Test was created in $full_path"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Codeception/Command/Init.php: -------------------------------------------------------------------------------- 1 | setDefinition( 18 | [ 19 | new InputArgument('template', InputArgument::REQUIRED, 'Init template for the setup'), 20 | new InputOption('path', null, InputOption::VALUE_REQUIRED, 'Change current directory', null), 21 | new InputOption('namespace', null, InputOption::VALUE_OPTIONAL, 'Namespace to add for actor classes and helpers\'', null), 22 | 23 | ] 24 | ); 25 | } 26 | 27 | public function getDescription() 28 | { 29 | return "Creates test suites by a template"; 30 | } 31 | 32 | public function execute(InputInterface $input, OutputInterface $output) 33 | { 34 | $template = $input->getArgument('template'); 35 | 36 | if (class_exists($template)) { 37 | $className = $template; 38 | } else { 39 | $className = 'Codeception\Template\\' . ucfirst($template); 40 | 41 | if (!class_exists($className)) { 42 | throw new \Exception("Template from a $className can't be loaded; Init can't be executed"); 43 | } 44 | } 45 | 46 | $initProcess = new $className($input, $output); 47 | if (!$initProcess instanceof InitTemplate) { 48 | throw new \Exception("$className is not a valid template"); 49 | } 50 | if ($input->getOption('path')) { 51 | $initProcess->initDir($input->getOption('path')); 52 | } 53 | $initProcess->setup(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Codeception/Command/Shared/FileSystem.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 39 | $this->name = $this->getShortClassName($name); 40 | $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Step\\' . $name); 41 | } 42 | 43 | public function produce() 44 | { 45 | $actor = $this->settings['actor']; 46 | if (!$actor) { 47 | throw new ConfigurationException("Steps can't be created for suite without an actor"); 48 | } 49 | $ns = $this->getNamespaceString($this->settings['namespace'] . '\\' . $actor . '\\' . $this->name); 50 | $ns = ltrim($ns, '\\'); 51 | 52 | $extended = '\\' . ltrim('\\' . $this->settings['namespace'] . '\\' . $actor, '\\'); 53 | 54 | return (new Template($this->template)) 55 | ->place('namespace', $this->namespace) 56 | ->place('name', $this->name) 57 | ->place('actorClass', $extended) 58 | ->place('actions', $this->actions) 59 | ->produce(); 60 | } 61 | 62 | public function createAction($action) 63 | { 64 | $this->actions .= (new Template($this->actionTemplate)) 65 | ->place('action', $action) 66 | ->produce(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateTest.php: -------------------------------------------------------------------------------- 1 | setDefinition( 24 | [ 25 | new InputArgument('suite', InputArgument::REQUIRED, 'suite where tests will be put'), 26 | new InputArgument('class', InputArgument::REQUIRED, 'class name'), 27 | ] 28 | ); 29 | parent::configure(); 30 | } 31 | 32 | public function getDescription() 33 | { 34 | return 'Generates empty unit test file in suite'; 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $suite = $input->getArgument('suite'); 40 | $class = $input->getArgument('class'); 41 | 42 | $config = $this->getSuiteConfig($suite); 43 | 44 | $className = $this->getShortClassName($class); 45 | $path = $this->createDirectoryFor($config['path'], $class); 46 | 47 | $filename = $this->completeSuffix($className, 'Test'); 48 | $filename = $path . $filename; 49 | 50 | $gen = new TestGenerator($config, $class); 51 | 52 | $res = $this->createFile($filename, $gen->produce()); 53 | 54 | if (!$res) { 55 | $output->writeln("Test $filename already exists"); 56 | return; 57 | } 58 | $output->writeln("Test was created in $filename"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php: -------------------------------------------------------------------------------- 1 | handler = function (Event $event) { 24 | if ($event->sender instanceof Connection) { 25 | $this->connectionOpened($event->sender); 26 | } 27 | }; 28 | } 29 | 30 | protected function connectionOpened(Connection $connection) 31 | { 32 | $this->debug('Connection opened!'); 33 | if ($connection instanceof Connection) { 34 | $this->connections[] = $connection; 35 | } 36 | } 37 | 38 | public function start() 39 | { 40 | Event::on(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler); 41 | $this->debug('watching new connections'); 42 | } 43 | 44 | public function stop() 45 | { 46 | Event::off(Connection::class, Connection::EVENT_AFTER_OPEN, $this->handler); 47 | $this->debug('no longer watching new connections'); 48 | } 49 | 50 | public function closeAll() 51 | { 52 | $count = count($this->connections); 53 | $this->debug("closing all ($count) connections"); 54 | foreach ($this->connections as $connection) { 55 | $connection->close(); 56 | } 57 | } 58 | 59 | protected function debug($message) 60 | { 61 | $title = (new \ReflectionClass($this))->getShortName(); 62 | if (is_array($message) or is_object($message)) { 63 | $message = stripslashes(json_encode($message)); 64 | } 65 | codecept_debug("[$title] $message"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /codecept: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new Codeception\Command\Build('build')); 13 | $app->add(new Codeception\Command\Run('run')); 14 | $app->add(new Codeception\Command\Init('init')); 15 | $app->add(new Codeception\Command\Console('console')); 16 | $app->add(new Codeception\Command\Bootstrap('bootstrap')); 17 | $app->add(new Codeception\Command\GenerateCept('generate:cept')); 18 | $app->add(new Codeception\Command\GenerateCest('generate:cest')); 19 | $app->add(new Codeception\Command\GenerateTest('generate:test')); 20 | $app->add(new Codeception\Command\GenerateSuite('generate:suite')); 21 | $app->add(new Codeception\Command\GenerateHelper('generate:helper')); 22 | $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios')); 23 | $app->add(new Codeception\Command\Clean('clean')); 24 | $app->add(new Codeception\Command\GenerateGroup('generate:groupobject')); 25 | $app->add(new Codeception\Command\GeneratePageObject('generate:pageobject')); 26 | $app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot')); 27 | $app->add(new Codeception\Command\GenerateStepObject('generate:stepobject')); 28 | $app->add(new Codeception\Command\GenerateEnvironment('generate:environment')); 29 | $app->add(new Codeception\Command\GenerateFeature('generate:feature')); 30 | $app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets')); 31 | $app->add(new Codeception\Command\GherkinSteps('gherkin:steps')); 32 | $app->add(new Codeception\Command\DryRun('dry-run')); 33 | $app->add(new Codeception\Command\ConfigValidate('config:validate')); 34 | 35 | // Suggests package 36 | if (class_exists('Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand')) { 37 | $app->add(new Codeception\Command\Completion()); 38 | } else { 39 | $app->add(new Codeception\Command\CompletionFallback()); 40 | } 41 | 42 | $app->registerCustomCommands(); 43 | $app->run(); 44 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateEnvironment.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 26 | new InputArgument('env', InputArgument::REQUIRED, 'Environment name'), 27 | ]); 28 | } 29 | 30 | public function getDescription() 31 | { 32 | return 'Generates empty environment config'; 33 | } 34 | 35 | public function execute(InputInterface $input, OutputInterface $output) 36 | { 37 | $conf = $this->getGlobalConfig(); 38 | if (!Configuration::envsDir()) { 39 | throw new ConfigurationException( 40 | "Path for environments configuration is not set.\n" 41 | . "Please specify envs path in your `codeception.yml`\n \n" 42 | . "envs: tests/_envs" 43 | ); 44 | } 45 | $relativePath = $conf['paths']['envs']; 46 | $env = $input->getArgument('env'); 47 | $file = "$env.yml"; 48 | 49 | $path = $this->createDirectoryFor($relativePath, $file); 50 | $saved = $this->createFile($path . $file, "# `$env` environment config goes here"); 51 | 52 | if ($saved) { 53 | $output->writeln("$env config was created in $relativePath/$file"); 54 | } else { 55 | $output->writeln("File $relativePath/$file already exists"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/Snapshot.php: -------------------------------------------------------------------------------- 1 | {{actor}} = \$I; 36 | } 37 | EOF; 38 | 39 | protected $namespace; 40 | protected $name; 41 | protected $settings; 42 | 43 | public function __construct($settings, $name) 44 | { 45 | $this->settings = $settings; 46 | $this->name = $this->getShortClassName($name); 47 | $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Snapshot\\' . $name); 48 | } 49 | 50 | public function produce() 51 | { 52 | return (new Template($this->template)) 53 | ->place('namespace', $this->namespace) 54 | ->place('actions', $this->produceActions()) 55 | ->place('name', $this->name) 56 | ->produce(); 57 | } 58 | 59 | protected function produceActions() 60 | { 61 | if (!isset($this->settings['actor'])) { 62 | return ''; // no actor in suite 63 | } 64 | 65 | $actor = lcfirst($this->settings['actor']); 66 | $actorClass = $this->settings['actor']; 67 | if (!empty($this->settings['namespace'])) { 68 | $actorClass = rtrim($this->settings['namespace'], '\\') . '\\' . $actorClass; 69 | } 70 | 71 | return (new Template($this->actionsTemplate)) 72 | ->place('actorClass', $actorClass) 73 | ->place('actor', $actor) 74 | ->produce(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateFeature.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 27 | new InputArgument('suite', InputArgument::REQUIRED, 'suite to be tested'), 28 | new InputArgument('feature', InputArgument::REQUIRED, 'feature to be generated'), 29 | new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), 30 | ]); 31 | } 32 | 33 | public function getDescription() 34 | { 35 | return 'Generates empty feature file in suite'; 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $suite = $input->getArgument('suite'); 41 | $filename = $input->getArgument('feature'); 42 | 43 | $config = $this->getSuiteConfig($suite); 44 | $this->createDirectoryFor($config['path'], $filename); 45 | 46 | $gen = new Feature(basename($filename)); 47 | if (!preg_match('~\.feature$~', $filename)) { 48 | $filename .= '.feature'; 49 | } 50 | $full_path = rtrim($config['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename; 51 | $res = $this->createFile($full_path, $gen->produce()); 52 | if (!$res) { 53 | $output->writeln("Feature $filename already exists"); 54 | return; 55 | } 56 | $output->writeln("Feature was created in $full_path"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Actor/Shared/Pause.php: -------------------------------------------------------------------------------- 1 | setAutocompleter( 22 | new \Hoa\Console\Readline\Autocompleter\Word(get_class_methods($this)) 23 | ); 24 | $output = new ConsoleOutput(); 25 | $output->writeln(" Execution PAUSED, starting interactive shell..."); 26 | $output->writeln(" Type in commands to try them, ENTER to continue, TAB to auto-complete"); 27 | 28 | $result = ''; 29 | 30 | do { 31 | $command = $readline->readLine('$I->'); // “> ” is the prefix of the line. 32 | 33 | if ($command == 'exit') { 34 | return; 35 | } 36 | if ($command == '') { 37 | return; 38 | } 39 | try { 40 | $value = eval("return \$I->$command;"); 41 | if ($value) { 42 | $result = $value; 43 | if (!is_object($result)) { 44 | codecept_debug($result); 45 | } 46 | codecept_debug('>> Result saved to $result variable, you can use it in next commands'); 47 | } 48 | } catch (\PHPUnit\Framework\AssertionFailedError $fail) { 49 | $output->writeln("fail " . $fail->getMessage()); 50 | } catch (\Exception $e) { 51 | $output->writeln("error " . $e->getMessage()); 52 | } catch (\Throwable $e) { 53 | $output->writeln("syntax error " . $e->getMessage()); 54 | } 55 | } while (true); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateCest.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 27 | new InputArgument('suite', InputArgument::REQUIRED, 'suite where tests will be put'), 28 | new InputArgument('class', InputArgument::REQUIRED, 'test name'), 29 | ]); 30 | } 31 | 32 | public function getDescription() 33 | { 34 | return 'Generates empty Cest file in suite'; 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $suite = $input->getArgument('suite'); 40 | $class = $input->getArgument('class'); 41 | 42 | $config = $this->getSuiteConfig($suite); 43 | $className = $this->getShortClassName($class); 44 | $path = $this->createDirectoryFor($config['path'], $class); 45 | 46 | $filename = $this->completeSuffix($className, 'Cest'); 47 | $filename = $path . $filename; 48 | 49 | if (file_exists($filename)) { 50 | $output->writeln("Test $filename already exists"); 51 | return; 52 | } 53 | $gen = new CestGenerator($class, $config); 54 | $res = $this->createFile($filename, $gen->produce()); 55 | if (!$res) { 56 | $output->writeln("Test $filename already exists"); 57 | return; 58 | } 59 | 60 | $output->writeln("Test was created in $filename"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Connector/Universal.php: -------------------------------------------------------------------------------- 1 | index = $index; 17 | } 18 | 19 | public function mockResponse($response) 20 | { 21 | $this->mockedResponse = $response; 22 | } 23 | 24 | public function doRequest($request) 25 | { 26 | if ($this->mockedResponse) { 27 | $response = $this->mockedResponse; 28 | $this->mockedResponse = null; 29 | return $response; 30 | } 31 | 32 | $_COOKIE = $request->getCookies(); 33 | $_SERVER = $request->getServer(); 34 | $_FILES = $this->remapFiles($request->getFiles()); 35 | 36 | $uri = str_replace('http://localhost', '', $request->getUri()); 37 | 38 | $_REQUEST = $this->remapRequestParameters($request->getParameters()); 39 | if (strtoupper($request->getMethod()) == 'GET') { 40 | $_GET = $_REQUEST; 41 | } else { 42 | $_POST = $_REQUEST; 43 | } 44 | 45 | $_SERVER['REQUEST_METHOD'] = strtoupper($request->getMethod()); 46 | $_SERVER['REQUEST_URI'] = $uri; 47 | 48 | ob_start(); 49 | include $this->index; 50 | 51 | $content = ob_get_contents(); 52 | ob_end_clean(); 53 | 54 | $headers = []; 55 | $php_headers = headers_list(); 56 | foreach ($php_headers as $value) { 57 | // Get the header name 58 | $parts = explode(':', $value); 59 | if (count($parts) > 1) { 60 | $name = trim(array_shift($parts)); 61 | // Build the header hash map 62 | $headers[$name] = trim(implode(':', $parts)); 63 | } 64 | } 65 | $headers['Content-type'] = isset($headers['Content-type']) 66 | ? $headers['Content-type'] 67 | : "text/html; charset=UTF-8"; 68 | 69 | $response = new Response($content, 200, $headers); 70 | return $response; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Codeception/Suite.php: -------------------------------------------------------------------------------- 1 | tests as $test) { 16 | $tests = array_merge($tests, $this->getDependencies($test)); 17 | } 18 | 19 | $queue = []; 20 | $hashes = []; 21 | foreach ($tests as $test) { 22 | if (in_array(spl_object_hash($test), $hashes)) { 23 | continue; 24 | } 25 | $hashes[] = spl_object_hash($test); 26 | $queue[] = $test; 27 | } 28 | $this->tests = $queue; 29 | } 30 | 31 | protected function getDependencies($test) 32 | { 33 | if (!$test instanceof Dependent) { 34 | return [$test]; 35 | } 36 | $tests = []; 37 | foreach ($test->fetchDependencies() as $requiredTestName) { 38 | $required = $this->findMatchedTest($requiredTestName); 39 | if (!$required) { 40 | continue; 41 | } 42 | $tests = array_merge($tests, $this->getDependencies($required)); 43 | } 44 | $tests[] = $test; 45 | return $tests; 46 | } 47 | 48 | protected function findMatchedTest($testSignature) 49 | { 50 | foreach ($this->tests as $test) { 51 | $signature = Descriptor::getTestSignature($test); 52 | if ($signature === $testSignature) { 53 | return $test; 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * @return mixed 60 | */ 61 | public function getModules() 62 | { 63 | return $this->modules; 64 | } 65 | 66 | /** 67 | * @param mixed $modules 68 | */ 69 | public function setModules($modules) 70 | { 71 | $this->modules = $modules; 72 | } 73 | 74 | /** 75 | * @return mixed 76 | */ 77 | public function getBaseName() 78 | { 79 | return $this->baseName; 80 | } 81 | 82 | /** 83 | * @param mixed $baseName 84 | */ 85 | public function setBaseName($baseName) 86 | { 87 | $this->baseName = $baseName; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Codeception/Command/GherkinSteps.php: -------------------------------------------------------------------------------- 1 | setDefinition( 28 | [ 29 | new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'), 30 | new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config'), 31 | ] 32 | ); 33 | parent::configure(); 34 | } 35 | 36 | public function getDescription() 37 | { 38 | return 'Prints all defined feature steps'; 39 | } 40 | 41 | public function execute(InputInterface $input, OutputInterface $output) 42 | { 43 | $this->addStyles($output); 44 | $suite = $input->getArgument('suite'); 45 | $config = $this->getSuiteConfig($suite); 46 | $config['describe_steps'] = true; 47 | 48 | $loader = new Gherkin($config); 49 | $steps = $loader->getSteps(); 50 | 51 | foreach ($steps as $name => $context) { 52 | /** @var $table Table **/ 53 | $table = new Table($output); 54 | $table->setHeaders(['Step', 'Implementation']); 55 | $output->writeln("Steps from $name context:"); 56 | 57 | foreach ($context as $step => $callable) { 58 | if (count($callable) < 2) { 59 | continue; 60 | } 61 | $method = $callable[0] . '::' . $callable[1]; 62 | $table->addRow([$step, $method]); 63 | } 64 | $table->render(); 65 | } 66 | 67 | if (!isset($table)) { 68 | $output->writeln("No steps are defined, start creating them by running gherkin:snippets"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Codeception/Command/Bootstrap.php: -------------------------------------------------------------------------------- 1 | setDefinition( 30 | [ 31 | new InputArgument('path', InputArgument::OPTIONAL, 'custom installation dir', null), 32 | new InputOption( 33 | 'namespace', 34 | 's', 35 | InputOption::VALUE_OPTIONAL, 36 | 'Namespace to add for actor classes and helpers' 37 | ), 38 | new InputOption('actor', 'a', InputOption::VALUE_OPTIONAL, 'Custom actor instead of Tester'), 39 | new InputOption('empty', 'e', InputOption::VALUE_NONE, 'Don\'t create standard suites') 40 | ] 41 | ); 42 | } 43 | 44 | public function getDescription() 45 | { 46 | return "Creates default test suites and generates all required files"; 47 | } 48 | 49 | public function execute(InputInterface $input, OutputInterface $output) 50 | { 51 | $bootstrap = new BootstrapTemplate($input, $output); 52 | if ($input->getArgument('path')) { 53 | $bootstrap->initDir($input->getArgument('path')); 54 | } 55 | $bootstrap->setup(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Codeception/Test/Cept.php: -------------------------------------------------------------------------------- 1 | setName($name); 25 | $metadata->setFilename($file); 26 | $this->setMetadata($metadata); 27 | $this->createScenario(); 28 | $this->parser = new Parser($this->getScenario(), $this->getMetadata()); 29 | } 30 | 31 | public function preload() 32 | { 33 | $this->getParser()->prepareToRun($this->getSourceCode()); 34 | } 35 | 36 | public function test() 37 | { 38 | $scenario = $this->getScenario(); 39 | $testFile = $this->getMetadata()->getFilename(); 40 | /** @noinspection PhpIncludeInspection */ 41 | try { 42 | require $testFile; 43 | } catch (\ParseError $e) { 44 | throw new TestParseException($testFile, $e->getMessage(), $e->getLine()); 45 | } 46 | } 47 | 48 | public function getSignature() 49 | { 50 | return $this->getMetadata()->getName() . 'Cept'; 51 | } 52 | 53 | public function toString() 54 | { 55 | return $this->getSignature() . ': ' . Message::ucfirst($this->getFeature()); 56 | } 57 | 58 | public function getSourceCode() 59 | { 60 | return file_get_contents($this->getFileName()); 61 | } 62 | 63 | public function getReportFields() 64 | { 65 | return [ 66 | 'name' => basename($this->getFileName(), 'Cept.php'), 67 | 'file' => $this->getFileName(), 68 | 'feature' => $this->getFeature() 69 | ]; 70 | } 71 | 72 | /** 73 | * @return Parser 74 | */ 75 | protected function getParser() 76 | { 77 | return $this->parser; 78 | } 79 | 80 | public function fetchDependencies() 81 | { 82 | return $this->getMetadata()->getDependencies(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PruneTest.php: -------------------------------------------------------------------------------- 1 | '/.*Yii2.*/', 41 | 'Lumen' => '/.*(Lumen|LaravelCommon).*/', 42 | 'Laravel' => '/.*Laravel.*/', 43 | 'Phalcon' => '/.*Phalcon.*/', 44 | 'Symfony' => '/.*Symfony.*/', 45 | 'ZendExpressive' => '/.*ZendExpressive.*/', 46 | 'Zend2' => '/.*ZF2.*/', 47 | ]; 48 | 49 | // First check if changes include files that are not framework files. 50 | $frameworkOnly = true; 51 | $frameworks = []; 52 | foreach ($files as $file) { 53 | $match = false; 54 | stderr("Testing file: $file"); 55 | foreach ($regexes as $framework => $regex) { 56 | stderr("Checking framework $framework...", false); 57 | if (preg_match($regex, $file)) { 58 | $match = true; 59 | $frameworks[$framework] = $framework; 60 | stderr("MATCH"); 61 | break; 62 | } 63 | stderr('X'); 64 | } 65 | if (!$match) { 66 | stderr("No framework matched, need to run all tests"); 67 | $frameworkOnly = false; 68 | break; 69 | } 70 | } 71 | 72 | if ($frameworkOnly) { 73 | stderr('Changes limited to frameworks: ' . implode(', ', $frameworks)); 74 | if (!isset($frameworks[$currentFramework])) { 75 | stderr("Skipping test for framework: $currentFramework"); 76 | echo "travis_terminate 0\n"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateSnapshot.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 28 | new InputArgument('suite', InputArgument::REQUIRED, 'Suite name or snapshot name)'), 29 | new InputArgument('snapshot', InputArgument::OPTIONAL, 'Name of snapshot'), 30 | ]); 31 | parent::configure(); 32 | } 33 | 34 | public function getDescription() 35 | { 36 | return 'Generates empty Snapshot class'; 37 | } 38 | 39 | public function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $suite = $input->getArgument('suite'); 42 | $class = $input->getArgument('snapshot'); 43 | 44 | if (!$class) { 45 | $class = $suite; 46 | $suite = null; 47 | } 48 | 49 | $conf = $suite 50 | ? $this->getSuiteConfig($suite) 51 | : $this->getGlobalConfig(); 52 | 53 | if ($suite) { 54 | $suite = DIRECTORY_SEPARATOR . ucfirst($suite); 55 | } 56 | 57 | $path = $this->createDirectoryFor(Configuration::supportDir() . 'Snapshot' . $suite, $class); 58 | 59 | $filename = $path . $this->getShortClassName($class) . '.php'; 60 | 61 | $output->writeln($filename); 62 | 63 | $gen = new SnapshotGenerator($conf, ucfirst($suite) . '\\' . $class); 64 | $res = $this->createFile($filename, $gen->produce()); 65 | 66 | if (!$res) { 67 | $output->writeln("Snapshot $filename already exists"); 68 | exit; 69 | } 70 | $output->writeln("Snapshot was created in $filename"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Codeception/Util/Template.php: -------------------------------------------------------------------------------- 1 | template = $template; 22 | $this->placeholderStart = $placeholderStart; 23 | $this->placeholderEnd = $placeholderEnd; 24 | } 25 | 26 | /** 27 | * Replaces {{var}} string with provided value 28 | * 29 | * @param $var 30 | * @param $val 31 | * @return $this 32 | */ 33 | public function place($var, $val) 34 | { 35 | $this->vars[$var] = $val; 36 | return $this; 37 | } 38 | 39 | /** 40 | * Sets all template vars 41 | * 42 | * @param array $vars 43 | */ 44 | public function setVars(array $vars) 45 | { 46 | $this->vars = $vars; 47 | } 48 | 49 | public function getVar($name) 50 | { 51 | if (isset($this->vars[$name])) { 52 | return $this->vars[$name]; 53 | } 54 | } 55 | 56 | /** 57 | * Fills up template string with placed variables. 58 | * 59 | * @return mixed 60 | */ 61 | public function produce() 62 | { 63 | $result = $this->template; 64 | $regex = sprintf('~%s([\w\.]+)%s~m', $this->placeholderStart, $this->placeholderEnd); 65 | 66 | $matched = preg_match_all($regex, $result, $matches, PREG_SET_ORDER); 67 | if (!$matched) { 68 | return $result; 69 | } 70 | 71 | foreach ($matches as $match) { // fill in placeholders 72 | $placeholder = $match[1]; 73 | $value = $this->vars; 74 | foreach (explode('.', $placeholder) as $segment) { 75 | if (is_array($value) && array_key_exists($segment, $value)) { 76 | $value = $value[$segment]; 77 | } else { 78 | continue 2; 79 | } 80 | } 81 | 82 | $result = str_replace($this->placeholderStart . $placeholder . $this->placeholderEnd, $value, $result); 83 | } 84 | return $result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/AutoRebuild.php: -------------------------------------------------------------------------------- 1 | 'updateActor' 16 | ]; 17 | 18 | public function updateActor(SuiteEvent $e) 19 | { 20 | $settings = $e->getSettings(); 21 | if (!$settings['actor']) { 22 | codecept_debug('actor is empty'); 23 | return; // no actor 24 | } 25 | 26 | $modules = $e->getSuite()->getModules(); 27 | 28 | $actorActionsFile = Configuration::supportDir() . '_generated' . DIRECTORY_SEPARATOR 29 | . $settings['actor'] . 'Actions.php'; 30 | 31 | if (!file_exists($actorActionsFile)) { 32 | codecept_debug("Generating {$settings['actor']}Actions..."); 33 | $this->generateActorActions($actorActionsFile, $settings); 34 | return; 35 | } 36 | 37 | // load actor class to see hash 38 | $handle = @fopen($actorActionsFile, "r"); 39 | if ($handle and is_writable($actorActionsFile)) { 40 | $line = @fgets($handle); 41 | if (preg_match('~\[STAMP\] ([a-f0-9]*)~', $line, $matches)) { 42 | $hash = $matches[1]; 43 | $currentHash = Actions::genHash($modules, $settings); 44 | 45 | // regenerate actor class when hashes do not match 46 | if ($hash != $currentHash) { 47 | codecept_debug("Rebuilding {$settings['actor']}..."); 48 | @fclose($handle); 49 | $this->generateActorActions($actorActionsFile, $settings); 50 | return; 51 | } 52 | } 53 | @fclose($handle); 54 | } 55 | } 56 | 57 | protected function generateActorActions($actorActionsFile, $settings) 58 | { 59 | if (!file_exists(Configuration::supportDir() . '_generated')) { 60 | @mkdir(Configuration::supportDir() . '_generated'); 61 | } 62 | $actionsGenerator = new Actions($settings); 63 | $generated = $actionsGenerator->produce(); 64 | @file_put_contents($actorActionsFile, $generated); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Driver/Beanstalk.php: -------------------------------------------------------------------------------- 1 | queue = new Pheanstalk($config['host'], $config['port'], $config['timeout']); 19 | } 20 | 21 | /** 22 | * Post/Put a message on to the queue server 23 | * 24 | * @param string $message Message Body to be send 25 | * @param string $queue Queue Name 26 | */ 27 | public function addMessageToQueue($message, $queue) 28 | { 29 | $this->queue->putInTube($queue, $message); 30 | } 31 | 32 | /** 33 | * Count the total number of messages on the queue. 34 | * 35 | * @param $queue Queue Name 36 | * 37 | * @return int Count 38 | */ 39 | public function getMessagesTotalCountOnQueue($queue) 40 | { 41 | try { 42 | return $this->queue->statsTube($queue)['total-jobs']; 43 | } catch (ConnectionException $ex) { 44 | \PHPUnit\Framework\Assert::fail("queue [$queue] not found"); 45 | } 46 | } 47 | 48 | public function clearQueue($queue = 'default') 49 | { 50 | while ($job = $this->queue->reserveFromTube($queue, 0)) { 51 | $this->queue->delete($job); 52 | } 53 | } 54 | 55 | /** 56 | * Return a list of queues/tubes on the queueing server 57 | * 58 | * @return array Array of Queues 59 | */ 60 | public function getQueues() 61 | { 62 | return $this->queue->listTubes(); 63 | } 64 | 65 | /** 66 | * Count the current number of messages on the queue. 67 | * 68 | * @param $queue Queue Name 69 | * 70 | * @return int Count 71 | */ 72 | public function getMessagesCurrentCountOnQueue($queue) 73 | { 74 | try { 75 | return $this->queue->statsTube($queue)['current-jobs-ready']; 76 | } catch (ConnectionException $e) { 77 | \PHPUnit\Framework\Assert::fail("queue [$queue] not found"); 78 | } 79 | } 80 | 81 | public function getRequiredConfig() 82 | { 83 | return ['host']; 84 | } 85 | 86 | public function getDefaultConfig() 87 | { 88 | return ['port' => 11300, 'timeout' => 90]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Friend.php: -------------------------------------------------------------------------------- 1 | name = $name; 17 | $this->actor = $actor; 18 | 19 | $this->multiSessionModules = array_filter($modules, function ($m) { 20 | return $m instanceof Interfaces\MultiSession; 21 | }); 22 | 23 | if (empty($this->multiSessionModules)) { 24 | throw new TestRuntimeException("No multisession modules used. Can't instantiate friend"); 25 | } 26 | } 27 | 28 | public function does($closure) 29 | { 30 | $currentUserData = []; 31 | 32 | foreach ($this->multiSessionModules as $module) { 33 | $name = $module->_getName(); 34 | $currentUserData[$name] = $module->_backupSession(); 35 | if (empty($this->data[$name])) { 36 | $module->_initializeSession(); 37 | $this->data[$name] = $module->_backupSession(); 38 | continue; 39 | } 40 | $module->_loadSession($this->data[$name]); 41 | }; 42 | 43 | $this->actor->comment(strtoupper("{$this->name} does ---")); 44 | $ret = $closure($this->actor); 45 | $this->actor->comment(strtoupper("--- {$this->name} finished")); 46 | 47 | foreach ($this->multiSessionModules as $module) { 48 | $name = $module->_getName(); 49 | $this->data[$name] = $module->_backupSession(); 50 | $module->_loadSession($currentUserData[$name]); 51 | }; 52 | return $ret; 53 | } 54 | 55 | public function isGoingTo($argumentation) 56 | { 57 | $this->actor->amGoingTo($argumentation); 58 | } 59 | 60 | public function expects($prediction) 61 | { 62 | $this->actor->expect($prediction); 63 | } 64 | 65 | public function expectsTo($prediction) 66 | { 67 | $this->actor->expectTo($prediction); 68 | } 69 | 70 | public function leave() 71 | { 72 | foreach ($this->multiSessionModules as $module) { 73 | if (isset($this->data[$module->_getName()])) { 74 | $module->_closeSession($this->data[$module->_getName()]); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Codeception/Command/Shared/Config.php: -------------------------------------------------------------------------------- 1 | getGlobalConfig()); 14 | } 15 | 16 | protected function getGlobalConfig($conf = null) 17 | { 18 | return Configuration::config($conf); 19 | } 20 | 21 | protected function getSuites($conf = null) 22 | { 23 | return Configuration::suites(); 24 | } 25 | 26 | protected function overrideConfig($configOptions) 27 | { 28 | $updatedConfig = []; 29 | foreach ($configOptions as $option) { 30 | $keys = explode(': ', $option); 31 | if (count($keys) < 2) { 32 | throw new \InvalidArgumentException('--config-option should have config passed as "key:value"'); 33 | } 34 | $value = array_pop($keys); 35 | $yaml = ''; 36 | for ($ind = 0; count($keys); $ind += 2) { 37 | $yaml .= "\n" . str_repeat(' ', $ind) . array_shift($keys) . ': '; 38 | } 39 | $yaml .= $value; 40 | try { 41 | $config = Yaml::parse($yaml); 42 | } catch (ParseException $e) { 43 | throw new \Codeception\Exception\ParseException("Overridden config can't be parsed: \n$yaml\n" . $e->getParsedLine()); 44 | } 45 | $updatedConfig = array_merge_recursive($updatedConfig, $config); 46 | } 47 | return Configuration::append($updatedConfig); 48 | } 49 | 50 | protected function enableExtensions($extensions) 51 | { 52 | $config = ['extensions' => ['enabled' => []]]; 53 | foreach ($extensions as $name) { 54 | if (!class_exists($name)) { 55 | $className = 'Codeception\\Extension\\' . ucfirst($name); 56 | if (!class_exists($className)) { 57 | throw new InvalidOptionException("Extension $name can't be loaded (tried by $name and $className)"); 58 | } 59 | $config['extensions']['enabled'][] = $className; 60 | continue; 61 | } 62 | $config['extensions']['enabled'][] = $name; 63 | } 64 | return Configuration::append($config); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Codeception/Command/GeneratePageObject.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 27 | new InputArgument('suite', InputArgument::REQUIRED, 'Either suite name or page object name)'), 28 | new InputArgument('page', InputArgument::OPTIONAL, 'Page name of pageobject to represent'), 29 | ]); 30 | parent::configure(); 31 | } 32 | 33 | public function getDescription() 34 | { 35 | return 'Generates empty PageObject class'; 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $suite = $input->getArgument('suite'); 41 | $class = $input->getArgument('page'); 42 | 43 | if (!$class) { 44 | $class = $suite; 45 | $suite = null; 46 | } 47 | 48 | $conf = $suite 49 | ? $this->getSuiteConfig($suite) 50 | : $this->getGlobalConfig(); 51 | 52 | if ($suite) { 53 | $suite = DIRECTORY_SEPARATOR . ucfirst($suite); 54 | } 55 | 56 | $path = $this->createDirectoryFor(Configuration::supportDir() . 'Page' . $suite, $class); 57 | 58 | $filename = $path . $this->getShortClassName($class) . '.php'; 59 | 60 | $output->writeln($filename); 61 | 62 | $gen = new PageObjectGenerator($conf, ucfirst($suite) . '\\' . $class); 63 | $res = $this->createFile($filename, $gen->produce()); 64 | 65 | if (!$res) { 66 | $output->writeln("PageObject $filename already exists"); 67 | exit; 68 | } 69 | $output->writeln("PageObject was created in $filename"); 70 | } 71 | 72 | protected function pathToPageObject($class, $suite) 73 | { 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Generator/PageObject.php: -------------------------------------------------------------------------------- 1 | {{actor}} = \$I; 51 | } 52 | 53 | EOF; 54 | 55 | protected $actions = ''; 56 | protected $settings; 57 | protected $name; 58 | protected $namespace; 59 | 60 | public function __construct($settings, $name) 61 | { 62 | $this->settings = $settings; 63 | $this->name = $this->getShortClassName($name); 64 | $this->namespace = $this->getNamespaceString($this->settings['namespace'] . '\\Page\\' . $name); 65 | } 66 | 67 | public function produce() 68 | { 69 | return (new Template($this->template)) 70 | ->place('namespace', $this->namespace) 71 | ->place('actions', $this->produceActions()) 72 | ->place('class', $this->name) 73 | ->produce(); 74 | } 75 | 76 | protected function produceActions() 77 | { 78 | if (!isset($this->settings['actor'])) { 79 | return ''; // global pageobject 80 | } 81 | 82 | $actor = lcfirst($this->settings['actor']); 83 | $actorClass = $this->settings['actor']; 84 | if (!empty($this->settings['namespace'])) { 85 | $actorClass = rtrim($this->settings['namespace'], '\\') . '\\' . $actorClass; 86 | } 87 | 88 | return (new Template($this->actionsTemplate)) 89 | ->place('actorClass', $actorClass) 90 | ->place('actor', $actor) 91 | ->place('pageObject', $this->name) 92 | ->produce(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Interfaces/Db.php: -------------------------------------------------------------------------------- 1 | seeInDatabase('users', ['name' => 'Davert', 'email' => 'davert@mail.com']); 13 | * ``` 14 | * Fails if no such user found. 15 | * 16 | * Comparison expressions can be used as well: 17 | * 18 | * ```php 19 | * seeInDatabase('posts', ['num_comments >=' => '0']); 21 | * $I->seeInDatabase('users', ['email like' => 'miles@davis.com']); 22 | * ``` 23 | * 24 | * Supported operators: `<`, `>`, `>=`, `<=`, `!=`, `like`. 25 | * 26 | * 27 | * @param string $table 28 | * @param array $criteria 29 | */ 30 | public function seeInDatabase($table, $criteria = []); 31 | 32 | /** 33 | * Effect is opposite to ->seeInDatabase 34 | * 35 | * Asserts that there is no record with the given column values in a database. 36 | * Provide table name and column values. 37 | * 38 | * ``` php 39 | * dontSeeInDatabase('users', ['name' => 'Davert', 'email' => 'davert@mail.com']); 41 | * ``` 42 | * Fails if such user was found. 43 | * 44 | * Comparison expressions can be used as well: 45 | * 46 | * ```php 47 | * dontSeeInDatabase('posts', ['num_comments >=' => '0']); 49 | * $I->dontSeeInDatabase('users', ['email like' => 'miles%']); 50 | * ``` 51 | * 52 | * Supported operators: `<`, `>`, `>=`, `<=`, `!=`, `like`. 53 | * 54 | * @param string $table 55 | * @param array $criteria 56 | */ 57 | public function dontSeeInDatabase($table, $criteria = []); 58 | 59 | /** 60 | * Fetches a single column value from a database. 61 | * Provide table name, desired column and criteria. 62 | * 63 | * ``` php 64 | * grabFromDatabase('users', 'email', array('name' => 'Davert')); 66 | * ``` 67 | * Comparison expressions can be used as well: 68 | * 69 | * ```php 70 | * grabFromDatabase('posts', ['num_comments >=' => 100]); 72 | * $user = $I->grabFromDatabase('users', ['email like' => 'miles%']); 73 | * ``` 74 | * 75 | * Supported operators: `<`, `>`, `>=`, `<=`, `!=`, `like`. 76 | * 77 | * @param string $table 78 | * @param string $column 79 | * @param array $criteria 80 | * 81 | * @return mixed 82 | */ 83 | public function grabFromDatabase($table, $column, $criteria = []); 84 | } 85 | -------------------------------------------------------------------------------- /src/Codeception/Step/Retry.php: -------------------------------------------------------------------------------- 1 | retry(); 19 | * 20 | * @see \{{module}}::{{method}}() 21 | */ 22 | public function {{action}}({{params}}) { 23 | \$retryNum = isset(\$this->retryNum) ? \$this->retryNum : 1; 24 | \$retryInterval = isset(\$this->retryInterval) ? \$this->retryInterval : 200; 25 | return \$this->getScenario()->runStep(new \Codeception\Step\Retry('{{method}}', func_get_args(), \$retryNum, \$retryInterval)); 26 | } 27 | EOF; 28 | 29 | private $retryNum; 30 | private $retryInterval; 31 | 32 | public function __construct($action, array $arguments = [], $retryNum, $retryInterval) 33 | { 34 | $this->action = $action; 35 | $this->arguments = $arguments; 36 | $this->retryNum = $retryNum; 37 | $this->retryInterval = $retryInterval; 38 | } 39 | 40 | public function run(ModuleContainer $container = null) 41 | { 42 | $retry = 0; 43 | $interval = $this->retryInterval; 44 | while (true) { 45 | try { 46 | $this->isTry = $retry < $this->retryNum; 47 | return parent::run($container); 48 | } catch (\Exception $e) { 49 | $retry++; 50 | if (!$this->isTry) { 51 | throw $e; 52 | } 53 | codecept_debug("Retrying #$retry in ${interval}ms"); 54 | usleep($interval * 1000); 55 | $interval *= 2; 56 | } 57 | } 58 | } 59 | 60 | public static function getTemplate(Template $template) 61 | { 62 | $action = $template->getVar('action'); 63 | 64 | if ((strpos($action, 'have') === 0) || (strpos($action, 'am') === 0)) { 65 | return; // dont retry conditions 66 | } 67 | 68 | if (strpos($action, 'wait') === 0) { 69 | return; // dont retry waiters 70 | } 71 | 72 | $doc = "* Executes $action and retries on failure."; 73 | 74 | return (new Template(self::$methodTemplate)) 75 | ->place('method', $template->getVar('method')) 76 | ->place('module', $template->getVar('module')) 77 | ->place('params', $template->getVar('params')) 78 | ->place('doc', $doc) 79 | ->place('action', 'retry'. ucfirst($action)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Codeception/Util/FileSystem.php: -------------------------------------------------------------------------------- 1 | isDir()) { 28 | rmdir((string)$path); 29 | } else { 30 | unlink((string)$path); 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @param $dir 37 | * @return bool 38 | */ 39 | public static function deleteDir($dir) 40 | { 41 | if (!file_exists($dir)) { 42 | return true; 43 | } 44 | 45 | if (!is_dir($dir) || is_link($dir)) { 46 | return @unlink($dir); 47 | } 48 | 49 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 50 | $dir = str_replace('/', '\\', $dir); 51 | exec('rd /s /q "'.$dir.'"'); 52 | return true; 53 | } 54 | 55 | foreach (scandir($dir) as $item) { 56 | if ($item === '.' || $item === '..') { 57 | continue; 58 | } 59 | 60 | if (!self::deleteDir($dir . DIRECTORY_SEPARATOR . $item)) { 61 | chmod($dir . DIRECTORY_SEPARATOR . $item, 0777); 62 | if (!self::deleteDir($dir . DIRECTORY_SEPARATOR . $item)) { 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | return @rmdir($dir); 69 | } 70 | 71 | /** 72 | * @param $src 73 | * @param $dst 74 | */ 75 | public static function copyDir($src, $dst) 76 | { 77 | $dir = opendir($src); 78 | @mkdir($dst); 79 | while (false !== ($file = readdir($dir))) { 80 | if (($file != '.') && ($file != '..')) { 81 | if (is_dir($src . DIRECTORY_SEPARATOR . $file)) { 82 | self::copyDir($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); 83 | } else { 84 | copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); 85 | } 86 | } 87 | } 88 | closedir($dir); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ext/RunFailed.php: -------------------------------------------------------------------------------- 1 | 'saveFailed' 37 | ]; 38 | 39 | /** @var string filename/groupname for failed tests */ 40 | protected $group = 'failed'; 41 | 42 | public function _initialize() 43 | { 44 | if (array_key_exists('fail-group', $this->config) && $this->config['fail-group']) { 45 | $this->group = $this->config['fail-group']; 46 | } 47 | $logPath = str_replace($this->getRootDir(), '', $this->getLogDir()); // get local path to logs 48 | $this->_reconfigure(['groups' => [$this->group => $logPath . $this->group]]); 49 | } 50 | 51 | public function saveFailed(PrintResultEvent $e) 52 | { 53 | $file = $this->getLogDir() . $this->group; 54 | $result = $e->getResult(); 55 | if ($result->wasSuccessful()) { 56 | if (is_file($file)) { 57 | unlink($file); 58 | } 59 | return; 60 | } 61 | $output = []; 62 | foreach ($result->failures() as $fail) { 63 | $output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest())); 64 | } 65 | foreach ($result->errors() as $fail) { 66 | $output[] = $this->localizePath(Descriptor::getTestFullName($fail->failedTest())); 67 | } 68 | 69 | file_put_contents($file, implode("\n", $output)); 70 | } 71 | 72 | protected function localizePath($path) 73 | { 74 | $root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR; 75 | if (substr($path, 0, strlen($root)) == $root) { 76 | return substr($path, strlen($root)); 77 | } 78 | return $path; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Codeception/Util/XmlStructure.php: -------------------------------------------------------------------------------- 1 | xml = XmlUtils::toXml($xml); 20 | } 21 | 22 | public function matchesXpath($xpath) 23 | { 24 | $path = new \DOMXPath($this->xml); 25 | $res = $path->query($xpath); 26 | if ($res === false) { 27 | throw new MalformedLocatorException($xpath); 28 | } 29 | return $res->length > 0; 30 | } 31 | 32 | /** 33 | * @param $cssOrXPath 34 | * @return \DOMElement 35 | */ 36 | public function matchElement($cssOrXPath) 37 | { 38 | $xpath = new \DOMXpath($this->xml); 39 | try { 40 | $selector = (new CssSelectorConverter())->toXPath($cssOrXPath); 41 | $els = $xpath->query($selector); 42 | if ($els) { 43 | return $els->item(0); 44 | } 45 | } catch (ParseException $e) { 46 | } 47 | $els = $xpath->query($cssOrXPath); 48 | if ($els->length) { 49 | return $els->item(0); 50 | } 51 | throw new ElementNotFound($cssOrXPath); 52 | } 53 | /** 54 | 55 | * @param $xml 56 | * @return bool 57 | */ 58 | public function matchXmlStructure($xml) 59 | { 60 | $xml = XmlUtils::toXml($xml); 61 | $root = $xml->firstChild; 62 | $els = $this->xml->getElementsByTagName($root->nodeName); 63 | if (empty($els)) { 64 | throw new ElementNotFound($root->nodeName, 'Element'); 65 | } 66 | 67 | $matches = false; 68 | foreach ($els as $node) { 69 | $matches |= $this->matchForNode($root, $node); 70 | } 71 | return $matches; 72 | } 73 | 74 | protected function matchForNode($schema, $xml) 75 | { 76 | foreach ($schema->childNodes as $node1) { 77 | $matched = false; 78 | foreach ($xml->childNodes as $node2) { 79 | if ($node1->nodeName == $node2->nodeName) { 80 | $matched = $this->matchForNode($node1, $node2); 81 | if ($matched) { 82 | break; 83 | } 84 | } 85 | } 86 | if (!$matched) { 87 | return false; 88 | } 89 | } 90 | return true; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Codeception/Subscriber/Module.php: -------------------------------------------------------------------------------- 1 | 'before', 19 | Events::TEST_AFTER => 'after', 20 | Events::STEP_BEFORE => 'beforeStep', 21 | Events::STEP_AFTER => 'afterStep', 22 | Events::TEST_FAIL => 'failed', 23 | Events::TEST_ERROR => 'failed', 24 | Events::SUITE_BEFORE => 'beforeSuite', 25 | Events::SUITE_AFTER => 'afterSuite' 26 | ]; 27 | 28 | protected $modules = []; 29 | 30 | public function beforeSuite(SuiteEvent $e) 31 | { 32 | $suite = $e->getSuite(); 33 | if (!$suite instanceof Suite) { 34 | return; 35 | } 36 | $this->modules = $suite->getModules(); 37 | foreach ($this->modules as $module) { 38 | $module->_beforeSuite($e->getSettings()); 39 | } 40 | } 41 | 42 | public function afterSuite() 43 | { 44 | foreach ($this->modules as $module) { 45 | $module->_afterSuite(); 46 | } 47 | } 48 | 49 | public function before(TestEvent $event) 50 | { 51 | if (!$event->getTest() instanceof TestInterface) { 52 | return; 53 | } 54 | 55 | foreach ($this->modules as $module) { 56 | $module->_before($event->getTest()); 57 | } 58 | } 59 | 60 | public function after(TestEvent $e) 61 | { 62 | if (!$e->getTest() instanceof TestInterface) { 63 | return; 64 | } 65 | foreach ($this->modules as $module) { 66 | $module->_after($e->getTest()); 67 | $module->_resetConfig(); 68 | } 69 | } 70 | 71 | public function failed(FailEvent $e) 72 | { 73 | if (!$e->getTest() instanceof TestInterface) { 74 | return; 75 | } 76 | foreach ($this->modules as $module) { 77 | $module->_failed($e->getTest(), $e->getFail()); 78 | } 79 | } 80 | 81 | public function beforeStep(StepEvent $e) 82 | { 83 | foreach ($this->modules as $module) { 84 | $module->_beforeStep($e->getStep(), $e->getTest()); 85 | } 86 | } 87 | 88 | public function afterStep(StepEvent $e) 89 | { 90 | foreach ($this->modules as $module) { 91 | $module->_afterStep($e->getStep(), $e->getTest()); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Codeception/Command/GenerateStepObject.php: -------------------------------------------------------------------------------- 1 | setDefinition([ 28 | new InputArgument('suite', InputArgument::REQUIRED, 'Suite for StepObject'), 29 | new InputArgument('step', InputArgument::REQUIRED, 'StepObject name'), 30 | new InputOption('silent', '', InputOption::VALUE_NONE, 'skip verification question'), 31 | ]); 32 | } 33 | 34 | public function getDescription() 35 | { 36 | return 'Generates empty StepObject class'; 37 | } 38 | 39 | public function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $suite = $input->getArgument('suite'); 42 | $step = $input->getArgument('step'); 43 | $config = $this->getSuiteConfig($suite); 44 | 45 | $class = $this->getShortClassName($step); 46 | 47 | $path = $this->createDirectoryFor(Configuration::supportDir() . 'Step' . DIRECTORY_SEPARATOR . ucfirst($suite), $step); 48 | 49 | $dialog = $this->getHelperSet()->get('question'); 50 | $filename = $path . $class . '.php'; 51 | 52 | $helper = $this->getHelper('question'); 53 | $question = new Question("Add action to StepObject class (ENTER to exit): "); 54 | 55 | $gen = new StepObjectGenerator($config, ucfirst($suite) . '\\' . $step); 56 | 57 | if (!$input->getOption('silent')) { 58 | do { 59 | $question = new Question('Add action to StepObject class (ENTER to exit): ', null); 60 | $action = $dialog->ask($input, $output, $question); 61 | if ($action) { 62 | $gen->createAction($action); 63 | } 64 | } while ($action); 65 | } 66 | 67 | $res = $this->createFile($filename, $gen->produce()); 68 | 69 | if (!$res) { 70 | $output->writeln("StepObject $filename already exists"); 71 | exit; 72 | } 73 | $output->writeln("StepObject was created in $filename"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Codeception/Module/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Modules are high-level extensions that are used in tests. Modules are created for each test suites (according to suite configuration) and can be accessed directly from unit tests: 4 | 5 | ```php 6 | getModule('PhpBrowser')->client; 8 | ?> 9 | ``` 10 | 11 | or used inside scenario-driven tests, where `$I` acts as an wrapper to different modules 12 | 13 | ```php 14 | click(); // => PhpBrowser 16 | $I->seeInDatabase(); // => Db 17 | ?> 18 | ``` 19 | 20 | Each module is extending `Codeception\Module` class and defined in `Codeception\Module` namespace. All Codeception modules are autoloaded by searching in this particular namespace: `PhpBrowser` => `Codeception\Module\PhpBrowser`. 21 | 22 | ## What you should know before developing a module 23 | 24 | The core principles: 25 | 26 | 1. Public methods of modules are actions of an actor inside a test. That's why they should be named in proper format: 27 | 28 | ``` 29 | doSomeStuff() => $I->doSomeStuff() => I do some stuff 30 | doSomeStuffWith($a, $b) => $I->doSomeStuffWith("vodka", "gin"); => I do some stuff with "vodka", "gin" 31 | seeIsGreat() => $I->seeIsGreat() => I see is great 32 | ``` 33 | 34 | * Each method that define environment should start with `am` or `have` 35 | * Each assertion should start with `see` prefix 36 | * Each method that returns values should start with `grab` (grabbers) or `have` (definitions) 37 | 38 | Example: 39 | 40 | ```php 41 | $I->amSeller(); 42 | $I->haveProducts(['vodka', 'gin']); 43 | $I->haveDiscount('0.1'); 44 | $I->setPrice('gin', '10$'); 45 | $I->seePrice('gin', '9.9'); 46 | $price = $I->grabPriceFor('gin'); 47 | ``` 48 | 49 | 2. Configuration parameters are set in `.suite.yml` config and stored in `config` property array of a module. All default values can be set there as well. Required parameters should be set in `requiredFields` property. 50 | 51 | ```php 52 | 'firefox']; 54 | protected $requiredFields = ['url']; 55 | ?> 56 | ``` 57 | 58 | You should not perform validation if `url` was set. Module would perform it for you, so you could access `$this->config['url']` inside a module. 59 | 60 | 3. If you use low-level clients in your module (PDO driver, framework client, selenium client) you should allow developers to access them. That's why you should define their instances as `public` properties of method. 61 | 62 | Also you *may* provide a closure method to access low-level API 63 | 64 | ```php 65 | executeInSelenium(function(\WebDriverClient $wb) { 67 | $wd->manage()->addCookie(['name' => 'verified']); 68 | }); 69 | ?> 70 | ``` 71 | 72 | 4. Modules can be added to official repo, or published standalone. In any case module should be defined in `Codeception\Module` namespace. If you develop a module and you think it might be useful to others, please ask in Github Issues, maybe we would like to include it into the official repo. -------------------------------------------------------------------------------- /src/Codeception/Lib/Driver/SqlSrv.php: -------------------------------------------------------------------------------- 1 | dsn, $matches); 10 | 11 | if (!$matched) { 12 | return false; 13 | } 14 | 15 | return $matches[1]; 16 | } 17 | 18 | public function cleanup() 19 | { 20 | $this->dbh->exec( 21 | " 22 | DECLARE constraints_cursor CURSOR FOR SELECT name, parent_object_id FROM sys.foreign_keys; 23 | OPEN constraints_cursor 24 | DECLARE @constraint sysname; 25 | DECLARE @parent int; 26 | DECLARE @table nvarchar(128); 27 | FETCH NEXT FROM constraints_cursor INTO @constraint, @parent; 28 | WHILE (@@FETCH_STATUS <> -1) 29 | BEGIN 30 | SET @table = OBJECT_NAME(@parent) 31 | EXEC ('ALTER TABLE [' + @table + '] DROP CONSTRAINT [' + @constraint + ']') 32 | FETCH NEXT FROM constraints_cursor INTO @constraint, @parent; 33 | END 34 | DEALLOCATE constraints_cursor;" 35 | ); 36 | 37 | $this->dbh->exec( 38 | " 39 | DECLARE tables_cursor CURSOR FOR SELECT name FROM sysobjects WHERE type = 'U'; 40 | OPEN tables_cursor DECLARE @tablename sysname; 41 | FETCH NEXT FROM tables_cursor INTO @tablename; 42 | WHILE (@@FETCH_STATUS <> -1) 43 | BEGIN 44 | EXEC ('DROP TABLE [' + @tablename + ']') 45 | FETCH NEXT FROM tables_cursor INTO @tablename; 46 | END 47 | DEALLOCATE tables_cursor;" 48 | ); 49 | } 50 | 51 | public function getQuotedName($name) 52 | { 53 | return '[' . str_replace('.', '].[', $name) . ']'; 54 | } 55 | 56 | /** 57 | * @param string $tableName 58 | * 59 | * @return array[string] 60 | */ 61 | public function getPrimaryKey($tableName) 62 | { 63 | if (!isset($this->primaryKeys[$tableName])) { 64 | $primaryKey = []; 65 | $query = " 66 | SELECT Col.Column_Name from 67 | INFORMATION_SCHEMA.TABLE_CONSTRAINTS Tab, 68 | INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE Col 69 | WHERE 70 | Col.Constraint_Name = Tab.Constraint_Name 71 | AND Col.Table_Name = Tab.Table_Name 72 | AND Constraint_Type = 'PRIMARY KEY' AND Col.Table_Name = ?"; 73 | $stmt = $this->executeQuery($query, [$tableName]); 74 | $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); 75 | 76 | foreach ($columns as $column) { 77 | $primaryKey []= $column['Column_Name']; 78 | } 79 | $this->primaryKeys[$tableName] = $primaryKey; 80 | } 81 | 82 | return $this->primaryKeys[$tableName]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Codeception/Command/Completion.php: -------------------------------------------------------------------------------- 1 | addHandler(new ConsoleCompletion( 41 | $suiteCommand, 42 | 'suite', 43 | ConsoleCompletion::TYPE_ARGUMENT, 44 | Configuration::suites() 45 | )); 46 | } 47 | 48 | $handler->addHandlers([ 49 | new ShellPathCompletion( 50 | ConsoleCompletion::ALL_COMMANDS, 51 | 'path', 52 | ConsoleCompletion::TYPE_ARGUMENT 53 | ), 54 | new ShellPathCompletion( 55 | ConsoleCompletion::ALL_COMMANDS, 56 | 'test', 57 | ConsoleCompletion::TYPE_ARGUMENT 58 | ), 59 | ]); 60 | } 61 | 62 | protected function execute(InputInterface $input, OutputInterface $output) 63 | { 64 | if ($input->getOption('generate-hook') && $input->getOption('use-vendor-bin')) { 65 | global $argv; 66 | $argv[0] = 'vendor/bin/' . basename($argv[0]); 67 | } 68 | 69 | parent::execute($input, $output); 70 | } 71 | 72 | protected function createDefinition() 73 | { 74 | $definition = parent::createDefinition(); 75 | $definition->addOption(new InputOption( 76 | 'use-vendor-bin', 77 | null, 78 | InputOption::VALUE_NONE, 79 | 'Use the vendor bin for autocompletion.' 80 | )); 81 | 82 | return $definition; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Codeception/Coverage/Subscriber/RemoteServer.php: -------------------------------------------------------------------------------- 1 | module and $this->settings['remote'] and $this->settings['enabled']; 20 | } 21 | 22 | public function afterSuite(SuiteEvent $e) 23 | { 24 | if (!$this->isEnabled()) { 25 | return; 26 | } 27 | 28 | $suite = strtr($e->getSuite()->getName(), ['\\' => '.']); 29 | if ($this->options['coverage-xml']) { 30 | $this->retrieveAndPrintXml($suite); 31 | } 32 | if ($this->options['coverage-html']) { 33 | $this->retrieveAndPrintHtml($suite); 34 | } 35 | if ($this->options['coverage-crap4j']) { 36 | $this->retrieveAndPrintCrap4j($suite); 37 | } 38 | if ($this->options['coverage-phpunit']) { 39 | $this->retrieveAndPrintPHPUnit($suite); 40 | } 41 | } 42 | 43 | protected function retrieveAndPrintHtml($suite) 44 | { 45 | $tempFile = tempnam(sys_get_temp_dir(), 'C3') . '.tar'; 46 | file_put_contents($tempFile, $this->c3Request('html')); 47 | 48 | $destDir = Configuration::outputDir() . $suite . '.remote.coverage'; 49 | if (is_dir($destDir)) { 50 | FileSystem::doEmptyDir($destDir); 51 | } else { 52 | mkdir($destDir, 0777, true); 53 | } 54 | 55 | $phar = new \PharData($tempFile); 56 | $phar->extractTo($destDir); 57 | 58 | unlink($tempFile); 59 | } 60 | 61 | protected function retrieveAndPrintXml($suite) 62 | { 63 | $destFile = Configuration::outputDir() . $suite . '.remote.coverage.xml'; 64 | file_put_contents($destFile, $this->c3Request('clover')); 65 | } 66 | 67 | protected function retrieveAndPrintCrap4j($suite) 68 | { 69 | $destFile = Configuration::outputDir() . $suite . '.remote.crap4j.xml'; 70 | file_put_contents($destFile, $this->c3Request('crap4j')); 71 | } 72 | 73 | protected function retrieveAndPrintPHPUnit($suite) 74 | { 75 | $tempFile = tempnam(sys_get_temp_dir(), 'C3') . '.tar'; 76 | file_put_contents($tempFile, $this->c3Request('phpunit')); 77 | 78 | $destDir = Configuration::outputDir() . $suite . '.remote.coverage-phpunit'; 79 | if (is_dir($destDir)) { 80 | FileSystem::doEmptyDir($destDir); 81 | } else { 82 | mkdir($destDir, 0777, true); 83 | } 84 | 85 | $phar = new \PharData($tempFile); 86 | $phar->extractTo($destDir); 87 | 88 | unlink($tempFile); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Codeception/Test/Loader/Unit.php: -------------------------------------------------------------------------------- 1 | isInstantiable()) { 26 | continue; 27 | } 28 | 29 | foreach ($reflected->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { 30 | $test = $this->createTestFromPhpUnitMethod($reflected, $method); 31 | if (!$test) { 32 | continue; 33 | } 34 | $this->tests[] = $test; 35 | } 36 | } 37 | } 38 | 39 | public function getTests() 40 | { 41 | return $this->tests; 42 | } 43 | 44 | protected function createTestFromPhpUnitMethod(\ReflectionClass $class, \ReflectionMethod $method) 45 | { 46 | if (method_exists(\PHPUnit\Framework\TestSuite::class, 'isTestMethod')) { 47 | //PHPUnit <8.2 48 | if (!\PHPUnit\Framework\TestSuite::isTestMethod($method)) { 49 | return; 50 | } 51 | $test = \PHPUnit\Framework\TestSuite::createTest($class, $method->name); 52 | } elseif (method_exists(\PHPUnit\Util\Test::class, 'isTestMethod')) { 53 | //PHPUnit >=8.2 54 | if (!\PHPUnit\Util\Test::isTestMethod($method)) { 55 | return; 56 | } 57 | $test = (new \PHPUnit\Framework\TestBuilder)->build($class, $method->name); 58 | } else { 59 | throw new \Exception('Unsupported version of PHPUnit, where is isTestMethod method?'); 60 | } 61 | 62 | 63 | if ($test instanceof \PHPUnit\Framework\DataProviderTestSuite) { 64 | foreach ($test->tests() as $t) { 65 | $this->enhancePhpunitTest($t); 66 | } 67 | return $test; 68 | } 69 | 70 | $this->enhancePhpunitTest($test); 71 | return $test; 72 | } 73 | 74 | protected function enhancePhpunitTest(\PHPUnit\Framework\Test $test) 75 | { 76 | $className = get_class($test); 77 | $methodName = $test->getName(false); 78 | $dependencies = \PHPUnit\Util\Test::getDependencies($className, $methodName); 79 | $test->setDependencies($dependencies); 80 | if ($test instanceof UnitFormat) { 81 | $test->getMetadata()->setParamsFromAnnotations(Annotation::forMethod($test, $methodName)->raw()); 82 | $test->getMetadata()->setFilename(Descriptor::getTestFileName($test)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Driver/Sqlite.php: -------------------------------------------------------------------------------- 1 | filename = Configuration::projectDir() . $filename; 21 | $this->dsn = 'sqlite:' . $this->filename; 22 | parent::__construct($this->dsn, $user, $password, $options); 23 | } 24 | 25 | public function cleanup() 26 | { 27 | $this->dbh = null; 28 | gc_collect_cycles(); 29 | file_put_contents($this->filename, ''); 30 | $this->dbh = self::connect($this->dsn, $this->user, $this->password); 31 | } 32 | 33 | public function load($sql) 34 | { 35 | if ($this->hasSnapshot) { 36 | $this->dbh = null; 37 | copy($this->filename . '_snapshot', $this->filename); 38 | $this->dbh = new \PDO($this->dsn, $this->user, $this->password); 39 | } else { 40 | if (file_exists($this->filename . '_snapshot')) { 41 | unlink($this->filename . '_snapshot'); 42 | } 43 | parent::load($sql); 44 | copy($this->filename, $this->filename . '_snapshot'); 45 | $this->hasSnapshot = true; 46 | } 47 | } 48 | 49 | /** 50 | * @param string $tableName 51 | * 52 | * @return array[string] 53 | */ 54 | public function getPrimaryKey($tableName) 55 | { 56 | if (!isset($this->primaryKeys[$tableName])) { 57 | if ($this->hasRowId($tableName)) { 58 | return $this->primaryKeys[$tableName] = ['_ROWID_']; 59 | } 60 | 61 | $primaryKey = []; 62 | $query = 'PRAGMA table_info(' . $this->getQuotedName($tableName) . ')'; 63 | $stmt = $this->executeQuery($query, []); 64 | $columns = $stmt->fetchAll(\PDO::FETCH_ASSOC); 65 | 66 | foreach ($columns as $column) { 67 | if ($column['pk'] !== '0') { 68 | $primaryKey []= $column['name']; 69 | } 70 | } 71 | 72 | $this->primaryKeys[$tableName] = $primaryKey; 73 | } 74 | 75 | return $this->primaryKeys[$tableName]; 76 | } 77 | 78 | /** 79 | * @param $tableName 80 | * @return bool 81 | */ 82 | private function hasRowId($tableName) 83 | { 84 | $params = ['type' => 'table', 'name' => $tableName]; 85 | $select = $this->select('sql', 'sqlite_master', $params); 86 | $result = $this->executeQuery($select, $params); 87 | $sql = $result->fetchColumn(0); 88 | return strpos($sql, ') WITHOUT ROWID') === false; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Codeception/Lib/Driver/Iron.php: -------------------------------------------------------------------------------- 1 | queue = new \IronMQ([ 21 | "token" => $config['token'], 22 | "project_id" => $config['project'], 23 | "host" => $config['host'] 24 | ]); 25 | if (!$this->queue) { 26 | \PHPUnit\Framework\Assert::fail('connection failed or timed-out.'); 27 | } 28 | } 29 | 30 | /** 31 | * Post/Put a message on to the queue server 32 | * 33 | * @param string $message Message Body to be send 34 | * @param string $queue Queue Name 35 | */ 36 | public function addMessageToQueue($message, $queue) 37 | { 38 | $this->queue->postMessage($queue, $message); 39 | } 40 | 41 | /** 42 | * Return a list of queues/tubes on the queueing server 43 | * 44 | * @return array Array of Queues 45 | */ 46 | public function getQueues() 47 | { 48 | // Format the output to suit 49 | $queues = []; 50 | foreach ($this->queue->getQueues() as $queue) { 51 | $queues[] = $queue->name; 52 | } 53 | return $queues; 54 | } 55 | 56 | /** 57 | * Count the current number of messages on the queue. 58 | * 59 | * @param $queue Queue Name 60 | * 61 | * @return int Count 62 | */ 63 | public function getMessagesCurrentCountOnQueue($queue) 64 | { 65 | try { 66 | return $this->queue->getQueue($queue)->size; 67 | } catch (\Http_Exception $ex) { 68 | \PHPUnit\Framework\Assert::fail("queue [$queue] not found"); 69 | } 70 | } 71 | 72 | /** 73 | * Count the total number of messages on the queue. 74 | * 75 | * @param $queue Queue Name 76 | * 77 | * @return int Count 78 | */ 79 | public function getMessagesTotalCountOnQueue($queue) 80 | { 81 | try { 82 | return $this->queue->getQueue($queue)->total_messages; 83 | } catch (\Http_Exception $e) { 84 | \PHPUnit\Framework\Assert::fail("queue [$queue] not found"); 85 | } 86 | } 87 | 88 | public function clearQueue($queue) 89 | { 90 | try { 91 | $this->queue->clearQueue($queue); 92 | } catch (\Http_Exception $ex) { 93 | \PHPUnit\Framework\Assert::fail("queue [$queue] not found"); 94 | } 95 | } 96 | 97 | public function getRequiredConfig() 98 | { 99 | return ['host', 'token', 'project']; 100 | } 101 | 102 | public function getDefaultConfig() 103 | { 104 | return []; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Codeception/Example.php: -------------------------------------------------------------------------------- 1 | data = $data; 13 | } 14 | 15 | /** 16 | * Whether a offset exists 17 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 18 | * @param mixed $offset

19 | * An offset to check for. 20 | *

21 | * @return boolean true on success or false on failure. 22 | *

23 | *

24 | * The return value will be casted to boolean if non-boolean was returned. 25 | * @since 5.0.0 26 | */ 27 | public function offsetExists($offset) 28 | { 29 | return array_key_exists($offset, $this->data); 30 | } 31 | 32 | /** 33 | * Offset to retrieve 34 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 35 | * @param mixed $offset

36 | * The offset to retrieve. 37 | *

38 | * @return mixed Can return all value types. 39 | * @since 5.0.0 40 | */ 41 | public function offsetGet($offset) 42 | { 43 | if (!$this->offsetExists($offset)) { 44 | throw new \PHPUnit\Framework\AssertionFailedError("Example $offset doesn't exist"); 45 | }; 46 | return $this->data[$offset]; 47 | } 48 | 49 | /** 50 | * Offset to set 51 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 52 | * @param mixed $offset

53 | * The offset to assign the value to. 54 | *

55 | * @param mixed $value

56 | * The value to set. 57 | *

58 | * @return void 59 | * @since 5.0.0 60 | */ 61 | public function offsetSet($offset, $value) 62 | { 63 | $this->data[$offset] = $value; 64 | } 65 | 66 | /** 67 | * Offset to unset 68 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 69 | * @param mixed $offset

70 | * The offset to unset. 71 | *

72 | * @return void 73 | * @since 5.0.0 74 | */ 75 | public function offsetUnset($offset) 76 | { 77 | unset($this->data[$offset]); 78 | } 79 | 80 | /** 81 | * Count elements of an object 82 | * @link http://php.net/manual/en/countable.count.php 83 | * @return int The custom count as an integer. 84 | *

85 | *

86 | * The return value is cast to an integer. 87 | * @since 5.1.0 88 | */ 89 | public function count() 90 | { 91 | return count($this->data); 92 | } 93 | 94 | /** 95 | * Retrieve an external iterator 96 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 97 | * @return Traversable An instance of an object implementing Iterator or 98 | * Traversable 99 | * @since 5.0.0 100 | */ 101 | public function getIterator() 102 | { 103 | return new \ArrayIterator($this->data); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"codeception/base", 3 | "description":"BDD-style testing framework", 4 | "keywords":["BDD", "acceptance testing", "functional testing", "unit testing", "tdd"], 5 | "homepage":"http://codeception.com/", 6 | "type":"library", 7 | "license":"MIT", 8 | "authors":[ 9 | { 10 | "name":"Michael Bodnarchuk", 11 | "email":"davert@mail.ua", 12 | "homepage":"http://codegyre.com" 13 | } 14 | ], 15 | "minimum-stability": "RC", 16 | 17 | "require": { 18 | "php": ">=5.6.0 <8.0", 19 | "ext-curl": "*", 20 | "ext-json": "*", 21 | "ext-mbstring": "*", 22 | 23 | "guzzlehttp/psr7": "~1.4", 24 | 25 | "symfony/finder": ">=2.7 <5.0", 26 | "symfony/console": ">=2.7 <5.0", 27 | "symfony/event-dispatcher": ">=2.7 <5.0", 28 | "symfony/yaml": ">=2.7 <5.0", 29 | "symfony/browser-kit": ">=2.7 <5.0", 30 | "symfony/css-selector": ">=2.7 <5.0", 31 | "symfony/dom-crawler": ">=2.7 <5.0", 32 | "behat/gherkin": "^4.4.0", 33 | "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3", 34 | "codeception/stub": "^2.0 | ^3.0" 35 | }, 36 | "require-dev": { 37 | "monolog/monolog": "~1.8", 38 | "php-amqplib/php-amqplib": "~2.4", 39 | "codeception/specify": "~0.3", 40 | "pda/pheanstalk": "~3.0", 41 | "flow/jsonpath": "~0.2", 42 | "predis/predis": "^1.0", 43 | "squizlabs/php_codesniffer": "~2.0", 44 | "vlucas/phpdotenv": "^3.0", 45 | "symfony/process": ">=2.7 <5.0", 46 | "doctrine/orm": "^2", 47 | "doctrine/annotations": "^1", 48 | "doctrine/data-fixtures": "^1", 49 | "ramsey/uuid-doctrine": "^1.5" 50 | }, 51 | "suggest": { 52 | "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", 53 | "codeception/specify": "BDD-style code blocks", 54 | "codeception/verify": "BDD-style assertions", 55 | "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", 56 | "flow/jsonpath": "For using JSONPath in REST module", 57 | "phpseclib/phpseclib": "for SFTP option in FTP Module", 58 | "league/factory-muffin": "For DataFactory module", 59 | "league/factory-muffin-faker": "For Faker support in DataFactory module", 60 | "symfony/phpunit-bridge": "For phpunit-bridge support", 61 | "stecman/symfony-console-completion": "For BASH autocompletion" 62 | }, 63 | 64 | "autoload":{ 65 | "psr-4":{ 66 | "Codeception\\": "src/Codeception", 67 | "Codeception\\Extension\\": "ext" 68 | } 69 | }, 70 | "autoload-dev": { 71 | "classmap": [ 72 | "tests/cli/_steps", 73 | "tests/web/_steps", 74 | "tests/data/DummyClass.php", 75 | "tests/data/claypit/tests/_data" 76 | ] 77 | }, 78 | "extra": { 79 | "branch-alias": { 80 | } 81 | }, 82 | "bin":["codecept"], 83 | "config": { 84 | "platform": {} 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /shim.php: -------------------------------------------------------------------------------- 1 |