├── 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 |