diff --git a/.gitattributes b/.gitattributes
index a69ee2c6db..c87466518e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -32,4 +32,12 @@
*.ttf binary
*.ttf binary
*.woff binary
-*.phar binary
\ No newline at end of file
+*.phar binary
+
+# Exclude files that don't need to be present in packages (so they're not downloaded by Composer)
+/docs export-ignore
+/tests export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/*.md export-ignore
+/*.yml export-ignore
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..755a0f214a
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: codeception
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.gitignore b/.gitignore
index 96c422ef3f..ea324caf8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,8 @@ tests/data/exception_in_before/tests/_support/_generated/UnitTesterActions.php
tests/data/exception_in_before/tests/_output/
tests/data/bundled_suites/_support/_generated/UnitTesterActions.php
tests/data/bundled_suites/_output/
+tests/data/snapshots/tests/_support/_generated
+tests/data/snapshots/tests/_data/Snapshot.UserSnapshot.json
.DS_Store
robo.phar
.env
diff --git a/.travis.yml b/.travis.yml
index bdd48e50fb..ceb54d6906 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,26 +1,60 @@
-sudo: false
-
language: php
-
+php:
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - 7.3
+env:
+ global:
+ - COVERAGE=
+ - SUITES=
+ - PECL=
+ - TEST_PATH='framework-tests'
+ - SYMFONY_DEPRECATIONS_HELPER=weak
+ matrix:
+ - FRAMEWORK=Codeception SUITES=cli,unit TEST_PATH=. PECL="mongodb" COVERAGE=1
+ - FRAMEWORK=Yii2 TEST_REPO="https://github.com/Codeception/yii2-tests"
+ - FRAMEWORK=Symfony VERSION=2.8 TEST_REPO='-b 2.1 https://github.com/Codeception/symfony-demo.git' SUITES=functional TEST_PATH=framework-tests/src/AppBundle
+ - FRAMEWORK=Symfony VERSION=3.4 TEST_REPO='--recurse-submodules https://github.com/Naktibalda/codeception-symfony-tests'
+ - FRAMEWORK=Symfony VERSION=4 TEST_REPO='https://github.com/Codeception/symfony-demo.git' SUITES=functional,unit
+ - FRAMEWORK=Lumen TEST_REPO='-b codeception-2.2 https://github.com/codeception/codeception-lumen-sample.git'
+ - FRAMEWORK=Laravel TEST_REPO='-b codeception-3.0 https://github.com/codeception/codeception-laravel5-sample.git'
+ - FRAMEWORK=Phalcon TEST_REPO=https://github.com/Codeception/phalcon-demo.git
+ - FRAMEWORK=Zend2 TEST_REPO='-b 2.2 --recurse-submodules https://github.com/Naktibalda/codeception-zf2-tests' SUITES=functional
+ - FRAMEWORK=ZendExpressive TEST_REPO='-b 2.2 --recurse-submodules https://github.com/Naktibalda/codeception-zend-expressive-tests' SUITES=functional
matrix:
+ fast_finish: true
include:
- - php: 5.4 # lowest versions of all dependencies
- env: SYMFONY=2.7.19 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 2.7.*
- - php: 5.5
- env: SYMFONY=2.7.19 SYMFONY_DEPRECATIONS_HELPER=weak #latest version of 2.7.*
- - php: 5.6
- env: SYMFONY=2.8.12 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 2.8.*
- - php: 7.0
- env: SYMFONY=3.2.9 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 3.2.*
- - php: 7.1
- env: SYMFONY=3.3.2 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 3.3.*
- - php: 7.1
- env: SYMFONY=4.0.1 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 4.0.*
- - php: 7.2
- env: SYMFONY=4.0.1 SYMFONY_DEPRECATIONS_HELPER=weak # latest version of 4.0.*
+ # Run our coverage suite on 7.2 (so it uses pcov)
+ - php: 7.2
+ env: FRAMEWORK=Codeception SUITES=cli,unit,coverage TEST_PATH=. PECL="mongodb" COVERAGE=1
+ exclude:
+ - php: 5.6
+ env: FRAMEWORK=Yii2 TEST_REPO="https://github.com/Codeception/yii2-tests"
+ - php: 7.2
+ env: FRAMEWORK=Codeception SUITES=cli,unit TEST_PATH=. PECL="mongodb" COVERAGE=1
+ - php: 7.0
+ env: FRAMEWORK=Symfony VERSION=2.8 TEST_REPO='-b 2.1 https://github.com/Codeception/symfony-demo.git' SUITES=functional TEST_PATH=framework-tests/src/AppBundle
+ - php: 7.1
+ env: FRAMEWORK=Symfony VERSION=2.8 TEST_REPO='-b 2.1 https://github.com/Codeception/symfony-demo.git' SUITES=functional TEST_PATH=framework-tests/src/AppBundle
+ - php: 7.2
+ env: FRAMEWORK=Symfony VERSION=2.8 TEST_REPO='-b 2.1 https://github.com/Codeception/symfony-demo.git' SUITES=functional TEST_PATH=framework-tests/src/AppBundle
+ - php: 7.3
+ env: FRAMEWORK=Symfony VERSION=2.8 TEST_REPO='-b 2.1 https://github.com/Codeception/symfony-demo.git' SUITES=functional TEST_PATH=framework-tests/src/AppBundle
+ - php: 5.6
+ env: FRAMEWORK=Symfony VERSION=3.4 TEST_REPO='--recurse-submodules https://github.com/Naktibalda/codeception-symfony-tests'
+ - php: 7.0
+ env: FRAMEWORK=Symfony VERSION=3.4 TEST_REPO='--recurse-submodules https://github.com/Naktibalda/codeception-symfony-tests'
+ - php: 7.3
+ env: FRAMEWORK=Symfony VERSION=3.4 TEST_REPO='--recurse-submodules https://github.com/Naktibalda/codeception-symfony-tests'
+ - php: 5.6
+ env: FRAMEWORK=Symfony VERSION=4 TEST_REPO='https://github.com/Codeception/symfony-demo.git' SUITES=functional,unit
+ - php: 7.0
+ env: FRAMEWORK=Symfony VERSION=4 TEST_REPO='https://github.com/Codeception/symfony-demo.git' SUITES=functional,unit
addons:
- postgresql: "9.2"
+ postgresql: "9.6"
branches:
except:
@@ -36,103 +70,69 @@ services:
- rabbitmq
- postgresql
- redis
+ - mysql
+before_install:
+ # Install coverage driver; for php < 7.2 we use xdebug, for >= 7.2 we use pcov, pcov supports 7.1, but phpunit only supports pcov from 8.0 which requires php 7.2s
+ - '[[ -z "$COVERAGE" ]] || [[ "$TRAVIS_PHP_VERSION" < 7.2 ]] || (phpenv config-rm xdebug.ini; pecl install -f pcov)'
+ - php -i
+ - '$(php PruneTest.php)'
+ - export INI=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
+ - echo memory_limit = -1 >> $INI
+ - composer global require hirak/prestissimo
install:
- - export SF_VERSION=$(echo $SYMFONY | head -c 1)
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || pecl install -f mongodb'
- - yes '' | pecl install imagick
- #- echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- - composer self-update && composer --version
- - if [ -n "$CI_USER_TOKEN" ]; then composer config github-oauth.github.com ${CI_USER_TOKEN}; echo "Configured Github token"; fi;
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || composer require mongodb/mongodb --no-update'
- - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || composer global require "fxp/composer-asset-plugin:~1.3.1"'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/finder=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/yaml=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/console=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/event-dispatcher=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/css-selector=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/dom-crawler=~$SYMFONY --no-update --ignore-platform-reqs'
- - '[[ -z "$SYMFONY" ]] || composer require symfony/browser-kit=~$SYMFONY --no-update --ignore-platform-reqs'
- - composer_parameters="-n --prefer-dist" # this variable will be used in all composer install commands
- - '[[ "$dependencies" != "lowest" ]] || composer_parameters="$composer_parameters --prefer-lowest"'
- - composer update $composer_parameters
- - composer_parameters="$composer_parameters --no-dev" # Codeception needs dev dependencies, but frameworks don't
- # Yii2
- - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || composer create-project "yiisoft/yii2-app-basic" frameworks-yii-basic --no-dev'
+ - '[[ -z "$CI_USER_TOKEN" ]] || composer config github-oauth.github.com ${CI_USER_TOKEN};'
+ # Add extensions
+ - '[[ -z "$PECL" ]] || (yes "" | pecl -q install -f $PECL)'
+ # Clone test repository
+ - '[[ "$FRAMEWORK" == "Codeception" ]] || git clone -q --depth=1 $TEST_REPO framework-tests'
+ - '[[ "$FRAMEWORK" == "Codeception" ]] || git --git-dir framework-tests/.git log -n 1'
+ - '[[ "$FRAMEWORK" != "Codeception" ]] || composer require mongodb/mongodb --no-update'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/finder=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/yaml=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/console=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/event-dispatcher=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/css-selector=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/dom-crawler=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/browser-kit=~$VERSION --no-update --ignore-platform-reqs'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || composer require symfony/browser-kit=~$VERSION --no-update --ignore-platform-reqs'
# Phalcon
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || git clone -q --depth=1 https://github.com/phalcon/cphalcon.git'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || (cd cphalcon/build; bash ./install --phpize $(phpenv which phpize) --php-config $(phpenv which php-config) &>/dev/null && phpenv config-add ../tests/_ci/phalcon.ini &> /dev/null)'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || git clone -q --depth=1 https://github.com/Codeception/phalcon-demo.git frameworks-phalcon'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || composer update -d frameworks-phalcon $composer_parameters'
- # Laravel 5
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || git clone -q --depth=1 -b codeception-2.3 https://github.com/codeception/codeception-laravel5-sample.git frameworks-l5'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || composer update -d frameworks-l5 $composer_parameters'
- # Lumen
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || git clone -q --depth=1 -b codeception-2.2 https://github.com/codeception/codeception-lumen-sample.git frameworks-lumen'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || composer update -d frameworks-lumen $composer_parameters'
+ - '[[ "$FRAMEWORK" != "Phalcon" ]] || git clone -q --depth=1 https://github.com/phalcon/cphalcon.git -b v3.4.2'
+ - '[[ "$FRAMEWORK" != "Phalcon" ]] || (cd cphalcon/build; bash ./install --phpize $(phpenv which phpize) --php-config $(phpenv which php-config) &>/dev/null && phpenv config-add ../tests/_ci/phalcon.ini &> /dev/null)'
# Symfony
- - '[[ "$SF_VERSION" != "2" ]] || (git clone -q --depth=1 -b 2.1 https://github.com/Codeception/symfony-demo.git frameworks-symfony && echo "Cloned Symfony 2 site")'
- - '[[ "$SF_VERSION" != "3" ]] || (git clone -q --depth=1 -b master --recursive https://github.com/Naktibalda/codeception-symfony-tests frameworks-symfony && echo "Cloned Symfony 3 site")'
- - '[[ "$SF_VERSION" != "4" ]] || (git clone -q --depth=1 -b master https://github.com/Codeception/symfony-demo.git frameworks-symfony && echo "Cloned Symfony 4 site")'
- - '[[ "$SF_VERSION" == "4" ]] || composer require -d frameworks-symfony symfony/symfony=~$SYMFONY --no-update'
- - "mysql -e 'create database symfony_test;'"
- - 'composer update -d frameworks-symfony $composer_parameters'
- # ZF1
- - git clone -q -b 2.2 --recursive https://github.com/Naktibalda/codeception-zf1-tests frameworks-zf1
- - composer update -d frameworks-zf1 $composer_parameters
- # ZF2
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || (git clone -q -b 2.2 --recursive https://github.com/Naktibalda/codeception-zf2-tests frameworks-zf2)'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || (composer update -d frameworks-zf2 $composer_parameters)'
- # Zend Expressive
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || git clone -q -b 2.2 --recursive https://github.com/Naktibalda/codeception-zend-expressive-tests frameworks-zend-expressive'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || composer update -d frameworks-zend-expressive $composer_parameters'
-
+ #- '[[ "$FRAMEWORK$VERSION" != "Symfony3" ]] || composer require -d framework-tests symfony/symfony=~$VERSION --no-update'
+ - '[[ -z "$FRAMEWORK" ]] || composer install'
+ - '[[ "$FRAMEWORK" == "Codeception" ]] || [[ -z "$FRAMEWORK" ]] || composer update -d framework-tests --no-dev --prefer-dist'
before_script:
- - '[[ "$TRAVIS_PHP_VERSION" == 7.* ]] || echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'
+ # It seems pecl does this.
+ # - '[[ "$TRAVIS_PHP_VERSION" == 7.* ]] || echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'
# preparing databases
- - "mysql -e 'create database codeception_test;'"
- - psql -c 'create database codeception_test;' -U postgres
+ - '[[ "$FRAMEWORK" != "Codeception" ]] || mysql -e "create database codeception_test;"'
+ - '[[ "$FRAMEWORK" != "Codeception" ]] || psql -c "create database codeception_test;" -U postgres'
+ - '[[ "$FRAMEWORK" != "Symfony" ]] || mysql -e "create database symfony_test;"'
# starting demo servers
- - 'php -S 127.0.0.1:8000 -t tests/data/app >/dev/null 2>&1 &'
- - 'php -S 127.0.0.1:8010 -t tests/data >/dev/null 2>&1 &'
+ - '[[ "$FRAMEWORK" != "Codeception" ]] || php -S 127.0.0.1:8000 -t tests/data/app -d pcov.directory=$(pwd)/tests/data/app >/dev/null 2>&1 &'
+ - '[[ "$FRAMEWORK" != "Codeception" ]] || php -S 127.0.0.1:8010 -t tests/data -d pcov.directory=$(pwd)/tests/data >/dev/null 2>&1 &'
# Phalcon
- - mysql -e 'CREATE DATABASE phalcon_demo CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || cat frameworks-phalcon/schemas/phalcon_demo.sql | mysql phalcon_demo'
+ - '[[ "$FRAMEWORK" != "Phalcon" ]] || mysql -e "CREATE DATABASE phalcon_demo CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"'
+ - '[[ "$FRAMEWORK" != "Phalcon" ]] || cat framework-tests/schemas/phalcon_demo.sql | mysql phalcon_demo'
# Laravel 5
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || touch frameworks-l5/storage/testing.sqlite'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php frameworks-l5/artisan migrate --env=testing --database=sqlite_testing --force'
+ - '[[ "$FRAMEWORK" != "Laravel" ]] || touch framework-tests/storage/testing.sqlite'
+ - '[[ "$FRAMEWORK" != "Laravel" ]] || php framework-tests/artisan migrate --env=testing --database=sqlite_testing --force'
+ - '[[ "$FRAMEWORK" != "Laravel" ]] || php framework-tests/artisan config:clear'
# Lumen
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || cp frameworks-lumen/.env.testing frameworks-lumen/.env'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || touch frameworks-lumen/storage/testing.sqlite'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php frameworks-lumen/artisan migrate --database=testing --force'
+ - '[[ "$FRAMEWORK" != "Lumen" ]] || cp framework-tests/.env.testing framework-tests/.env'
+ - '[[ "$FRAMEWORK" != "Lumen" ]] || touch framework-tests/storage/testing.sqlite'
+ - '[[ "$FRAMEWORK" != "Lumen" ]] || php framework-tests/artisan migrate --database=testing --force'
# Symfony
- - '[[ "$SF_VERSION" != "2" ]] || php frameworks-symfony/app/console doctrine:schema:create -n --env test'
- - '[[ "$SF_VERSION" != "2" ]] || php frameworks-symfony/app/console doctrine:fixtures:load -n --env test'
- - '[[ "$SF_VERSION" != "3" ]] || php frameworks-symfony/bin/console doctrine:schema:update --force -n'
+ - '[[ "$FRAMEWORK$VERSION" != "Symfony2.8" ]] || php framework-tests/app/console doctrine:schema:create -n --env test'
+ - '[[ "$FRAMEWORK$VERSION" != "Symfony2.8" ]] || php framework-tests/app/console doctrine:fixtures:load -n --env test'
+ - '[[ "$FRAMEWORK$VERSION" != "Symfony3.4" ]] || php framework-tests/bin/console doctrine:schema:update --force -n'
# ZF2
- - "mysql -e 'create database zf2_test;'"
- - php frameworks-zf2/vendor/bin/doctrine-module orm:schema-tool:create
+ - '[[ "$FRAMEWORK" != "Zend2" ]] || mysql -e "create database zf2_test;"'
+ - '[[ "$FRAMEWORK" != "Zend2" ]] || php framework-tests/vendor/bin/doctrine-module orm:schema-tool:create'
# Build
- - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || php codecept build -c frameworks-yii-basic'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || php codecept build -c frameworks-phalcon'
- - '[[ "$SF_VERSION" != "2" ]] || php codecept build -c frameworks-symfony/src/AppBundle'
- - '[[ "$SF_VERSION" == "2" ]] || php codecept build -c frameworks-symfony'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept build -c frameworks-l5'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept build -c frameworks-lumen'
- - php codecept build -c frameworks-zf1
- - php codecept build -c frameworks-zf2
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept build -c frameworks-zend-expressive'
-
+ - '[[ -z "$FRAMEWORK" ]] || php codecept build -c $TEST_PATH'
script:
- - php codecept run cli,unit # self tests
- - '[[ "$TRAVIS_PHP_VERSION" == "7.0" ]] || php codecept run coverage' # run coverage tests on php only
- - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || php codecept run functional -c frameworks-yii-basic' # Yii2 tests
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept run -c frameworks-l5 --skip=seeder' # Laravel5 Tests
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept run -c frameworks-lumen' # Lumen Tests
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || php codecept run functional -c frameworks-phalcon' # Phalcon Tests
- - '[[ "$SF_VERSION" != "2" ]] || php codecept run functional -c frameworks-symfony/src/AppBundle' # Symfony 2 Tests
- - '[[ "$SF_VERSION" != "3" ]] || php codecept run -c frameworks-symfony' # Symfony 3 Tests
- - '[[ "$SF_VERSION" != "4" ]] || php codecept run -c frameworks-symfony functional,unit' # Symfony 4 Tests
- - php codecept run functional -c frameworks-zf1 # ZF1 Tests
- - '[[ "$TRAVIS_PHP_VERSION" < "5.6" ]] || php codecept run -c frameworks-zf2 functional'
- - '[[ "$TRAVIS_PHP_VERSION" == "5.4" ]] || [[ "$TRAVIS_PHP_VERSION" == "5.5" ]] || php codecept run functional -c frameworks-zend-expressive' # Zend Expressive Tests
+ # Run tests if $FRAMEWORK is not empty
+ - '[[ -z "$FRAMEWORK" ]] || php codecept run $SUITES -c $TEST_PATH'
diff --git a/CHANGELOG-2.4.md b/CHANGELOG-2.4.md
new file mode 100644
index 0000000000..8683fbd02a
--- /dev/null
+++ b/CHANGELOG-2.4.md
@@ -0,0 +1,101 @@
+#### 2.4.5
+
+* Fixed PHPUnit 7.2 compatibility.
+* Introduced **RunBefore** extension to execute scripts before running tests. See #5049 by @aashmelev.
+* [Db] Added two options for MySQL by @bangertz
+ * `ssl_cipher` - list of one or more permissible ciphers to use for SSL encryption
+ * `ssl_verify_server_cert` - disables certificate CN verification
+* [Db] Always disconnect before connect when `reconnect` is set. By @ashnazg
+* [Db] More explicit PDO closing upon destruction and close opened transactions by @ashnazg.
+* [Recorder Extension] Improved error logging by @OneEyedSpaceFish. See #5101
+* [Lumen] Fixed file uploads via REST module. By @retnek.
+* Fixed: function getMetadata() may not exist, results in fatal error. See #4913 by @marcovtwout
+
+#### 2.4.4
+
+* Recently added `extends` property in the `codeception.yml` and `*.suite.yml` files now support absolute paths; by @silverfire
+* Fixed absolute paths handling on Windows in ParamLoader; by @silverfire
+* [Yii2] Refactored database connection handling by @SamMousa. Database connections should now always be closed after tests no matter how you have opened them or who is holding references to them. See #5045
+* [Symfony] Email handling improved by @mbohal. Fixes #5058.
+ * Added optional argument `$expectedCount` to `seeEmailIsSent`
+ * Added `dontSeeEmailIsSent`
+* [Recorder Extension] Added `ignore_steps` option to disable recording of specific steps. By @sspat.
+* [WebDriver] Fixed "No Session Timeout" fatal error by @davertmik.
+* [WebDriver] Added ability to locate clickable element by its title. See #5065 by @gimler
+* [Db] Add `waitlock` config option for the database session to wait for lock in Oracle. By @ashnazg. See #5069
+* [REST] Fixed `seeXmlResponseEquals` by @Voziv
+
+#### 2.4.3
+
+* [Create your own test formats](https://codeception.com/docs/07-AdvancedUsage#Formats) (e.g., Cept, Cest, ...); by @mlambley
+* [Symfony] Fixed a bug in order to use multiple Kernels; by @alefcastelo
+* [Asserts] Added new methods `assertNotTrue` and `assertNotFalse` methods; by @johannesschobel
+* [REST][PhpBrowser][Frameworks] Added new methods to check for `Http Status Ranges` with nice "wrappers" (e.g., `seeHttpStatusCodeIsSuccessful()` checks the code between 200 and 299); by @johannesschobel
+* Improved the docs; by community
+
+#### 2.4.2
+
+* Added support for `extends` in the `codeception.yml` and `*.suite.yml` files; by @johannesschobel.
+ Allows to inherit current config from a provided file. See example for `functional.suite.yml`:
+
+```yml
+actor: FunctionalTester
+extends: shared.functional.suite.yml
+modules:
+ enabled:
+ - \App\Modules\X\Tests\Helper\Functional
+```
+
+* [Yii2] Restore null check for client in Yii2 by @wkritzinger. See #4940
+* [Yii2] Resetting Yii application in `_after`. By @SamMousa. See #4928
+* [Yii2] **Breaking** `$settings['configFile']` now supports absolute paths. In you have defined relative path to config in absolute manner
+* [WebDriverIO] Added `deleteSessionSnapshot` by @vi4o
+* [Symfony] Added support for custom kernel names with `kernel_class` config option. By @omnilight.
+* [Asserts] Better exception message for `expectException` by @Slamdunk
+* [REST] Decode all non-arrays to array. See #4946 by @Amunak, fixes #4944.
+* [ZF2] Fixed compatibility with ZF2 ServiceManager by @omnilight.
+* [Laravel5] Fixed memory leak when using Laravel factories inside Codeception. See #4971 by @AdrianSkierniewski
+* [Db] Added support for `null` values in MSSQL driver by @philek
+* Handle absolute paths in ParamsLoader by @SilverFire
+* Fix error on single file test. See #4986 by @mikbox74
+* Upgraded to Codeception/Stub 2.0 by @Naktibalda, fixed compatibility.
+
+
+#### 2.4.1
+
+* Fixed "Uncaught Error: Call to undefined method Codeception\Test\Descriptor::getTestDataSetIndex()" error when filtering tests.
+* Better support of PHPUnit warning status by @edno:
+ * support PHPUnit addWarning()
+ * display 'W' instead of success for warning test cases
+* Fixed Running test with invalid dataprovider by @okneloper. Fixed #4888 by @edno
+* [Yii2] **Request flow and database transactions refactored** (by @sammousa):
+ * **Breaking** Application is no longer available in helpers via `$this->getModule('Yii2'')->app`, now you must use `\Yii::$app` everywhere
+ * Multiple databases are now supported
+ * More reliable application state before and during test execution
+ * Fixtures method is now configurable
+ * Subset of misconfigurations are now detected and informative messages created
+* Fixed using `$settings['path']` in `Codeception\Configuration::suiteSettings()` on Windows by @olegpro
+(starting with `/`), you must change it. @silverfire
+* [Laravel5] Added Laravel 5.4+ (5.1+ backward compatible) support for `callArtisan` method in Laravel5 module. See #4860 by @mohamed-aiman
+* Fixed #4854: unnecessary escaping in operation arguments logging by @nicholascus
+* Fixed humanizing steps for utf8 strings by @nicholascus. See #4850
+* Fixed parsing relative urls in `parse_url`. See #4853 by @quantum-x
+
+#### 2.4.0
+
+* **PHPUnit 7.x compatibility**
+* **Dropped PHP 5.4 and PHP 5.5** support (PHP 5.5 still may work)
+* Internal API refactored:
+ * Modern PHP class names used internally
+ * Moved PHPUnit related classes to [codeception/phpunit-wrapper](https://github.com/Codeception/phpunit-wrapper) package.
+ * Removed `shims` for underscore PHPUnit classes > namespaced PHP classes
+* Cest hooks behavior changed (by @fffilimonov):
+ * `_failed` called when test fails
+ * `_passed` called when tests is successful
+ * `_after` is called for failing and successful tests
+
+**Upgrade Notice**: If you face issues with underscore PHPUnit class names (like PHPUnit_Framework_Assert) you have two options:
+
+* Lock version for PHPUnit in composer.json: "phpunit/phpunit":"^5.0.0"
+* Update your codebase and replace underscore PHPUnit class names to namespaced (PHPUnit 6+ API)
+
diff --git a/CHANGELOG-2.5.md b/CHANGELOG-2.5.md
new file mode 100644
index 0000000000..a03d8bd000
--- /dev/null
+++ b/CHANGELOG-2.5.md
@@ -0,0 +1,110 @@
+#### 2.5.6
+
+* [WebDriver] Fixed `loadSessionSnapshot` with php-webdriver 1.1.3 by @Naktibalda.
+* [WebDriver] Avoid removing required fields in cookies. #5470 by @JorisVanEijden
+* [PhpBrowser][Frameworks] Support for `formaction` attribute in `button` to submit forms. By @Dezzpil
+* [FTP] Updated to phpseclib v2 constants
+* Fixes: Gherkin Scenarios not loading when specified in group file by @mozillalives. See #5457
+
+
+#### 2.5.5
+
+* [Laravel] Fix missing declaration shouldReport in Laravel 5.8 by @edno
+* [Lumen] add support for Laravel\Lumen\Application::boot by @lendormi
+* [WebDriver] Fixed SetCookie for chromedriver 2.46+ by @JorisVanEijden
+* [ZendExpressive] Fixed recreateApplicationBetweenRequests option, by @artmnv
+* [Gherkin] Add possibility to dynamically load contexts (#5409) by @hansdubois
+* [Build command] Fixed message printing number of methods in actor class
+* Documented usage of IS NULL in Doctrine module by @ThomasLandauer
+
+#### 2.5.4
+
+* Restored compatibility with vlucas/phpdotenv v2
+* [Doctrine] Fixed cleanup issue #5326
+
+#### 2.5.3
+
+* [Db] cleanup database if populator is used
+* [FTP] Compatibility with phpseclib v2 by @kardagan
+* [JsonType] Fixed issue #5230 Dropped filters after a string:regex by @ellisgl
+* [Symfony] Fixed persistent service functionality for Symfony 3 by @Naktibalda
+* [ZendExpressive] Set Cookie header in request by @Naktibalda
+* Updated vlucas/phpdotenv package to ^3.0 version by @KartaviK
+* Documentation improvements by @chrisaligent @richleland @SanzhiyevMergen @sdlins
+
+
+#### 2.5.2
+
+* [ZendExppressive] Support for Zend Expressive v3 by @Naktibalda
+* [ZendExppressive] Added options to reload application between tests and between requests by @Naktibalda
+* [Symfony] Fix "already initialized service", "reboot kernel issue" #5262 by @gdmfx
+* {Yii2] Prevent NPE #5259 by @SilverFire
+* [Db] isPopulated method was hidden by renaming to _isPopulated by @Naktibalda
+* [Db] don't clear database for empty dump by @Slamdunk
+* [AMQP] added methods `seeQueueIsEmpty`, `dontSeeQueueIsEmpty`, `seeNumberOfMessagesInQueue`, `scheduleQueueCleanup` method by @kardagan
+* [REST][PhpBrowser][Frameworks] Save page source as .fail.json or .fail.xml depending on content type, by @freiondrej
+* [Doctrine2] Cleanup property works after on reconfigure #5250 by @joelmedeiros
+* [JsonType] Allow to use : in regex filter (#5273) by @ellisgl
+* [WebDriver] Print curl error to debug output if WebDriver failed to connect #5315 by @Naktibalda
+* [Logger] Ignores empty context and extra fields, by @siad007
+* [Recorder] Improved steps ignoring in Recorder extension with meta steps and annotations support #5210 by @sspat.
+* `@dataProvider` works with yield/generators #5271 by @burned42
+* Fixed issue ArrayContainsComparator do not Intersect correctly Empty expected nested array #5303 by @malinink
+* Fixed issue of steps with mocked objects #5163 by @dh9325
+* Added Environment Name To Descriptor Unique Signatures #5294 by @Tenzian
+* Run command: Added `--phpunit-xml` option, which produces xml report having the same structure as PhpUnit's #5004 by @Naktibalda
+* Bootstrap command: Changed namespace shortcut to `-s` #5275 by @Naktibalda
+* Improved the docs; by @h311ion, @gimler, @picass0, @josephzidell
+
+
+#### 2.5.1
+
+* Recorder extension improvements by @OneEyedSpaceFish. See #5177:
+ * HTML layout improvements
+ * Restructured tests to show nested output
+ * file operation exceptions / log them without throwing exceptions
+ * fix edge cases with file operations (too long wantTo, etc.)
+ * the ability to automatically purge old reports (from previous runs)
+ * display errors in the recorded page rather than saving it as error.png
+ * the ability not to display any Unicode characters if ANSI only output is requested
+ * the ability not to display any colors in output if no-colors is requested
+ * the ability to change colors in the generated list based on configuration
+* [Db] Made `_loadDump` unconditional like it was in 2.4. Fixed #5195 by @Naktibalda
+* [Db] Allows to specify more than one dump file. See #5220 by @Fenikkusu
+* [WebDriver] Added `waitForElementClickable` by @FatBoyXPC
+* Code coverage: added `work_dir` config option to map remote paths to local. See #5225 by @Fenikkusu
+* [Lumen] Added Lumen 5.5+ support for getRoutes method by @lendormi
+* [Yii2] Restored `getApplication()` API by @Slamdunk
+* [Yii2] Added deprecation doc to `getApplication()` by @Slamdunks
+* [Doctrine2] Reloading module on reconfigure to persist new configs. See #5241 by @joelmedeiros
+* [Doctrine2] Rollback all nested transactions created within test by @Dukecz
+* [DataFactory] Reloading module on reconfigure to persist new configs. See #5241 by @joelmedeiros
+* [Phalcon] Allows null content in response. By @Fenikkusu
+* [Phalcon] Added `session` config option to override session class. By @Fenikkusu
+* [Asserts] Added `expectThrowable()` method by @burned42
+* Use `*.yaml` for params loading
+
+#### 2.5.0
+
+* [**Snapshot testing**](https://codeception.com/docs/09-Data#Testing-Dynamic-Data-with-Snapshots) introduced. Test dynamic data sets by comparing current values with previously saved ones.
+* [Db] **Multi database support**. See #4857 by @eXorus
+ * `amConnectedToDatabase` method added.
+ * `performInDatabase` method added.
+* Rerun tests in **[shuffle mode](https://codeception.com/docs/07-AdvancedUsage#Shuffle)** in the same order by setting seed value. By @SamMousa
+* [PhpBrowser][Frameworks] **Breaking Change** `seeLink` now matches the end of a URL, instead of partial matching. By @Slamdunk
+ * Previous: `$I->seeLink('Delete','/post/1');` matches `Delete`
+ * Now: `$I->seeLink('Delete','/post/1');` does NOT match `Delete`
+* [WebDriver] Keep coverage cookies in `loadSessionSnapshot`. Fix by @rajras
+* [Yii2] Prevent null pointer exception by @SilverFire. See #5136
+* [Yii2] Fixed issue with empty response stream by @SamMousa.
+* [Yii2] Fixed `Too many connections` issue #4926. By @roslov
+* [Yii2] Fixed #4769: `amLoggedInAs()` throws TypeError. By @SamMousa
+* [Recorder Extension] Fixing recorder extension issues caused by phpunit 7.2.7 update by @OneEyedSpaceFish
+* [Logger Extension] Added `codecept_log` function to write to logs from any place. Fixes #3551 by @siad007
+* [WebDriver] Report correct strict locator in error message. When `see()` and `dontSee()` are used with array selector. Fix by @Naktibalda.
+* [Phalcon] Use bind for find record. See #5158 by @Joilson
+* [Phalcon] Add support for nullable fields in `findRecord()` by @arjanwestdorp
+* Added `memory_limit` to `dry-run` command by @siad007. Fixes #5090
+* Added ext-curl to the composer require section by @siad007
+* Make `coverage: show_only_summary` configurable. See #5142 by @Quexer69
+* Ensure php extension `mbstring` is available by @siad007. Fixes #4575
diff --git a/CHANGELOG-3.0.md b/CHANGELOG-3.0.md
new file mode 100644
index 0000000000..cda3755003
--- /dev/null
+++ b/CHANGELOG-3.0.md
@@ -0,0 +1,62 @@
+#### 3.0.3
+- [Laravel5] Add `make` and `makeMultiple` methods for generating model instance by @ibpavlov
+- [Lumen] Add `make` and `makeMultiple` methods for generating model instance by @ibpavlov
+- [Phalcon] ActiveRecord: escape all column names using [] by @maxgalbu
+- [Yii2] Fixed issue on PHP7.3 because `preg_quote` now also quotes `#` by @SamMousa
+- [ZF2] Persistent service functionality for ZF3 by @svycka
+- [ZF2] Doctrine entity manager name is configurable by @svycka
+- [Db] Fix sqlite connection close when holding reference in PHP's GC (#5557) by @hoogi91
+- [Doctrine2] Fixed handling of embedables, inherited entities and parameter name clashes by @alexkunin
+- [Frameworks][PhpBrowser] Fixed compatibility with symfony/browserkit 4.3 by @kapcus
+- [Docs] Small documentation updates by @Nebulosar, @reinholdfuereder and @richardbrinkman
+- [Docker] Switched to buster php build by @OneEyedSpaceFish
+
+#### 3.0.2
+* @weshooper reduced size of exported package.
+* --no-redirect option disables the redirect to a Composer-installed version, by @DanielRuf
+* PhpUnit 8.2 support by @Naktibalda
+* Retry: double wait interval after each execution by @Naktibalda
+* [FTP] Bugfix: Make sure _directory will return its directory by @bbatsche
+* [Doctrine2] Fixed recursive building of association queries by @alexkunin
+* [PhpBrowser] Pass file type option to Guzzle if specified #5548 by @Naktibalda
+* [PhpBrowser][Frameworks] InnerBrowser: selectOption can match by text when option has no value attribute #5547 by @Naktibalda
+* [REST] Updated url construction logic, so it does not produce double slashes, by @nicholascus
+* [ZF2] Add check for console class before calling it by @carnage
+* [Gherkin] Fixed Gherkin setup for single run from group file by @bnpatel1990
+* [CodeCoverage] Ability to use a custom cookie domain for code coverage by @maksimovic
+* [Docs] @EspadaV8 fixed env substitution in DB module example
+* [Docs] @splinter89 mentioned phpdbg and pcov for code coverage
+* @el7cosmos, @KartaviK and @davertMik fixed various deprecation messages
+
+#### 3.0.1
+
+* Fixed code duplication when building actors. Fixes #5506 #5500
+* Fixed autoloader generation for 3.0 docker images by @OneEyedSpaceFish
+* Removed `hoa/console` dependency from `codeception/base` package.
+
+#### 3.0.0
+
+* **BREAKING** Modules removed:
+ * Yii1
+ * XMLRPC
+ * AngularJS
+ * Silex
+ * Facebook
+ * ZF1
+* **POSSIBLE BREAKING** PHPUnit 8.x support.
+> Upgrade Notice: If you face issues with conflicting PHPUnit classes or difference in method signatures, lock version for PHPUnit in composer.json: “phpunit/phpunit”:”^7.0.0”
+* **BREAKING** Multi-session testing disabled by default. Add `use \Codeception\Lib\Actor\Shared\Friend;` to enable `$I->haveFriend`.
+* **BREAKING** [WebDriver] `pauseExecution` removed in favor of `$I->pause()`
+* [Interactive pause](https://codeception.com/docs/02-GettingStarted#Interactive-Pause) inside tests with `$I->pause()` command in debug mode added. Allows to write and debug test in realtime.
+* Introduced [Step Decorators](https://codeception.com/docs/08-Customization#Step-Decorators) - auto-generated actions around module and helper methods. As part of this feature implemented:
+ * [Conditional Assertions](https://codeception.com/docs/03-AcceptanceTests#Conditional-Assertions) (`$I->canSee()`)
+ * [Retries](https://codeception.com/docs/03-AcceptanceTests#Retry) (`$I->retryClick()`)
+ * [Silent Actions](https://codeception.com/docs/03-AcceptanceTests#AB-Testing) (`$I->tryToClick()`)
+* Print artifacts on test failure
+* [REST] Short API responses in debug mode with `shortDebugResponse` config option. See #5455 by @sebastianneubert
+* [WebDriver] `switchToIFrame` allow to locate iframe by CSS/XPath.
+* [PhpBrowser][Frameworks] clickButton throws exception if button is outside form by @Naktibalda.
+* Updated to PHP 7.3 in Docker container by @OneEyedSpaceFish
+* Recorder Extension: Added timestamp information with `include_microseconds` config option. By @OneEyedSpaceFish.
+* [REST] Fixed sending request with duplicated slash with endpoint + URL. By @nicholascus
+* [Db] Remove generateWhereClause method from SqlSrv to be compatible with other drivers. By @Naktibalda
diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md
new file mode 100644
index 0000000000..a72ce40169
--- /dev/null
+++ b/CHANGELOG-3.1.md
@@ -0,0 +1,65 @@
+#### 3.1.3
+
+* Security fix: Disable deserialization of RunProcess class (#6241)
+
+#### 3.1.2
+
+* [Doctrine2] Support non-entity doctrine @id on the haveInRepository debug message by @Basster
+* [Frameworks][PhpBrowser] Improved detection of content-type for .fail files by @jasny
+* [Frameworks][PhpBrowser] Add HTML snapshots of failing tests to HTML Report by @reinholdfuereder
+* [Symfony] Fixed runConsoleCommand by @wirwolf
+* [Symfony] grabService uses the special test service container if available by @burned42
+* [Webdriver] Display cookie details in debug output by @Mitrichius
+* [WebDriver] Improved error text of WaitForJS method by @eriksape
+* Code coverage does not fail when excluded directory not exists by @Mitrichius
+* Use path resolver for bootstrap file detection by @laoneo
+* [Docs] Documentation updates by @burned42, @kishorehariram, @Mitrichius, @ruudboon, @sva1sva
+
+#### 3.1.1
+
+* Preparation for Symfony 5, removes deprecation message when Symfony 4.3 components are used. See #5670 by @Naktibalda
+* [Db] Support initial queries execution after creating connection. See #5660 by @tadasauciunas
+
+```yml
+Db:
+ dsn: # dsn goes hre
+ initial_queries:
+ - 'CREATE DATABASE IF NOT EXISTS temp_db;'
+ - 'USE temp_db;'
+ - 'SET NAMES utf8;'
+```
+
+* Do not fail steps for `retry` and `tryTo` step decorators. Fixes #5666 by @Mitrichius
+* [Symfony] Added `runSymfonyConsoleCommand` by @wirwolf
+
+```php
+$result = $I->runSymfonyConsoleCommand('hello:world', '--verbose' => 3]);
+```
+* [Doctrine2] Bugfix: calling `haveInRepository` with preconstructed entity requires providing constructor parameters. See #5680 by @burned42
+* [Doctrine2] Make debug message in `haveInRepository` to support entities with composite keys of entities in bidirectional relations. See #5685 by Basster. Fixes #5663.
+* Adds possibility to use absolute path for `groups` files in `codeception.yml`. #5674 by @maks-rafalko
+* Fixes the issue with paths in `groups` section when `codeception.yml` is not in the root of the project. #5674) by maks-rafalko.
+* [Asserts] `expectException` deprecated in favor of `expectThrowable`.
+
+#### 3.1.0
+
+* Unified bootstrap file loading
+* Deprecated configuring bootstrap for all suites implicitly
+* Added `--bootstrap` option to `run`
+* [Asserts] Added specialized assertEquals methods
+* [Doctrine2] Added support for Expression and Criteria objects in queries by @alexkunin
+* [Doctrine2] Added fixture support by @alexkunin
+* [Doctrine2] added refreshEntities and clearEntityManager methods by @alexkunin
+* [Doctrine2] implement recursive entity creation by @alexkunin
+* [Doctrine2] properly handle non-typical primary keys in haveInRepository by @alexkunin
+* [Doctrine2] merge functionality of persistEntity into haveInRepository by @alexkunin
+* [Doctrine2] deprecated persistEntity method by @alexkunin
+* [Doctrine2] Make haveInRepository support constructor parameters by @burned42
+* [Symfony] Fixed symfony 4.3 deprecation message removal by @juntereiner
+* [Yii2] Allow to preserve session with recreateApplication enabled by @Slamdunk
+* [Docs] Documentation updates by @Nebulosar and @mikehaertl
+* [Docker] Changed base image to 7.3-cli by @OneEyedSpaceFish
+* Robo build:phar command can be used for building custom phar files again
+* Improved file path detection for groups #5609 by @adaniloff
+* Shortened error output of unexpected exit to one line by @Slamdunk
+* Fixed composer.json of codeception/base package
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 724214c7eb..011baf4afb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ https://github.com/Codeception/Codeception/wiki/Git-workflow-for-Codeception-con
All contributions must follow [PSR-2](http://www.php-fig.org/psr/psr-2/) coding standard.
## Code
-**Bugfixes should be sent to to current stable branch, which is the same as major version number.**
+**Bugfixes should be sent to the current stable branch, which is the same as major version number.**
Breaking features and major improvements should be sent into `master`. When you send PRs to master, they will be added to release cycle only when the next stable branch is started.
@@ -23,7 +23,7 @@ We also encourage to submit bug reports with a failing test or test environment
## Documentation
### Guides
-If you want to contribute documentation to the guides you are asked to send your changes to the /docs/ folder: https://github.com/Codeception/Codeception/tree/2.2/docs. Theses files are the source for the codeception website guides: http://codeception.com/docs/01-Introduction. Remind to send your documentation improvements to the right "repository branch" depending on the Codeception version you are working with: 2.2, master,...
+If you want to contribute documentation to the guides you are asked to send your changes to the /docs/ folder: https://github.com/Codeception/Codeception/tree/2.2/docs. These files are the source for the codeception website guides: http://codeception.com/docs/01-Introduction. Remind to send your documentation improvements to the right "repository branch" depending on the Codeception version you are working with: 2.2, master,...
### Modules Documentation
The documentation for each module is directly generated from the corresponding docblock which can be found in each module (src/Codeception/Module/*.php).
diff --git a/Dockerfile b/Dockerfile
index 9b2df52cff..52ff91b878 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM php:7.2-cli
+FROM php:7.3-cli
MAINTAINER Tobias Munk tobias@diemeisterei.de
@@ -8,6 +8,8 @@ RUN apt-get update && \
git \
zlib1g-dev \
libssl-dev \
+ libzip-dev \
+ unzip \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -20,8 +22,10 @@ RUN docker-php-ext-install \
# Install pecl extensions
RUN pecl install \
mongodb \
- xdebug-2.6.0beta1 && \
+ apcu \
+ xdebug-2.7.2 && \
docker-php-ext-enable \
+ apcu.so \
mongodb.so \
xdebug
@@ -33,7 +37,7 @@ ENV COMPOSER_ALLOW_SUPERUSER=1
RUN curl -sS https://getcomposer.org/installer | php -- \
--filename=composer \
--install-dir=/usr/local/bin
-RUN composer global require --optimize-autoloader \
+RUN composer global require --prefer-dist --no-interaction --optimize-autoloader --apcu-autoloader \
"hirak/prestissimo"
# Prepare application
@@ -41,7 +45,7 @@ WORKDIR /repo
# Install vendor
COPY ./composer.json /repo/composer.json
-RUN composer install --prefer-dist --optimize-autoloader
+RUN composer install --prefer-dist --no-interaction --optimize-autoloader --apcu-autoloader
# Add source-code
COPY . /repo
@@ -51,4 +55,4 @@ ENTRYPOINT ["codecept"]
# Prepare host-volume working directory
RUN mkdir /project
-WORKDIR /project
\ No newline at end of file
+WORKDIR /project
diff --git a/PruneTest.php b/PruneTest.php
new file mode 100644
index 0000000000..c17073a3cc
--- /dev/null
+++ b/PruneTest.php
@@ -0,0 +1,78 @@
+ '/.*Yii2.*/',
+ 'Lumen' => '/.*(Lumen|LaravelCommon).*/',
+ 'Laravel' => '/.*Laravel.*/',
+ 'Phalcon' => '/.*Phalcon.*/',
+ 'Symfony' => '/.*Symfony.*/',
+ 'ZendExpressive' => '/.*ZendExpressive.*/',
+ 'Zend2' => '/.*ZF2.*/',
+];
+
+// First check if changes include files that are not framework files.
+$frameworkOnly = true;
+$frameworks = [];
+foreach ($files as $file) {
+ $match = false;
+ stderr("Testing file: $file");
+ foreach ($regexes as $framework => $regex) {
+ stderr("Checking framework $framework...", false);
+ if (preg_match($regex, $file)) {
+ $match = true;
+ $frameworks[$framework] = $framework;
+ stderr("MATCH");
+ break;
+ }
+ stderr('X');
+ }
+ if (!$match) {
+ stderr("No framework matched, need to run all tests");
+ $frameworkOnly = false;
+ break;
+ }
+}
+
+if ($frameworkOnly) {
+ stderr('Changes limited to frameworks: ' . implode(', ', $frameworks));
+ if (!isset($frameworks[$currentFramework])) {
+ stderr("Skipping test for framework: $currentFramework");
+ echo "travis_terminate 0\n";
+ }
+}
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000000..8ad5f77e5c
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,10 @@
+# Release process
+
+1. Checkout this repository.
+2. Add remote for base repo: git remote add base git@github.com:Codeception/base.git
+3. Run composer install.
+5. Download robo.phar file `wget https://robo.li/robo.phar`
+4. Disable phar.readonly in your php.ini file, it must be `phar.readonly = Off`
+6. Set VERSION in src/Codeception/Codecept.php to version number you want to release and commit this change(if it wasn't updated earlier).
+7. Run `php robo.phar release`
+8. (Optional) Commit updated Codecept.php with VERSION set to next version.
diff --git a/RoboFile.php b/RoboFile.php
index f4c8c22b5f..f6f203a7a9 100644
--- a/RoboFile.php
+++ b/RoboFile.php
@@ -6,16 +6,17 @@
class RoboFile extends \Robo\Tasks
{
- const STABLE_BRANCH = '2.3';
+ const STABLE_BRANCH = '3.1';
const REPO_BLOB_URL = 'https://github.com/Codeception/Codeception/blob';
public function release()
{
$this->say("CODECEPTION RELEASE: ".\Codeception\Codecept::VERSION);
+ $this->stopOnFail();
$this->update();
$this->buildDocs();
$this->publishDocs();
- $this->buildPhar();
+ $this->buildPhar7();
$this->buildPhar5();
$this->publishPhar();
$this->publishGit();
@@ -96,11 +97,6 @@ public function testCoverage()
public function testWebdriver($args = '', $opt = ['test|t' => null])
{
$test = $opt['test'] ? ':'.$opt['test'] : '';
- $container = $this->taskDockerRun('davert/selenium-env')
- ->detached()
- ->publish(4444, 4444)
- ->env('APP_PORT', 8000)
- ->run();
$this->taskServer(8000)
->dir('tests/data/app')
@@ -108,22 +104,14 @@ public function testWebdriver($args = '', $opt = ['test|t' => null])
->host('0.0.0.0')
->run();
- sleep(3); // wait for selenium to launch
-
$this->taskCodecept('./codecept')
- ->test('tests/web/WebDriverTest.php'.$test)
+ ->suite('web')
->args($args)
->run();
-
- $this->taskDockerStop($container)->run();
}
- public function testLaunchServer($pathToSelenium = '~/selenium-server.jar ')
+ public function testLaunchServer()
{
- $this->taskExec('java -jar '.$pathToSelenium)
- ->background()
- ->run();
-
$this->taskServer(8010)
->background()
->dir('tests/data/rest')
@@ -144,11 +132,11 @@ public function testCli()
->run();
}
- private function installDependenciesForPhp54()
+ private function installDependenciesForPhp5()
{
$this->taskReplaceInFile('composer.json')
->regex('/"platform": \{.*?\}/')
- ->to('"platform": {"php": "5.4.0"}')
+ ->to('"platform": {"php": "5.6.0"}')
->run();
$this->taskComposerUpdate()->run();
@@ -179,17 +167,27 @@ private function revertComposerJsonChanges()
*/
public function buildPhar()
{
- $this->installDependenciesForPhp70();
$this->packPhar('package/codecept.phar');
$code = $this->taskExec('php codecept.phar')->dir('package')->run()->getExitCode();
if ($code !== 0) {
throw new Exception("There was problem compiling phar");
}
+ }
+
+
+ /**
+ * @desc creates codecept.phar for PHP 7.0
+ * @throws Exception
+ */
+ public function buildPhar7()
+ {
+ $this->installDependenciesForPhp70();
+ $this->buildPhar();
$this->revertComposerJsonChanges();
}
/**
- * @desc creates codecept.phar with Guzzle 5.3 and Symfony 2.8
+ * @desc creates codecept.phar for PHP 5.6 with Guzzle 5.3 and Symfony 2.8
* @throws Exception
*/
public function buildPhar5()
@@ -197,7 +195,7 @@ public function buildPhar5()
if (!file_exists('package/php54')) {
mkdir('package/php54');
}
- $this->installDependenciesForPhp54();
+ $this->installDependenciesForPhp5();
$this->packPhar('package/codecept5.phar');
$this->_copy('package/codecept5.phar', 'package/php54/codecept.phar');
$code = $this->taskExec('php codecept.phar')->dir('package/php54')->run()->getExitCode();
@@ -270,10 +268,13 @@ private function packPhar($pharFileName)
$pharTask->addFile('autoload.php', 'autoload.php')
->addFile('codecept', 'package/bin')
- ->addFile('shim.php', 'shim.php')
- ->addFile('phpunit5-loggers.php', 'phpunit5-loggers.php')
- ->run();
+ ->addFile('shim.php', 'shim.php');
+ if (file_exists(__DIR__ .'phpunit5-loggers.php')) {
+ $pharTask->addFile('phpunit5-loggers.php', 'phpunit5-loggers.php');
+ }
+
+ $pharTask->run();
}
/**
@@ -377,10 +378,16 @@ public function buildDocsStub()
$this->taskGenDoc('docs/reference/Stub.md')
->docClass('Codeception\Stub')
- ->filterMethods(function(\ReflectionMethod $method) {
- if ($method->isConstructor() or $method->isDestructor()) return false;
- if (!$method->isPublic()) return false;
- if (strpos($method->name, '_') === 0) return false;
+ ->filterMethods(function (\ReflectionMethod $method) {
+ if ($method->isConstructor() or $method->isDestructor()) {
+ return false;
+ }
+ if (!$method->isPublic()) {
+ return false;
+ }
+ if (strpos($method->name, '_') === 0) {
+ return false;
+ }
return true;
})
->processMethodDocBlock(
@@ -388,7 +395,8 @@ function (\ReflectionMethod $m, $doc) {
$doc = str_replace(array('@since'), array(' * available since version'), $doc);
$doc = str_replace(array(' @', "\n@"), array(" * ", "\n * "), $doc);
return $doc;
- })
+ }
+ )
->processProperty(false)
->run();
@@ -405,11 +413,19 @@ function (\ReflectionMethod $m, $doc) {
->processClassDocBlock(false)
->processClassSignature(false)
->prepend($mocksDocumentation)
- ->filterMethods(function(\ReflectionMethod $method) {
- if ($method->isConstructor() or $method->isDestructor()) return false;
- if (!$method->isPublic()) return false;
- if (strpos($method->name, '_') === 0) return false;
- if (strpos($method->name, 'stub') === 0) return false;
+ ->filterMethods(function (\ReflectionMethod $method) {
+ if ($method->isConstructor() or $method->isDestructor()) {
+ return false;
+ }
+ if (!$method->isPublic()) {
+ return false;
+ }
+ if (strpos($method->name, '_') === 0) {
+ return false;
+ }
+ if (strpos($method->name, 'stub') === 0) {
+ return false;
+ }
return true;
})
->run();
@@ -529,8 +545,10 @@ public function publishPhar()
$releaseFile->line("\n## $branch");
if ($major < 2) {
$releaseFile->line("*Requires: PHP 5.3 and higher + CURL*\n");
- } else {
+ } elseif ($major == 2 && $minor < 4) {
$releaseFile->line("*Requires: PHP 5.4 and higher + CURL*\n");
+ } else {
+ $releaseFile->line("*Requires: PHP 5.6 and higher + CURL*\n");
}
$releaseFile->line("* **[Download Latest $branch Release]($downloadUrl)**");
}
@@ -538,7 +556,9 @@ public function publishPhar()
if (file_exists("releases/$releaseName/php54/codecept.phar")) {
$downloadUrl = "http://codeception.com/releases/$releaseName/php54/codecept.phar";
- if (version_compare($releaseName, '2.3.0', '>=')) {
+ if (version_compare($releaseName, '2.4.0', '>=')) {
+ $versionLine .= ", [for PHP 5.6]($downloadUrl)";
+ } elseif (version_compare($releaseName, '2.3.0', '>=')) {
$versionLine .= ", [for PHP 5.4 - 5.6]($downloadUrl)";
} else {
$versionLine .= ", [for PHP 5.4 or 5.5]($downloadUrl)";
@@ -607,7 +627,7 @@ public function publishDocs()
'source' => self::REPO_BLOB_URL."/".self::STABLE_BRANCH."/src/Codeception/Module/$name.php"
];
// building version switcher
- foreach (['master', '2.2', '2.1', '2.0', '1.8'] as $branch) {
+ foreach (['master', '3.0', '2.5', '1.8'] as $branch) {
$buttons[$branch] = self::REPO_BLOB_URL."/$branch/docs/modules/$name.md";
}
$buttonHtml = "\n\n".'
';
@@ -875,6 +895,11 @@ public function publishBase($branch = null, $tag = null)
->to('')
->run();
+ $this->taskReplaceInFile('composer.json')
+ ->regex('~^\s+"hoa\/console".*$~m')
+ ->to('')
+ ->run();
+
$this->taskReplaceInFile('composer.json')
->regex('~^\s+"guzzlehttp\/guzzle".*$~m')
->to('')
diff --git a/appveyor.yml b/appveyor.yml
index aa810db8ed..643a83fda3 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,10 +1,9 @@
build: false
-shallow_clone: true
platform: 'x86'
branches:
except:
- gh-pages
-
+
cache:
- c:\tools\php71 -> appveyor.yml
@@ -27,18 +26,14 @@ install:
- SET PATH=C:\Program Files\PostgreSQL\9.3\bin\;%PATH%
# php setup
- IF EXIST c:\tools\php71 (SET PHP=0)
+ - ps: Set-Service wuauserv -StartupType Manual
- IF %PHP%==1 cinst -y OpenSSL.Light
- SET PATH=C:\Program Files\OpenSSL;%PATH%
- cinst -y curl
- SET PATH=C:\Program Files\curl;%PATH%
- sc config wuauserv start= auto
- - net start wuauserv
- IF %PHP%==1 cinst -y php --version 7.1.14
- IF %PHP%==1 cd c:\tools\php71
- - IF %PHP%==1 cd ext
- - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/mongodb/1.2.0/php_mongodb-1.2.0-7.1-nts-vc14-x64.zip
- - IF %PHP%==1 7z x php_mongodb-1.2.0-7.1-nts-vc14-x64.zip -y >nul
- - IF %PHP%==1 cd ..
- IF %PHP%==1 copy php.ini-production php.ini
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
@@ -49,7 +44,6 @@ install:
- IF %PHP%==1 echo extension=php_pdo_pgsql.dll >> php.ini
- IF %PHP%==1 echo extension=php_pdo_sqlite.dll >> php.ini
- IF %PHP%==1 echo extension=php_pgsql.dll >> php.ini
- - IF %PHP%==1 echo extension=php_mongodb.dll >> php.ini
- SET PATH=C:\tools\php71;%PATH%
- cd %APPVEYOR_BUILD_FOLDER%
- appveyor DownloadFile https://getcomposer.org/composer.phar
@@ -60,5 +54,5 @@ before_test:
- mysql -uroot -pPassword12! -e "CREATE DATABASE codeception_test"
test_script:
- - php codecept run cli --no-colors -n --skip-group coverage
- - php codecept run unit -g core -g appveyor --no-colors -n
+ - codecept run cli --no-colors -n --skip-group coverage
+ - codecept run unit -g core -g appveyor --no-colors -n
diff --git a/autoload.php b/autoload.php
index 05b92f925d..cf59628eb2 100644
--- a/autoload.php
+++ b/autoload.php
@@ -1,11 +1,11 @@
'No error',
- JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
- JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
- JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
- JSON_ERROR_SYNTAX => 'Syntax error',
- JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
- );
-
- $error = json_last_error();
- return isset($errors[$error]) ? $errors[$error] : 'Unknown error';
- }
-}
-
// function not autoloaded in PHP, thus its a good place for them
if (!function_exists('codecept_debug')) {
function codecept_debug($data)
@@ -121,3 +80,32 @@ function codecept_relative_path($path)
);
}
}
+
+if (!function_exists('codecept_absolute_path')) {
+ /**
+ * If $path is absolute, it will be returned without changes.
+ * If $path is relative, it will be passed to `codecept_root_dir()` function
+ * to make it absolute.
+ *
+ * @param string $path
+ * @return string the absolute path
+ */
+ function codecept_absolute_path($path)
+ {
+ return codecept_is_path_absolute($path) ? $path : codecept_root_dir($path);
+ }
+}
+
+if (!function_exists('codecept_is_path_absolute')) {
+ /**
+ * Check whether the given $path is absolute.
+ *
+ * @param string $path
+ * @return bool
+ * @since 2.4.4
+ */
+ function codecept_is_path_absolute($path)
+ {
+ return \Codeception\Util\PathResolver::isPathAbsolute($path);
+ }
+}
diff --git a/codecept b/codecept
index cb11444b7f..8047a92a46 100755
--- a/codecept
+++ b/codecept
@@ -23,6 +23,7 @@ $app->add(new Codeception\Command\GenerateScenarios('generate:scenarios'));
$app->add(new Codeception\Command\Clean('clean'));
$app->add(new Codeception\Command\GenerateGroup('generate:groupobject'));
$app->add(new Codeception\Command\GeneratePageObject('generate:pageobject'));
+$app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot'));
$app->add(new Codeception\Command\GenerateStepObject('generate:stepobject'));
$app->add(new Codeception\Command\GenerateEnvironment('generate:environment'));
$app->add(new Codeception\Command\GenerateFeature('generate:feature'));
diff --git a/codecept.bat b/codecept.bat
index 0edc8f7934..7d800d1548 100644
--- a/codecept.bat
+++ b/codecept.bat
@@ -1,11 +1,7 @@
@echo off
-if "%PHPBIN%" == "" set PHPBIN=@php_bin@
-if exist "codecept" goto INTERNAL
-if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
-GOTO RUN
-:USE_PEAR_PATH
-set PHPBIN=%PHP_PEAR_PHP_BIN%
-:RUN
-"%PHPBIN%" "@bin_dir@\codecept" %*
-:INTERNAL
-"%PHPBIN%" "codecept" %*
\ No newline at end of file
+
+if "%PHP_PEAR_PHP_BIN%" neq "" (
+ set PHPBIN=%PHP_PEAR_PHP_BIN%
+) else set PHPBIN=php
+
+"%PHPBIN%" "codecept" %*
diff --git a/codeception.yml b/codeception.yml
index 1b9828b94f..cf11c92990 100644
--- a/codeception.yml
+++ b/codeception.yml
@@ -6,7 +6,6 @@ paths:
envs: tests/envs
settings:
shuffle: true
- bootstrap: _bootstrap.php
suite_class: \PHPUnit_Framework_TestSuite
colors: true
memory_limit: 1024M
@@ -37,10 +36,5 @@ coverage:
enabled: true
include:
- src/Codeception/Command/*
- - src/Codeception/Module/Symfony1.php
- - src/Codeception/Module/Symfony2.php
- - src/Codeception/Module/Doctrine1.php
+ - src/Codeception/Module/Symfony.php
- src/Codeception/Module/Doctrine2.php
- - src/Codeception/Module/Kohana.php
- - src/Codeception/Module/SocialEngine.php
- - src/Codeception/Module/ZF1.php
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 806d03548c..16fe67c19a 100644
--- a/composer.json
+++ b/composer.json
@@ -15,14 +15,14 @@
"minimum-stability": "RC",
"require": {
- "php": ">=5.4.0 <8.0",
+ "php": ">=5.6.0 <8.0",
+ "ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
- "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0",
- "phpunit/php-code-coverage": ">=2.2.4 <6.0",
- "facebook/webdriver": ">=1.1.3 <2.0",
- "guzzlehttp/guzzle": ">=4.1.4 <7.0",
- "guzzlehttp/psr7": "~1.0",
+ "facebook/webdriver": "^1.6.0",
+ "guzzlehttp/guzzle": "^6.3.0",
+ "guzzlehttp/psr7": "~1.4",
+ "hoa/console": "~3.0",
"symfony/finder": ">=2.7 <5.0",
"symfony/console": ">=2.7 <5.0",
"symfony/event-dispatcher": ">=2.7 <5.0",
@@ -30,22 +30,24 @@
"symfony/browser-kit": ">=2.7 <5.0",
"symfony/css-selector": ">=2.7 <5.0",
"symfony/dom-crawler": ">=2.7 <5.0",
- "behat/gherkin": "~4.4.0",
- "sebastian/comparator": ">1.1 <3.0",
- "sebastian/diff": ">=1.4 <3.0",
- "codeception/stub": "^1.0"
+ "behat/gherkin": "^4.4.0",
+ "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3",
+ "codeception/stub": "^2.0 | ^3.0"
},
"require-dev": {
"monolog/monolog": "~1.8",
- "facebook/graph-sdk": "~5.3",
"php-amqplib/php-amqplib": "~2.4",
"codeception/specify": "~0.3",
"pda/pheanstalk": "~3.0",
"flow/jsonpath": "~0.2",
"predis/predis": "^1.0",
"squizlabs/php_codesniffer": "~2.0",
- "vlucas/phpdotenv": "^2.4.0",
- "symfony/process": ">=2.7 <5.0"
+ "vlucas/phpdotenv": "^3.0",
+ "symfony/process": ">=2.7 <5.0",
+ "doctrine/orm": "^2",
+ "doctrine/annotations": "^1",
+ "doctrine/data-fixtures": "^1",
+ "ramsey/uuid-doctrine": "^1.5"
},
"suggest": {
"aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module",
@@ -62,10 +64,18 @@
"autoload":{
"psr-4":{
- "Codeception\\": "src\\Codeception",
+ "Codeception\\": "src/Codeception",
"Codeception\\Extension\\": "ext"
}
},
+ "autoload-dev": {
+ "classmap": [
+ "tests/cli/_steps",
+ "tests/web/_steps",
+ "tests/data/DummyClass.php",
+ "tests/data/claypit/tests/_data"
+ ]
+ },
"extra": {
"branch-alias": {
}
diff --git a/docs/01-Introduction.md b/docs/01-Introduction.md
index ff97b24663..c075b17c89 100644
--- a/docs/01-Introduction.md
+++ b/docs/01-Introduction.md
@@ -16,17 +16,17 @@ To test the behavior of your application as a whole, you should write functional
Codeception supports all three testing types.
Out of the box you have tools for writing unit, functional, and acceptance tests in a unified framework.
-| | Codeception Unit Tests | Codeception Functional Tests | Codeception Acceptance Tests
+| | Unit Tests | Functional Tests | Acceptance Tests
| --- | --- | --- | --- |
| Scope of the test | Single PHP class | PHP Framework (Routing, Controllers, etc.) | Page in browser (Chrome, Firefox, or PhpBrowser) |
| Testing computer needs access to project's PHP files | Yes | Yes | No |
| Webserver required | No | No | Yes |
| JavaScript | No | No | Yes |
-| Additional software required | None | None |
For WebDriver: Selenium Server or PhantomJS (deprecated)
For PhpBrowser: None
|
-| Test execution speed | High | High | Low |
+| Additional software required | None | None | Selenium for browser testing |
+| Speed | Fast | Fast | Slow |
| Configuration file | `unit.suite.yml` | `functional.suite.yml` | `acceptance.suite.yml` |
-One of the main advantages of Codeception is that you don't have to decide on just *one* type of testing. You can have all three!
+One of the main advantages of Codeception is that you don't have to decide on just *one* type of testing. You should have all three!
And chances are, that you will (sooner or later) need all three. That's why Codeception consists of three so-called "suites":
A "unit suite" for all unit tests, a "functional suite" for all functional tests, and an "acceptance suite" for all acceptance tests.
@@ -34,45 +34,40 @@ Let's review those three testing types in reverse order.
### Acceptance Tests
-How does your client, manager, tester, or any other non-technical person know your website is working?
-By opening the browser, accessing the site, clicking on links, filling in the forms,
-and actually seeing the content on a web page. They have no idea of the programming language, framework, database, web-server,
+How does your client, manager, tester, or any other non-technical person know your website is working? By opening the browser, accessing the site, clicking on links, filling in the forms, and actually seeing the content on a web page. They have no idea of the programming language, framework, database, web-server,
or why the application did (or did not) behave as expected.
This is what acceptance tests are doing. They cover scenarios from a user's perspective.
With acceptance tests, you can be confident that users, following all the defined scenarios, won't get errors.
-**Any website** can be covered with acceptance tests, even if you use a very exotic CMS or framework.
+> **Any website** can be covered with acceptance tests, even if you use a very exotic CMS or framework.
#### Sample acceptance test
```php
amOnPage('/');
$I->click('Sign Up');
-$I->submitForm('#signup', ['username' => 'MilesDavis', 'email' => 'miles@davis.com']);
+$I->submitForm('#signup', [
+ 'username' => 'MilesDavis',
+ 'email' => 'miles@davis.com'
+]);
$I->see('Thank you for Signing Up!');
```
### Functional Tests
-What if we could check our application without running it on a server?
-That way we could see detailed exceptions on errors, have our tests run faster,
-and check the database against predictable and expected results. That's what functional tests are for.
+What if you could check our application without running it on a server?
+That way you could see detailed exceptions on errors, have our tests run faster, and check the database against predictable and expected results. That's what functional tests are for.
-For functional tests, you emulate a web request (`$_GET` and `$_POST` variables)
-which returns the HTML response. Inside a test, you can make assertions about the response,
-and you can check if the data was successfully stored in the database.
+For functional tests, you emulate a web request (`$_GET` and `$_POST` variables) which returns the HTML response. Inside a test, you can make assertions about the response, and you can check if the data was successfully stored in the database.
-For functional tests, your application needs to be structured in order to run in a test environment.
-Codeception provides connectors to several popular PHP frameworks. You can also write your own.
+For functional tests, your application needs to be structured in order to run in a test environment. Codeception provides connectors to all popular PHP frameworks.
#### Sample functional test
```php
amOnPage('/');
$I->click('Sign Up');
$I->submitForm('#signup', ['username' => 'MilesDavis', 'email' => 'miles@davis.com']);
@@ -81,6 +76,8 @@ $I->seeEmailSent('miles@davis.com', 'Thank you for registration');
$I->seeInDatabase('users', ['email' => 'miles@davis.com']);
```
+> This looks very similar to acceptance tests. The behavior is the same, however, the test is executed inside PHP without launching a browser.
+
### Unit Tests
Testing pieces of code before coupling them together is highly important as well. This way,
@@ -99,23 +96,24 @@ The better you understand the testing scenario, the faster you can update it for
```php
setName('Miles');
$user->setSurname('Davis');
$user->save();
$this->assertEquals('Miles Davis', $user->getFullName());
- $this->unitTester->seeInDatabase('users', ['name' => 'Miles', 'surname' => 'Davis']);
+ $this->tester->seeInDatabase('users', [
+ 'name' => 'Miles',
+ 'surname' => 'Davis'
+ ]);
}
```
## Conclusion
-Despite the wide popularity of *TDD* (Test Driven Development), some PHP developers never write automated tests for their applications mostly because they think it's hard, slow or boring.
The Codeception framework was developed to actually make testing fun.
It allows writing unit, functional, integration, and acceptance tests in a single, coherent style.
-It can be called a *BDD* (Behavior Driven Development) framework. All Codeception tests are written in a descriptive manner.
+All Codeception tests are written in a descriptive manner.
Just by looking at the test body, you can clearly understand what is being tested and how it is performed.
-Even complex tests with many assertions are written in a simple PHP Domain-Specific Language (*DSL*).
diff --git a/docs/02-GettingStarted.md b/docs/02-GettingStarted.md
index 058b13193c..0862648466 100644
--- a/docs/02-GettingStarted.md
+++ b/docs/02-GettingStarted.md
@@ -14,18 +14,18 @@ Codeception follows simple naming rules to make it easy to remember (as well as
click('Login');
$I->fillField('#input-username', 'John Dough');
- $i->pressKey('#input-remarks', 'foo');
+ $I->pressKey('#input-remarks', 'foo');
```
* **Assertions** always start with "see" or "dontSee". Examples:
```php
see('Welcome');
$I->seeInTitle('My Company');
- $i->seeElement('nav');
- $i->dontSeeElement('#error-message');
- $i->dontSeeInPageSource('');
+ $I->seeElement('nav');
+ $I->dontSeeElement('#error-message');
+ $I->dontSeeInPageSource('');
```
-* **Grabbers** just *read* something from the page, but don't process it. The return value of those are meant to be saved as variables and used later. Example:
+* **Grabbers** take information. The return value of those are meant to be saved as variables and used later. Example:
```php
grabAttributeFrom('#login-form', 'method');
@@ -34,13 +34,11 @@ Codeception follows simple naming rules to make it easy to remember (as well as
## Actors
-One of the main concepts of Codeception is representation of tests as actions of a person.
-We have a UnitTester, who executes functions and tests the code. We also have a FunctionalTester, a qualified tester,
+One of the main concepts of Codeception is representation of tests as actions of a person.We have a UnitTester, who executes functions and tests the code. We also have a FunctionalTester, a qualified tester,
who tests the application as a whole, with knowledge of its internals. Lastly we have an AcceptanceTester, a user who works with our application
through an interface that we provide.
-**Methods of actor classes are generally taken from [Codeception Modules](http://codeception.com/docs/06-ModulesAndHelpers)**.
-Each module provides predefined actions for different testing purposes, and they can be combined to fit the testing environment.
+Methods of actor classes are generally taken from [Codeception Modules](http://codeception.com/docs/06-ModulesAndHelpers). Each module provides predefined actions for different testing purposes, and they can be combined to fit the testing environment.
Codeception tries to solve 90% of possible testing issues in its modules, so you don't have to reinvent the wheel.
We think that you can spend more time on writing tests and less on writing support code to make those tests run.
By default, AcceptanceTester relies on PhpBrowser module, which is set in the `tests/acceptance.suite.yml` configuration file:
@@ -59,66 +57,64 @@ When you change the configuration, the actor classes are rebuilt automatically.
try to generate them manually with the `build` command:
```bash
-php codecept build
+php vendor/bin/codecept build
```
-## Writing a Sample Scenario
+## Writing a Sample Test
-By default tests are written as narrative scenarios. To make a PHP file a valid scenario, its name should have a `Cept` suffix.
-
-Let's say we have created a file `tests/acceptance/SigninCept.php`
-
-We can do that by running the following command:
+Codeception has its own testing format called Cest (Codecept + Test).
+To start writing a test we need to create a new Cest file. We can do that by running the following command:
```bash
-php codecept generate:cept acceptance Signin
+php vendor/bin/codecept generate:cest acceptance Signin
```
-A scenario always starts with actor class initialization. After that, writing a scenario is just like typing `$I->`
-and choosing a proper action from the auto-completion list. Let's log in to our website:
+This will generate `SigninCest.php` file inside `tests/acceptance` directory. Let's open it:
```php
wantTo('login to website');
-
-```
-
-The `wantTo` section describes your scenario in brief. There are additional comment methods that are useful to describe the context of a scenario:
+class SigninCest
+{
+ function _before(AcceptanceTester $I)
+ {
+ }
+
+ public function _after(AcceptanceTester $I)
+ {
+ }
-```php
-am('user'); // actor's role
-$I->wantTo('login to website'); // feature to test
-$I->lookForwardTo('access website features for logged-in users'); // result to achieve
+ public function tryToTest(AcceptanceTester $I)
+ {
+ // todo: write test
+ }
+}
```
-After we have described the story background, let's start writing a scenario.
+We have `_before` and `_after` methods to run some common actions before and after a test. And we have a placeholder action `tryToTest` which we need to implement.
+If we try to test a signin process it's a good start to test a successful signin. Let's rename this method to `signInSuccessfully`.
-We'll assume that we have a 'login' page where we get authenticated by providing a username and password.
+We'll assume that we have a 'login' page where we get authenticated by providing a username and password.
Then we are sent to a user page, where we see the text `Hello, %username%`. Let's look at how this scenario is written in Codeception:
```php
am('user');
-$I->wantTo('login to website');
-$I->lookForwardTo('access website features for logged-in users');
-$I->amOnPage('/login');
-$I->fillField('Username','davert');
-$I->fillField('Password','qwerty');
-$I->click('Login');
-$I->see('Hello, davert');
+class SigninCest
+{
+ public function loginSuccessfully(AcceptanceTester $I)
+ {
+ $I->amOnPage('/login');
+ $I->fillField('Username','davert');
+ $I->fillField('Password','qwerty');
+ $I->click('Login');
+ $I->see('Hello, davert');
+ }
+}
```
This scenario can probably be read by non-technical people. If you just remove all special chars like braces, arrows and `$`,
this test transforms into plain English text:
```
-I am user
-I wantTo login to website
-I lookForwardTo access website features for logged-in users
I amOnPage '/login'
I fillField 'Username','davert'
I fillField 'Password','qwerty'
@@ -129,7 +125,7 @@ I see 'Hello, davert'
Codeception generates this text representation from PHP code by executing:
```bash
-php codecept generate:scenarios
+php vendor/bin/codecept generate:scenarios
```
These generated scenarios will be stored in your `_data` directory in text files.
@@ -149,14 +145,14 @@ modules:
After configuring the URL we can run this test with the `run` command:
```bash
-php codecept run
+php vendor/bin/codecept run
```
This is the output we should see:
```bash
Acceptance Tests (1) -------------------------------
-✔ SigninCept: Login to website
+✔ SigninCest: sign in successfully
----------------------------------------------------
Time: 1 second, Memory: 21.00Mb
@@ -167,19 +163,17 @@ OK (1 test, 1 assertions)
Let's get some detailed output:
```bash
-php codecept run acceptance --steps
+php vendor/bin/codecept run acceptance --steps
```
We should see a step-by-step report on the performed actions:
```bash
Acceptance Tests (1) -------------------------------
-SigninCept: Login to website
-Signature: SigninCept.php
-Test: tests/acceptance/SigninCept.php
+SigninCest: Login to website
+Signature: SigninCest.php:signInSuccessfully
+Test: tests/acceptance/SigninCest.php:signInSuccessfully
Scenario --
- I am user
- I look forward to access website features for logged-in users
I am on page "/login"
I fill field "Username" "davert"
I fill field "Password" "qwerty"
@@ -196,55 +190,87 @@ OK (1 test, 1 assertions)
This simple test can be extended to a complete scenario of site usage, therefore,
by emulating the user's actions, you can test any of your websites.
-Give it a try!
-
-## Cept, Cest and Test Formats
+To run more tests create a public method for each of them. Include `AcceptanceTester` object as `$I` as a method parameter and use the same `$I->` API you've seen before.
+If your tests share common setup actions put them into `_before` method.
-Codeception supports three test formats. Beside the previously described scenario-based Cept format,
-Codeception can also execute [PHPUnit test files for unit testing](http://codeception.com/docs/05-UnitTests), and Cest format.
-
-**Cest** combines scenario-driven test approach with OOP design. In case you want to group a few testing scenarios into one, you should consider using Cest format.
-In the example below we are testing CRUD actions within a single file but with several tests (one per operation):
+For instance, to test CRUD we want 4 methods to be implemented and all next tests should start at `/task` page:
```php
amOnPage('/');
+ $I->amOnPage('/task');
}
- function createPage(AcceptanceTester $I)
+ function createTask(AcceptanceTester $I)
{
// todo: write test
}
- function viewPage(AcceptanceTester $I)
+ function viewTask(AcceptanceTester $I)
{
// todo: write test
}
- function updatePage(AcceptanceTester $I)
+ function updateTask(AcceptanceTester $I)
{
// todo: write test
}
- function deletePage(AcceptanceTester $I)
+ function deleteTask(AcceptanceTester $I)
{
// todo: write test
}
}
```
-Cest files such as this can be created by running a generator:
+Learn more about the [Cest format](http://codeception.com/docs/07-AdvancedUsage#Cest-Classes) in the Advanced Testing section.
-```bash
-php codecept generate:cest acceptance PageCrud
+## Interactive Pause
+
+It's hard to write a complete test at once.
+You will need to try different commands with different arguments before you find a correct path.
+
+Since Codeception 3.0 you can pause execution in any point and enter interactive shell where you will be able to try commands in action.
+All you need to do is to **call `$I->pause()` in debug mode**.
+
+```php
+pause();
```
-Learn more about the [Cest format](http://codeception.com/docs/07-AdvancedUsage#Cest-Classes) in the Advanced Testing section.
+When a test gets to this point it stops and shows a console where you can try all available commands.
+This can be very useful when you write functional, acceptance, or api test.
+
+
+
+Inside interactive pause you can use all power of PHP interpreter. Use variables, functions, etc.
+Result of the last executed command (usually a grabber) is saved to `$result` variable, so you can use it in next commands.
+
+Inside acceptance or functional test you can save page screenshot or html snapshot to check the page you are working on.
+
+```php
+makeHtmlSnapshot();
+
+// inside WebDriver
+// saves screenshot and prints a path to created file
+$I->makeScreenshot();
+```
+
+To try commands without running a single test you can launch interactive console:
+
+``` bash
+$ php vendor/bin/codecept console suitename
+```
+
+Now you can execute all the commands of a corresponding Actor class and see the results immediately.
## BDD
@@ -262,44 +288,44 @@ The same goes for suite configs. For example, the `unit.suite.yml` will be merge
Tests can be started with the `run` command:
```bash
-php codecept run
+php vendor/bin/codecept run
```
With the first argument you can run all tests from one suite:
```bash
-php codecept run acceptance
+php vendor/bin/codecept run acceptance
```
To limit tests run to a single class, add a second argument. Provide a local path to the test class, from the suite directory:
```bash
-php codecept run acceptance SigninCept.php
+php vendor/bin/codecept run acceptance SigninCest.php
```
Alternatively you can provide the full path to test file:
```bash
-php codecept run tests/acceptance/SigninCept.php
+php vendor/bin/codecept run tests/acceptance/SigninCest.php
```
You can further filter which tests are run by appending a method name to the class, separated by a colon (for Cest or Test formats):
```bash
-php codecept run tests/acceptance/SignInCest.php:^anonymousLogin$
+php vendor/bin/codecept run tests/acceptance/SigninCest.php:^anonymousLogin$
```
You can provide a directory path as well. This will execute all acceptance tests from the `backend` dir:
```bash
-php codecept run tests/acceptance/backend
+php vendor/bin/codecept run tests/acceptance/backend
```
Using regular expressions, you can even run many different test methods from the same directory or class.
For example, this will execute all acceptance tests from the `backend` dir beginning with the word "login":
```bash
-php codecept run tests/acceptance/backend:^login
+php vendor/bin/codecept run tests/acceptance/backend:^login
```
To execute a group of tests that are not stored in the same directory, you can organize them in [groups](http://codeception.com/docs/07-AdvancedUsage#Groups).
@@ -309,7 +335,7 @@ To execute a group of tests that are not stored in the same directory, you can o
To generate JUnit XML output, you can provide the `--xml` option, and `--html` for HTML report.
```bash
-php codecept run --steps --xml --html
+php vendor/bin/codecept run --steps --xml --html
```
This command will run all tests for all suites, displaying the steps, and building HTML and XML reports. Reports will be stored in the `tests/_output/` directory.
@@ -317,7 +343,7 @@ This command will run all tests for all suites, displaying the steps, and buildi
To see all the available options, run the following command:
```bash
-php codecept help run
+php vendor/bin/codecept help run
```
## Debugging
@@ -329,7 +355,6 @@ You may print any information inside a test using the `codecept_debug` function.
There are plenty of useful Codeception commands:
-* `generate:cept` *suite* *filename* - Generates a sample Cept scenario
* `generate:cest` *suite* *filename* - Generates a sample Cest test
* `generate:test` *suite* *filename* - Generates a sample PHPUnit Test with Codeception hooks
* `generate:feature` *suite* *filename* - Generates Gherkin feature file
diff --git a/docs/03-AcceptanceTests.md b/docs/03-AcceptanceTests.md
index 171b1cb755..751a7030b1 100644
--- a/docs/03-AcceptanceTests.md
+++ b/docs/03-AcceptanceTests.md
@@ -60,13 +60,22 @@ modules:
- \Helper\Acceptance
```
-We should start by creating a 'Cept' file:
+We should start by creating a test with the next command:
+
+```
+php vendor/bin/codecept g:cest acceptance Signin
+```
+
+It will be placed into `tests/acceptance` directory.
```php
wantTo('sign in');
+class SigninCest
+{
+ public function tryToTest(AcceptanceTester $I)
+ {
+ }
+}
```
The `$I` object is used to write all interactions.
@@ -196,6 +205,21 @@ $I->submitForm('#update_form', array('user' => array(
)));
```
+##### Hiding Sensitive Data
+
+If you need to fill in sensitive data (like passwords) and hide it in logs,
+you can pass instance `\Codeception\Step\Argument\PasswordArgument` with the data which needs to be hidden.
+
+```php
+amOnPage('/form/password_argument');
+$I->fillField('password', new PasswordArgument('thisissecret'));
+```
+
+`thisissecret` will be filled into a form but it won't be shown in output and logs.
+
#### Assertions
In the `PhpBrowser` you can test the page contents.
@@ -249,6 +273,19 @@ $I->cantSeeInField('user[name]', 'Miles');
Each failed assertion will be shown in the test results, but it won't stop the test.
+Conditional assertions are disabled in bootstrap setup. To enable them you should add corresponding step decorators to suite config:
+
+> If you started project as `codecept init acceptance` they should be already enabled in config
+
+```yaml
+# in acceptance.suite.yml
+# or in codeception.yml inside suites section
+step_decorators:
+ - \Codeception\Step\ConditionalAssertion
+```
+
+Then rebuild actors with `codecept build` command.
+
#### Comments
Within a long scenario, you should describe what actions you are going to perform and what results should be achieved.
@@ -270,7 +307,7 @@ and you want to check that the user can log into the site using this password:
```php
fillField('email', 'miles@davis.com')
+$I->fillField('email', 'miles@davis.com');
$I->click('Generate Password');
$password = $I->grabTextFrom('#password');
$I->click('Login');
@@ -312,9 +349,9 @@ Actions for URLs:
```php
seeCurrentUrlEquals('/login');
-$I->seeCurrentUrlMatches('~$/users/(\d+)~');
+$I->seeCurrentUrlMatches('~^/users/(\d+)~');
$I->seeInCurrentUrl('user/1');
-$user_id = $I->grabFromCurrentUrl('~$/user/(\d+)/~');
+$user_id = $I->grabFromCurrentUrl('~^/user/(\d+)/~');
```
## WebDriver
@@ -347,19 +384,6 @@ This makes Selenium quite heavy to install, as it requires Java, browsers, Chrom
* Follow [Installation Instructions](http://codeception.com/docs/modules/WebDriver#Selenium)
* Enable [RunProcess](http://codeception.com/extensions#RunProcess) extension to start/stop Selenium automatically *(optional)*.
-#### PhantomJS
-
-PhantomJS is a customized WebKit-based [headless browser](https://en.wikipedia.org/wiki/Headless_browser)
-built for programmatic usage only. It doesn't display a browser window and doesn't require GUI (display server) to be installed.
-This makes PhantomJS highly popular for Continuous Integration systems.
-PhantomJS needs only one binary with no extra dependencies which make it the simplest WebDriver tool to install.
-
-However, it should be noted that PhantomJS is not a real browser, so the behavior and output in real browsers may differ from PhantomJS.
-And the most important: **PhantomJS is not maintained** anymore. So use it at your own risk.
-
-* Follow [Installation Instructions](http://codeception.com/docs/modules/WebDriver#PhantomJS)
-* Enable [RunProcess](http://codeception.com/extensions#RunProcess) extension to start/stop PhantomJS automatically *(optional)*.
-
#### ChromeDriver
ChromeDriver was created by Google to control Chrome and Chromium browsers programmatically.
@@ -425,8 +449,6 @@ $I->wait(3); // wait for 3 secs
#### SmartWait
-*since 2.3.4 version*
-
It is possible to wait for elements pragmatically.
If a test uses element which is not on a page yet, Codeception will wait for few extra seconds before failing.
This feature is based on [Implicit Wait](http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits) of Selenium.
@@ -469,6 +491,57 @@ $I->dontSeeElement('#login'); // DISABLED, can't wait for element to hide
$I->seeNumberOfElements(['css' => 'button.link'], 5); // DISABLED, can wait only for one element
```
+#### Retry
+
+When it's hard to define condition to wait for, we can retry a command few times until it succeeds.
+For instance, if you try to click while it's animating you can try to do it few times until it freezes.
+Since Codeception 3.0 each action and assertion have an alias prefixed with `retry` which allows to retry a flaky command.
+
+```php
+retryClick('flaky element');
+$I->retrySee('Something changed');
+```
+
+Retry can be configured via `$I->retry()` command, where you can set number of retries and initial interval:
+interval will be doubled on each unsuccessful execution.
+
+```php
+ 400ms + 800ms + 1600ms + 3200ms = 6000ms
+$I->retry(4, 400);
+```
+
+`$I->retry` takes 2 parameters:
+* number of retries (1 by default)
+* initial interval (200ms by default)
+
+Retries are disabled by default. To enable them you should add retry step decorators to suite config:
+
+> If you started project as `codecept init acceptance` they should be already enabled in config
+
+```yaml
+# in acceptance.suite.yml
+# or in codeception.yml inside suites section
+step_decorators:
+ - \Codeception\Step\Retry
+```
+
+Then add `\Codeception\Lib\Actor\Shared\Retry` trait into `AcceptanceTester` class:
+
+```php
+performOn('.confirm', function(\Codeception\Module\WebDriver $I) {
For more options see [`performOn()` reference](http://codeception.com/docs/modules/WebDriver#performOn).
+#### A/B Testing
+
+When a web site acts unpredictably you may need to react on that change.
+This happens if site configured for A/B testing, or shows different popups, based on environment.
+
+Since Codeception 3.0 you can have some actions to fail silently, is they are errored.
+Let's say, you open a page and some times there is a popup which should be closed.
+We may try to hit the "close" button but if this action fails (no popup on page) we just continue the test.
+
+This is how it can be implemented:
+
+```php
+amOnPage('/');
+$I->tryToClick('x', '.alert');
+// continue execution
+```
+
+You can also use `tryTo` as condition for your tests:
+
+```php
+tryToSeeElement('.alert')) {
+ $I->waitForText('Do you accept cookies?');
+ $I->click('Yes');
+}
+```
+
+A/B testing is disabled by default. To enable it you should add corresponding step decorators to suite config:
+
+> If you started project as `codecept init acceptance` in Codeception >= 3.0 they should be already enabled in config
+
+```yaml
+# in acceptance.suite.yml
+# or in codeception.yml inside suites section
+step_decorators:
+ - \Codeception\Step\TryTo
+```
+
+Then rebuild actors with `codecept build` command.
+
### Multi Session Testing
Codeception allows you to execute actions in concurrent sessions. The most obvious case for this
@@ -529,6 +643,18 @@ $nickAdmin->does(function(adminStep $I) {
$nickAdmin->leave();
```
+Multi session testing is disabled by default. To enable it, add `\Codeception\Lib\Actor\Shared\Friend` into `AcceptancTester`.
+
+```php
+amOnPage('/login');
+ $I->submitForm('#loginForm', [
+ 'login' => $name,
+ 'password' => $password
+ ]);
+ $I->see($name, '.navbar');
+ }
+}
+```
+
+Now you can use the `login` method inside your tests:
+
+```php
+login('miles', '123456');
+```
+
+However, implementing all actions for reuse in a single actor class may lead to
+breaking the [Single Responsibility Principle](http://en.wikipedia.org/wiki/Single_responsibility_principle).
+
+#### Single Login
+
+If you need to authorize a user for each test, you can do so by submitting the login form at the beginning of every test.
+Running those steps takes time, and in the case of Selenium tests (which are slow by themselves)
+that time loss can become significant.
+
+Codeception allows you to share cookies between tests, so a test user can stay logged in for other tests.
+
+Let's improve the code of our `login` method, executing the form submission only once
+and restoring the session from cookies for each subsequent login function call:
+
+``` php
+loadSessionSnapshot('login')) {
+ return;
+ }
+ // logging in
+ $I->amOnPage('/login');
+ $I->submitForm('#loginForm', [
+ 'login' => $name,
+ 'password' => $password
+ ]);
+ $I->see($name, '.navbar');
+ // saving snapshot
+ $I->saveSessionSnapshot('login');
+ }
+```
+
+Note that session restoration only works for `WebDriver` modules
+(modules implementing `Codeception\Lib\Interfaces\SessionSnapshot`).
+
### Custom Browser Sessions
diff --git a/docs/04-FunctionalTests.md b/docs/04-FunctionalTests.md
index 5e0f40f071..258afc65b6 100644
--- a/docs/04-FunctionalTests.md
+++ b/docs/04-FunctionalTests.md
@@ -18,7 +18,7 @@ This is a sample functional test:
class LoginCest
{
- public function tryLogin (FunctionalTester $I)
+ public function tryLogin(FunctionalTester $I)
{
$I->amOnPage('/');
$I->click('Login');
@@ -26,7 +26,7 @@ class LoginCest
$I->fillField('Password', 'Davis');
$I->click('Enter');
$I->see('Hello, Miles', 'h1');
- // $I->seeEmailIsSent(); // only for Symfony2
+ // $I->seeEmailIsSent(); // only for Symfony
}
}
```
@@ -77,7 +77,6 @@ modules:
- Symfony
- Doctrine2:
depends: Symfony # connect to Symfony
- - \Helper\Functional
```
By default this module will search for AppKernel in the `app` directory.
@@ -97,7 +96,6 @@ actor: FunctionalTester
modules:
enabled:
- Laravel5
- - \Helper\Functional
```
### Yii2
@@ -105,25 +103,6 @@ modules:
Yii2 tests are included in [Basic](https://github.com/yiisoft/yii2-app-basic)
and [Advanced](https://github.com/yiisoft/yii2-app-advanced) application templates. Follow the Yii2 guides to start.
-### Yii
-
-By itself Yii framework does not have an engine for functional testing.
-So Codeception is the first and the only functional testing framework for Yii.
-To use it with Yii include `Yii1` module into config:
-
-```yaml
-# functional.suite.yml
-
-actor: FunctionalTester
-modules:
- enabled:
- - Yii1
- - \Helper\Functional
-```
-
-To avoid the common pitfalls we discussed earlier, Codeception provides basic hooks over the Yii engine.
-Please set them up following [the installation steps in the module reference](http://codeception.com/docs/modules/Yii1).
-
### Zend Framework 2
Use [the ZF2 module](http://codeception.com/docs/modules/ZF2) to run functional tests inside Zend Framework 2:
@@ -135,14 +114,11 @@ actor: FunctionalTester
modules:
enabled:
- ZF2
- - \Helper\Functional
```
-### Zend Framework 1.x
+### Zend Expressive
-The module for Zend Framework is highly inspired by the ControllerTestCase class, used for functional testing with PHPUnit.
-It follows similar approaches for bootstrapping and cleaning up.
-To start using Zend Framework in your functional tests, include the `ZF1` module:
+[Zend Expressive](http://codeception.com/docs/modules/ZendExpressive) tests can be executed with enabling a corresponding module.
```yaml
# functional.suite.yml
@@ -150,11 +126,10 @@ To start using Zend Framework in your functional tests, include the `ZF1` module
actor: FunctionalTester
modules:
enabled:
- - ZF1
- - \Helper\Functional
+ - ZendExpressive
```
-[See the full reference](http://codeception.com/docs/modules/ZF1)
+> See module reference to more configuration options
### Phalcon
@@ -172,7 +147,6 @@ modules:
bootstrap: 'app/config/bootstrap.php'
cleanup: true
savepoints: true
- - \Helper\Functional
```
[See the full reference](http://codeception.com/docs/modules/Phalcon)
@@ -187,7 +161,6 @@ Therefore we can open a web page with `amOnPage` method:
```php
amOnPage('/login');
```
@@ -259,7 +232,7 @@ The error reporting level can be set in the suite configuration file:
actor: FunctionalTester
modules:
enabled:
- - Yii1
+ - Yii2
- \Helper\Functional
error_level: "E_ALL & ~E_STRICT & ~E_DEPRECATED"
```
diff --git a/docs/05-UnitTests.md b/docs/05-UnitTests.md
index cf42a962cd..3d02fd3ed0 100644
--- a/docs/05-UnitTests.md
+++ b/docs/05-UnitTests.md
@@ -9,7 +9,7 @@ Codeception adds some nice helpers to simplify common tasks.
Create a test using `generate:test` command with a suite and test names as parameters:
```bash
-php codecept generate:test unit Example
+php vendor/bin/codecept generate:test unit Example
```
It creates a new `ExampleTest` file located in the `tests/unit` directory.
@@ -17,13 +17,13 @@ It creates a new `ExampleTest` file located in the `tests/unit` directory.
As always, you can run the newly created test with this command:
```bash
-php codecept run unit ExampleTest
+php vendor/bin/codecept run unit ExampleTest
```
Or simply run the whole set of unit tests with:
```bash
-php codecept run unit
+php vendor/bin/codecept run unit
```
A test created by the `generate:test` command will look like this:
@@ -73,15 +73,15 @@ class UserTest extends \Codeception\Test\Unit
{
public function testValidation()
{
- $user = User::create();
+ $user = new User();
- $user->username = null;
+ $user->setName(null);
$this->assertFalse($user->validate(['username']));
- $user->username = 'toolooooongnaaaaaaameeee';
+ $user->setName('toolooooongnaaaaaaameeee');
$this->assertFalse($user->validate(['username']));
- $user->username = 'davert';
+ $user->setName('davert');
$this->assertTrue($user->validate(['username']));
}
}
@@ -156,7 +156,7 @@ In this
\Codeception\Stub::make(UserRepository::class, ['find' => new User]);
```
-See a reference for static Stub API
+See a reference for [static Stub API](http://codeception.com/docs/reference/Stub)
#### Mocks
@@ -459,7 +459,7 @@ public function testSingleton()
test::double('MySingleton', ['getInstance' => new DOMDocument]);
$this->assertInstanceOf('DOMDocument', $class);
}
-```
+```
* [AspectMock on GitHub](https://github.com/Codeception/AspectMock)
* [AspectMock in Action](http://codeception.com/07-31-2013/nothing-is-untestable-aspect-mock.html)
@@ -468,6 +468,6 @@ public function testSingleton()
## Conclusion
PHPUnit tests are first-class citizens in test suites. Whenever you need to write and execute unit tests,
-you don't need to install PHPUnit seperately, but use Codeception directly to execute them.
+you don't need to install PHPUnit separately, but use Codeception directly to execute them.
Some nice features can be added to common unit tests by integrating Codeception modules.
For most unit and integration testing, PHPUnit tests are enough. They run fast, and are easy to maintain.
diff --git a/docs/06-ModulesAndHelpers.md b/docs/06-ModulesAndHelpers.md
index 052a244034..2a91f88105 100644
--- a/docs/06-ModulesAndHelpers.md
+++ b/docs/06-ModulesAndHelpers.md
@@ -35,7 +35,7 @@ modules:
The FunctionalTester class has its methods defined in modules. Actually, it doesn't contain any of them,
but rather acts as a proxy. It knows which module executes this action and passes parameters into it.
-To make your IDE see all of the FunctionalTester methods, you should run use the `codecept build` command.
+To make your IDE see all of the FunctionalTester methods, you should run the `codecept build` command.
It generates method signatures from enabled modules and saves them into a trait which is included in an actor.
In the current example, the `tests/support/_generated/FunctionalTesterActions.php` file will be generated.
By default, Codeception automatically rebuilds the Actions trait on each change of the suite configuration.
@@ -357,6 +357,8 @@ Here is how it is done in the Db module:
class Db extends \Codeception\Module
{
protected $requiredFields = ['dsn', 'user', 'password'];
+ // ...
+}
```
The next time you start the suite without setting one of these values, an exception will be thrown.
@@ -370,6 +372,8 @@ class WebDriver extends \Codeception\Module
{
protected $requiredFields = ['browser', 'url'];
protected $config = ['host' => '127.0.0.1', 'port' => '4444'];
+ // ...
+}
```
The host and port parameter can be redefined in the suite configuration.
@@ -449,7 +453,7 @@ module:
- Db:
dsn: "mysql:host=%DB_HOST%;dbname=%DB_DATABASE%"
user: "%DB_USERNAME%"
- password: "DB_PASSWORD"
+ password: "%DB_PASSWORD%"
```
### Runtime Configuration
@@ -515,4 +519,4 @@ Modules are the real power of Codeception. They are used to emulate multiple inh
(UnitTester, FunctionalTester, AcceptanceTester, etc). Codeception provides modules to emulate web requests,
access data, interact with popular PHP libraries, etc. If the bundled modules are not enough for you that's OK,
you are free to write your own! Use Helpers (custom modules) for everything that Codeception can't do out of the box.
-Helpers also can be used to extend the functionality of the original modules.
\ No newline at end of file
+Helpers also can be used to extend the functionality of the original modules.
diff --git a/docs/06-ReusingTestCode.md b/docs/06-ReusingTestCode.md
index ba076e333c..b00d643943 100644
--- a/docs/06-ReusingTestCode.md
+++ b/docs/06-ReusingTestCode.md
@@ -12,7 +12,6 @@ We will get back to this later in this chapter, but for now let's look at the fo
```php
amOnPage('/');
$I->see('Hello');
$I->seeInDatabase('users', ['id' => 1]);
@@ -69,76 +68,108 @@ The most important part is the `_generated\AcceptanceTesterActions` trait, which
It knows which module executes which action and passes parameters into it.
This trait was created by running `codecept build` and is regenerated each time module or configuration changes.
-### Authorization
+> Use actor classes to set common actions which can be used accross a suite.
-It is recommended to put widely used actions inside an Actor class. A good example is the `login` action
-which would probably be actively involved in acceptance or functional testing:
-``` php
+## PageObjects
+
+For acceptance and functional testing, we will not only need to have common actions being reused across different tests,
+we should have buttons, links and form fields being reused as well. For those cases we need to implement
+the [PageObject pattern](http://docs.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern),
+which is widely used by test automation engineers. The PageObject pattern represents a web page as a class
+and the DOM elements on that page as its properties, and some basic interactions as its methods.
+PageObjects are very important when you are developing a flexible architecture of your acceptance or functional tests.
+Do not hard-code complex CSS or XPath locators in your tests but rather move them into PageObject classes.
+
+Codeception can generate a PageObject class for you with command:
+
+```bash
+php vendor/bin/codecept generate:pageobject acceptance Login
+```
+
+> It is recommended to use page objects for acceptance testing only
+
+This will create a `Login` class in `tests/_support/Page/Acceptance`.
+The basic PageObject is nothing more than an empty class with a few stubs.
+
+It is expected that you will populate it with the UI locators of a page it represents. Locators can be added as public properties:
+
+```php
amOnPage('/login');
- $I->submitForm('#loginForm', [
- 'login' => $name,
- 'password' => $password
- ]);
- $I->see($name, '.navbar');
- }
+ public $usernameField = '#mainForm #username';
+ public $passwordField = '#mainForm input[name=password]';
+ public $loginButton = '#mainForm input[type=submit]';
+
+ // ...
}
```
-Now you can use the `login` method inside your tests:
+But let's move further. The PageObject concept specifies that the methods for the page interaction should also be stored in a PageObject class.
+
+Let's define a `login` method in this class:
```php
login('miles', '123456');
-```
+namespace Page\Acceptance;
-However, implementing all actions for reuse in a single actor class may lead to
-breaking the [Single Responsibility Principle](http://en.wikipedia.org/wiki/Single_responsibility_principle).
+class Login
+{
+ public static $URL = '/login';
-### Session Snapshot
+ public $usernameField = '#mainForm #username';
+ public $passwordField = '#mainForm input[name=password]';
+ public $loginButton = '#mainForm input[type=submit]';
-If you need to authorize a user for each test, you can do so by submiting the login form at the beginning of every test.
-Running those steps takes time, and in the case of Selenium tests (which are slow by themselves)
-that time loss can become significant.
+ /**
+ * @var AcceptanceTester
+ */
+ protected $tester;
-Codeception allows you to share cookies between tests, so a test user can stay logged in for other tests.
+ // we inject AcceptanceTester into our class
+ public function __construct(\AcceptanceTester $I)
+ {
+ $this->tester = $I;
+ }
+
+ public function login($name, $password)
+ {
+ $I = $this->tester;
+
+ $I->amOnPage(self::$URL);
+ $I->fillField($this->usernameField, $name);
+ $I->fillField($this->passwordField, $password);
+ $I->click($this->loginButton);
+ }
+}
+```
-Let's improve the code of our `login` method, executing the form submission only once
-and restoring the session from cookies for each subsequent login function call:
+If you specify which object you need for a test, Codeception will try to create it using the dependency injection container.
+In the case of a PageObject you should declare a class as a parameter for a test method:
-``` php
+```php
loadSessionSnapshot('login')) {
- return;
- }
- // logging in
- $I->amOnPage('/login');
- $I->submitForm('#loginForm', [
- 'login' => $name,
- 'password' => $password
- ]);
- $I->see($name, '.navbar');
- // saving snapshot
- $I->saveSessionSnapshot('login');
+ $loginPage->login('bill evans', 'debby');
+ $I->amOnPage('/profile');
+ $I->see('Bill Evans Profile', 'h1');
}
+}
```
-Note that session restoration only works for `WebDriver` modules
-(modules implementing `Codeception\Lib\Interfaces\SessionSnapshot`).
+The dependency injection container can construct any object that requires any known class type.
+For instance, `Page\Login` required `AcceptanceTester`, and so it was injected into `Page\Login` constructor,
+and PageObject was created and passed into method arguments. You should explicitly specify
+the types of required objects for Codeception to know what objects should be created for a test.
+Dependency Injection will be described in the next chapter.
## StepObjects
@@ -150,14 +181,14 @@ We call such a classes StepObjects.
Lets create an Admin StepObject with the generator:
```bash
-php codecept generate:stepobject acceptance Admin
+php vendor/bin/codecept generate:stepobject acceptance Admin
```
You can supply optional action names. Enter one at a time, followed by a newline.
End with an empty line to continue to StepObject creation.
```bash
-php codecept generate:stepobject acceptance Admin
+php vendor/bin/codecept generate:stepobject acceptance Admin
Add action to StepObject class (ENTER to exit): loginAsAdmin
Add action to StepObject class (ENTER to exit):
StepObject was created in /tests/acceptance/_support/Step/Acceptance/Admin.php
@@ -200,18 +231,8 @@ class Admin extends \AcceptanceTester
}
```
-In tests, you can use a StepObject by instantiating `Step\Acceptance\Admin` instead of `AcceptanceTester`:
-```php
-loginAsAdmin();
-```
-
-The same way as above, a StepObject can be instantiated automatically by the Dependency Injection Container
-when used inside the Cest format:
+StepObject can be instantiated automatically when used inside the Cest format:
```php
wantTo('login to site');
-$I->amOnPage(LoginPage::$URL);
-$I->fillField(LoginPage::$usernameField, 'bill evans');
-$I->fillField(LoginPage::$passwordField, 'debby');
-$I->click(LoginPage::$loginButton);
-$I->see('Welcome, bill');
-```
-
-As you see, you can freely change markup of your login page, and all the tests interacting with this page
-will have their locators updated according to properties of LoginPage class.
-
-But let's move further. The PageObject concept specifies that the methods for the page interaction
-should also be stored in a PageObject class. It now stores a passed instance of an Actor class.
-An AcceptanceTester can be accessed via the `AcceptanceTester` property of that class.
-Let's define a `login` method in this class:
-
-```php
- Use StepObjects when you have multiple areas of applications or multiple roles.
- public function __construct(\AcceptanceTester $I)
- {
- $this->tester = $I;
- }
-
- public function login($name, $password)
- {
- $I = $this->tester;
-
- $I->amOnPage(self::$URL);
- $I->fillField(self::$usernameField, $name);
- $I->fillField(self::$passwordField, $password);
- $I->click(self::$loginButton);
-
- return $this;
- }
-}
-```
-
-And here is an example of how this PageObject can be used in a test:
-
-```php
-login('bill evans', 'debby');
-$I->amOnPage('/profile');
-$I->see('Bill Evans Profile', 'h1');
-```
-
-If you write your scenario-driven tests in the Cest format (which is the recommended approach),
-you can bypass the manual creation of a PageObject and delegate this task to Codeception.
-If you specify which object you need for a test, Codeception will try to create it using the dependency injection container.
-In the case of a PageObject you should declare a class as a parameter for a test method:
-
-```php
-login('bill evans', 'debby');
- $I->amOnPage('/profile');
- $I->see('Bill Evans Profile', 'h1');
- }
-}
-```
-
-The dependency injection container can construct any object that requires any known class type.
-For instance, `Page\Login` required `AcceptanceTester`, and so it was injected into `Page\Login` constructor,
-and PageObject was created and passed into method arguments. You should explicitly specify
-the types of required objects for Codeception to know what objects should be created for a test.
-Dependency Injection will be described in the next chapter.
## Conclusion
diff --git a/docs/07-AdvancedUsage.md b/docs/07-AdvancedUsage.md
index 7bc966f030..f970022308 100644
--- a/docs/07-AdvancedUsage.md
+++ b/docs/07-AdvancedUsage.md
@@ -12,7 +12,7 @@ and you want to split it, you can easily move it into classes.
You can create a Cest file by running the command:
```bash
-$ php codecept generate:cest suitename CestName
+php vendor/bin/codecept generate:cest suitename CestName
```
The generated file will look like this:
@@ -40,7 +40,6 @@ class BasicCest
and will receive an instance of the Actor class as the first parameter and the `$scenario` variable as the second one.
In `_before` and `_after` methods you can use common setups and teardowns for the tests in the class.
-This actually makes Cest tests more flexible than Cepts, which rely only on similar methods in Helper classes.
As you see, we are passing the Actor object into `tryToTest` method. This allows us to write scenarios the way we did before:
@@ -51,7 +50,6 @@ class BasicCest
// test
public function tryToTest(\AcceptanceTester $I)
{
- $I->wantTo('log in to site');
$I->amOnPage('/');
$I->click('Login');
$I->fillField('username', 'john');
@@ -63,11 +61,27 @@ class BasicCest
}
```
-As you see, Cest classes have no parents like `\Codeception\Test\Unit` or `PHPUnit_Framework_TestCase`.
+As you see, Cest classes have no parents.
This is done intentionally. It allows you to extend your classes with common behaviors and workarounds
that may be used in child classes. But don't forget to make these methods `protected` so they won't be executed as tests.
-You can also define a `_failed` method in Cest classes which will be called if test finishes with `error` or fails.
+Cest format also can contain hooks based on test results:
+
+* `_failed` will be executed on failed test
+* `_passed` will be executed on passed test
+
+```php
+wantTo('sign up');
-
$this->navBar->click('Sign up');
$this->signUp->register([
'first_name' => 'Joe',
@@ -170,7 +182,7 @@ Moreover, Codeception can resolve dependencies recursively (when `A` depends on
and handle parameters of primitive types with default values (like `$param = 'default'`).
Of course, you are not allowed to have *cyclic dependencies*.
-### Examples
+## Example Annotation
What if you want to execute the same test scenario with different data? In this case you can inject examples
as `\Codeception\Example` instances.
@@ -178,6 +190,8 @@ Data is defined via the `@example` annotation, using JSON or Doctrine-style nota
```php
sendGET($example[0]);
$I->seeResponseCodeIs($example[1]);
}
+}
```
JSON:
```php
see($example['title'], 'h1');
$I->seeInTitle($example['title']);
}
+}
```
@@ -218,6 +236,8 @@ Key-value data in Doctrine-style annotation syntax:
```php
see($example['title'], 'h1');
$I->seeInTitle($example['title']);
}
+}
```
-You can also use the `@dataprovider` annotation for creating dynamic examples, using a protected method for providing example data:
+## DataProvider Annotations
+
+You can also use the `@dataProvider` annotation for creating dynamic examples for [Cest classes](#Cest-Classes), using a **protected method** for providing example data:
```php
"/contact", 'title'=>"Contact Us"]
];
}
+}
```
-### Before/After Annotations
+`@dataprovider` annotation is also available for [unit tests](https://codeception.com/docs/05-UnitTests), in this case the data provider **method must be public**.
+For more details about how to use data provider for unit tests, please refer to [PHPUnit documentation](https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers).
+
+## Before/After Annotations
You can control execution flow with `@before` and `@after` annotations. You may move common actions
into protected (non-test) methods and invoke them before or after the test method by putting them into annotations.
@@ -358,7 +387,7 @@ The names of these files are used as environments names
You can generate a new file with this environment configuration by using the `generate:environment` command:
```bash
-$ php codecept g:env chrome
+$ php vendor/bin/codecept g:env chrome
```
In that file you can specify just the options you wish to override:
@@ -376,13 +405,13 @@ You can easily switch between those configs by running tests with `--env` option
To run the tests only for PhantomJS you just need to pass `--env phantom` as an option:
```bash
-$ php codecept run acceptance --env phantom
+$ php vendor/bin/codecept run acceptance --env phantom
```
To run the tests in all 3 browsers, list all the environments:
```bash
-$ php codecept run acceptance --env phantom --env chrome --env firefox
+$ php vendor/bin/codecept run acceptance --env phantom --env chrome --env firefox
```
The tests will be executed 3 times, each time in a different browser.
@@ -390,7 +419,7 @@ The tests will be executed 3 times, each time in a different browser.
It's also possible to merge multiple environments into a single configuration by separating them with a comma:
```bash
-$ php codecept run acceptance --env dev,phantom --env dev,chrome --env dev,firefox
+$ php vendor/bin/codecept run acceptance --env dev,phantom --env dev,chrome --env dev,firefox
```
The configuration is merged in the order given.
@@ -468,7 +497,7 @@ public function myTest(\AcceptanceTester $I, \Codeception\Scenario $scenario)
}
```
-`Codeception\Scenario` is also availble in Actor classes and StepObjects. You can access it with `$this->getScenario()`.
+`Codeception\Scenario` is also available in Actor classes and StepObjects. You can access it with `$this->getScenario()`.
### Dependencies
@@ -505,25 +534,39 @@ Signature: ModeratorCest:login`
Codeception reorders tests so dependent tests will always be executed before the tests that rely on them.
-## Interactive Console
+### Shuffle
+
+By default Codeception runs tests in alphabetic order.
+To ensure that tests are not depending on each other (unless explicitly declared via `@depends`) you can enable `shuffle` option.
+
+```yaml
+# inside codeception.yml
+settings:
+ shuffle: true
+```
+
+Alternatively, you may run tests in shuffle without changing the config:
-The interactive console was added to try Codeception commands before executing them inside a test.
+```
+codecept run -o "settings: shuffle: true"
+```
-
-You can run the console with the following command:
+Tests will be randomly reordered on each run. When tests executed in shuffle mode a seed value will be printed.
+Copy this seed value from output to be able to rerun tests in the same order.
-``` bash
-$ php codecept console suitename
+```
+$ codecept run
+Codeception PHP Testing Framework v2.4.5
+Powered by PHPUnit 5.7.27 by Sebastian Bergmann and contributors.
+[Seed] 1872290562
```
-Now you can execute all the commands of an appropriate Actor class and see the results immediately.
-This is especially useful when used with the `WebDriver` module. It always takes too long to launch Selenium
-and the browser for tests. But with the console you can try different selectors, and different commands,
-and then write a test that should pass when executed.
+Pass the copied seed into `--seed` option:
-And a special hint: show your boss how you can easily manipulate web pages with the console and Selenium.
-It will be easy to convince them to automate this step and introduce acceptance testing to the project.
+```
+codecept run --seed 1872290562
+```
## Running from different folders
@@ -531,15 +574,15 @@ If you have several projects with Codeception tests, you can use a single `codec
You can pass the `-c` option to any Codeception command (except `bootstrap`), to execute Codeception in another directory:
```bash
-$ php codecept run -c ~/projects/ecommerce/
-$ php codecept run -c ~/projects/drupal/
-$ php codecept generate:cept acceptance CreateArticle -c ~/projects/drupal/
+$ php vendor/bin/codecept run -c ~/projects/ecommerce/
+$ php vendor/bin/codecept run -c ~/projects/drupal/
+$ php vendor/bin/codecept generate:cept acceptance CreateArticle -c ~/projects/drupal/
```
To create a project in directory different from the current one, just provide its path as a parameter:
```bash
-$ php codecept bootstrap ~/projects/drupal/
+$ php vendor/bin/codecept bootstrap ~/projects/drupal/
```
Also, the `-c` option allows you to specify another config file to be used.
@@ -551,13 +594,13 @@ and settings). Just pass the `.yml` filename as the `-c` parameter to execute te
There are several ways to execute a bunch of tests. You can run tests from a specific directory:
```bash
-$ php codecept run tests/acceptance/admin
+$ php vendor/bin/codecept run tests/acceptance/admin
```
You can execute one (or several) specific groups of tests:
```bash
-$ php codecept run -g admin -g editor
+$ php vendor/bin/codecept run -g admin -g editor
```
The concept of groups was taken from PHPUnit and behave in the same way.
@@ -632,9 +675,53 @@ groups:
This will load all found `p*` files in `tests/_data` as groups. Group names will be as follows p1,p2,...,pN.
-## Shell autocompletion
+## Formats
+
+In addition to the standard test formats (Cept, Cest, Unit, Gherkin) you can implement your own format classes to customise your test execution.
+Specify these in your suite configuration:
+
+```yaml
+formats:
+ - \My\Namespace\MyFormat
+```
+
+Then define a class which implements the LoaderInterface
+
+```php
+namespace My\Namespace;
+
+class MyFormat implements \Codeception\Test\Loader\LoaderInterface
+{
+ protected $tests;
+
+ protected $settings;
+
+ public function __construct($settings = [])
+ {
+ //These are the suite settings
+ $this->settings = $settings;
+ }
+
+ public function loadTests($filename)
+ {
+ //Load file and create tests
+ }
+
+ public function getTests()
+ {
+ return $this->tests;
+ }
+
+ public function getPattern()
+ {
+ return '~Myformat\.php$~';
+ }
+}
+```
+
+## Shell auto-completion
-For bash and zsh shells, you can use autocompletion for your Codeception projects by executing the following in your shell (or add it to your .bashrc/.zshrc):
+For bash and zsh shells, you can use auto-completion for your Codeception projects by executing the following in your shell (or add it to your .bashrc/.zshrc):
```bash
# BASH ~4.x, ZSH
source <([codecept location] _completion --generate-hook --program codecept --use-vendor-bin)
diff --git a/docs/07-BDD.md b/docs/07-BDD.md
index 1c05986f37..c9aacf8da8 100644
--- a/docs/07-BDD.md
+++ b/docs/07-BDD.md
@@ -81,7 +81,7 @@ Feature file is written in Gherkin format. Codeception can generate a feature fi
We will assume that we will use scenarios in feature files for acceptance tests, so feature files to be placed in `acceptance` suite directory:
```bash
-php codecept g:feature acceptance checkout
+php vendor/bin/codecept g:feature acceptance checkout
```
Generated template will look like this:
@@ -200,7 +200,9 @@ Please note that regular expressions should start and end with `/` char. Regex i
```php
-Wildcards (*) can be used to specify multiple directories at once.
-
-
### Namespaces
To avoid naming conflicts between Actor classes and Helper classes, they should be separated into namespaces.
To create test suites with namespaces you can add `--namespace` option to the bootstrap command:
```bash
-php codecept bootstrap --namespace frontend
+php vendor/bin/codecept bootstrap --namespace frontend
```
This will bootstrap a new project with the `namespace: frontend` parameter in the `codeception.yml` file.
@@ -44,7 +18,7 @@ Once each of your applications (bundles) has its own namespace and different Hel
you can execute all the tests in a single runner. Run the Codeception tests as usual, using the meta-config we created earlier:
```bash
-php codecept run
+php vendor/bin/codecept run
```
This will launch the test suites for all three applications and merge the reports from all of them.
@@ -54,13 +28,53 @@ and you want to get a single report in JUnit and HTML format. The code coverage
If you want to run a specific suite from the application you can execute:
```
-php codecept run unit -c frontend
+php vendor/bin/codecept run unit -c frontend
```
Where `unit` is the name of suite and the `-c` option specifies the path to the `codeception.yml` configuration file to use.
In this example we will assume that there is `frontend/codeception.yml` configuration file
and that we will execute the unit tests for only that app.
+## Bootstrap
+
+To prepare environment for testing you can execute custom PHP script before all tests or just before a specific suite.
+This way you can initialize autoloader, check availability of a website, etc.
+
+### Global Bootstrap
+
+To run bootstrap script before all suites place it in `tests` directory (absolute paths supported as well).
+Then set a `bootstrap` config key in `codeception.yml`:
+
+```yml
+# file will be loaded from tests/bootstrap.php
+bootstrap: bootstrap.php
+```
+
+### Suite Bootstrap
+
+To run a script for a specific suite, place it into the suite directory and add to suite config:
+
+```yml
+# inside .suite.yml
+# file will be loaded from tests//bootstrap.php
+bootstrap: bootstrap.php
+```
+
+### On Fly Bootstrap
+
+Bootstrap script can be executed with `--bootstrap` option for `codecept run` command:
+
+```
+php vendor/bin/codecept run --bootstrap bootstrap.php
+```
+
+In this case, bootstrap script will be executed before the Codeception is initialized.
+Bootstrap script should be located in current working directory or by an absolute path.
+
+> Bootstrap is a classical way to run custom PHP code before your tests.
+However, we recommend you to use Extensions instead of bootstrap scripts for better flexibility.
+If you need configuration, conditional enabling or disabling bootstrap script, extensions should work for you better.
+
## Extension
Codeception has limited capabilities to extend its core features.
@@ -71,7 +85,7 @@ By default, one `RunFailed` Extension is already enabled in your global `codecep
It allows you to rerun failed tests by using the `-g failed` option:
```
-php codecept run -g failed
+php vendor/bin/codecept run -g failed
```
Codeception comes with bundled extensions located in `ext` directory.
@@ -95,29 +109,29 @@ All listed events are available as constants in `Codeception\Events` class.
| Event | When? | Triggered by
|:--------------------:| --------------------------------------- | --------------------------:
-| `suite.before` | Before suite is executed | [Suite, Settings](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/SuiteEvent.php)
-| `test.start` | Before test is executed | [Test](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/TestEvent.php)
-| `test.before` | At the very beginning of test execution | [Codeception Test](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/TestEvent.php)
-| `step.before` | Before step | [Step](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/StepEvent.php)
-| `step.after` | After step | [Step](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/StepEvent.php)
-| `step.fail` | After failed step | [Step](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/StepEvent.php)
-| `test.fail` | After failed test | [Test, Fail](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/FailEvent.php)
-| `test.error` | After test ended with error | [Test, Fail](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/FailEvent.php)
-| `test.incomplete` | After executing incomplete test | [Test, Fail](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/FailEvent.php)
-| `test.skipped` | After executing skipped test | [Test, Fail](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/FailEvent.php)
-| `test.success` | After executing successful test | [Test](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/TestEvent.php)
-| `test.after` | At the end of test execution | [Codeception Test](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/TestEvent.php)
-| `test.end` | After test execution | [Test](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/TestEvent.php)
-| `suite.after` | After suite was executed | [Suite, Result, Settings](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/SuiteEvent.php)
-| `test.fail.print` | When test fails are printed | [Test, Fail](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/FailEvent.php)
-| `result.print.after` | After result was printed | [Result, Printer](https://github.com/Codeception/Codeception/blob/master/src/Codeception/Event/PrintResultEvent.php)
+| `suite.before` | Before suite is executed | [Suite, Settings](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/SuiteEvent.php)
+| `test.start` | Before test is executed | [Test](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/TestEvent.php)
+| `test.before` | At the very beginning of test execution | [Codeception Test](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/TestEvent.php)
+| `step.before` | Before step | [Step](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/StepEvent.php)
+| `step.after` | After step | [Step](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/StepEvent.php)
+| `step.fail` | After failed step | [Step](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/StepEvent.php)
+| `test.fail` | After failed test | [Test, Fail](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/FailEvent.php)
+| `test.error` | After test ended with error | [Test, Fail](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/FailEvent.php)
+| `test.incomplete` | After executing incomplete test | [Test, Fail](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/FailEvent.php)
+| `test.skipped` | After executing skipped test | [Test, Fail](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/FailEvent.php)
+| `test.success` | After executing successful test | [Test](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/TestEvent.php)
+| `test.after` | At the end of test execution | [Codeception Test](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/TestEvent.php)
+| `test.end` | After test execution | [Test](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/TestEvent.php)
+| `suite.after` | After suite was executed | [Suite, Result, Settings](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/SuiteEvent.php)
+| `test.fail.print` | When test fails are printed | [Test, Fail](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/FailEvent.php)
+| `result.print.after` | After result was printed | [Result, Printer](https://github.com/Codeception/Codeception/blob/2.4/src/Codeception/Event/PrintResultEvent.php)
There may be some confusion between `test.start`/`test.before` and `test.after`/`test.end`.
The start and end events are triggered by PHPUnit, but the before and after events are triggered by Codeception.
Thus, when you are using classical PHPUnit tests (extended from `PHPUnit\Framework\TestCase`),
the before/after events won't be triggered for them. During the `test.before` event you can mark a test
as skipped or incomplete, which is not possible in `test.start`. You can learn more from
-[Codeception internal event listeners](https://github.com/Codeception/Codeception/tree/master/src/Codeception/Subscriber).
+[Codeception internal event listeners](https://github.com/Codeception/Codeception/tree/2.4/src/Codeception/Subscriber).
The extension class itself is inherited from `Codeception\Extension`:
@@ -132,7 +146,7 @@ class MyCustomExtension extends \Codeception\Extension
public static $events = array(
Events::SUITE_AFTER => 'afterSuite',
- Events::SUITE_BEFORE => 'beforeTest',
+ Events::TEST_BEFORE => 'beforeTest',
Events::STEP_BEFORE => 'beforeStep',
Events::TEST_FAIL => 'testFailed',
Events::RESULT_PRINT_AFTER => 'print',
@@ -180,15 +194,15 @@ To enable extension dynamically, execute the `run` command with `--ext` option.
Provide a class name as a parameter:
```bash
-codecept run --ext MyCustomExtension
-codecept run --ext "\My\Extension"
+php vendor/bin/codecept run --ext MyCustomExtension
+php vendor/bin/codecept run --ext "\My\Extension"
```
If a class is in a `Codeception\Extension` namespace you can skip it and provide only a shortname.
So Recorder extension can be started like this:
```bash
-codecept run --ext Recorder
+php vendor/bin/codecept run --ext Recorder
```
### Configuring Extension
@@ -267,9 +281,9 @@ class Admin extends \Codeception\GroupObject
$this->writeln('inserting additional admin users...');
$db = $this->getModule('Db');
- $db->haveInDatabase('users', array('name' => 'bill', 'role' => 'admin'));
- $db->haveInDatabase('users', array('name' => 'john', 'role' => 'admin'));
- $db->haveInDatabase('users', array('name' => 'mark', 'role' => 'banned'));
+ $db->haveInDatabase('users', ['name' => 'bill', 'role' => 'admin']);
+ $db->haveInDatabase('users', ['name' => 'john', 'role' => 'admin']);
+ $db->haveInDatabase('users', ['name' => 'mark', 'role' => 'banned']);
}
public function _after(\Codeception\Event\TestEvent $e)
@@ -293,7 +307,7 @@ For instance, for `nocleanup` group we prevent Doctrine2 module from wrapping te
}
```
-A group class can be created with `php codecept generate:group groupname` command.
+A group class can be created with `php vendor/bin/codecept generate:group groupname` command.
Group classes will be stored in the `tests/_support/Group` directory.
A group class can be enabled just like you enable an extension class. In the file `codeception.yml`:
@@ -305,6 +319,34 @@ extensions:
Now the Admin group class will listen for all events of tests that belong to the `admin` group.
+## Step Decorators
+
+Actor classes include generated steps taken from corresponding modules and helpers.
+You can introduce wrappers for those steps by using step decorators.
+
+Step decorators are used to implement conditional assertions.
+When enabled, conditional assertions take all method prefixed by `see` or `dontSee` and introduce new steps prefixed with `canSee` and `cantSee`.
+Contrary to standard assertions those assertions won't stop test on failure. This is done by wrapping action into try/catch blocks.
+
+List of available step decorators:
+
+- [ConditionalAssertion](https://github.com/Codeception/Codeception/blob/3.0/src/Codeception/Step/ConditionalAssertion.php) - failed assertion will be logged, but test will continue.
+- [TryTo](https://github.com/Codeception/Codeception/blob/3.0/src/Codeception/Step/TryTo.php) - failed action will be ignored.
+- [Retry](https://github.com/Codeception/Codeception/blob/3.0/src/Codeception/Step/Retry.php) - failed action will be retried automatically.
+
+Step decorators can be added to suite config inside `steps` block:
+
+```yml
+step_decorators:
+ - Codeception/Step/TryTo
+ - Codeception/Step/Retry
+ - Codeception/Step/ConditionalAssertion
+```
+
+You can introduce your own step decorators. Take a look into sample decorator classes and create your own class which implements `Codeception\Step\GeneratedStep` interface.
+A class should provide `getTemplate` method which returns a code block and variables passed into a template.
+Make your class accessible by autoloader and you can have your own step decorators working.
+
## Custom Reporters
Alternative reporters can be implemented as extension.
@@ -312,7 +354,7 @@ There are [DotReporter](http://codeception.com/extensions#DotReporter) and [Simp
Use them to change output or use them as an example to build your own reporter. They can be easily enabled with `--ext` option
```bash
-codecept run --ext DotReporter
+php vendor/bin/codecept run --ext DotReporter
```

@@ -350,12 +392,12 @@ Codeception has built-in installation templates for
They can be executed with `init` command:
```bash
-codecept init Acceptance
+php vendor/bin/codecept init Acceptance
```
To init tests in specific folder use `--path` option:
```bash
-codecept init Acceptance --path acceptance_tests
+php vendor/bin/codecept init Acceptance --path acceptance_tests
```
You will be asked several questions and then config files will be generated and all necessary directories will be created.
@@ -366,7 +408,33 @@ Learn from the examples above to build a custom Installation Template. Here are
* Use methods like `say`, `saySuccess`, `sayWarning`, `sayError`, `ask`, to interact with a user.
* Use `createDirectoryFor`, `createEmptyDirectory` methods to create directories
* Use `createHelper`, `createActor` methods to create helpers and actors.
-* Use [Codeception generators](https://github.com/Codeception/Codeception/tree/2.3/src/Codeception/Lib/Generator) to create other support classes.
+* Use [Codeception generators](https://github.com/Codeception/Codeception/tree/2.4/src/Codeception/Lib/Generator) to create other support classes.
+
+
+## One Runner for Multiple Applications
+
+If your project consists of several applications (frontend, admin, api) or you are using the Symfony framework
+with its bundles, you may be interested in having all tests for all applications (bundles) executed in one runner.
+In this case you will get one report that covers the whole project.
+
+Place the `codeception.yml` file into the root folder of your project
+and specify the paths to the other `codeception.yml` configurations that you want to include:
+
+```yaml
+include:
+ - frontend/src/*Bundle
+ - admin
+ - api/rest
+paths:
+ output: _output
+settings:
+ colors: false
+```
+
+You should also specify the path to the `log` directory, where the reports and logs will be saved.
+
+> Wildcards (*) can be used to specify multiple directories at once.
+
## Conclusion
diff --git a/docs/09-Data.md b/docs/09-Data.md
index 926d92a6d2..edddd00d37 100644
--- a/docs/09-Data.md
+++ b/docs/09-Data.md
@@ -158,9 +158,7 @@ $I->click('Delete');
$I->dontSeeRecord('posts', ['id' => $id]);
```
-
-Laravel5 module also provides `haveModel`, `makeModel` methods which use factories to generate models with fake data.
-
+Laravel5 module provides the method `have` which uses the [factory](https://laravel.com/docs/5.8/database-testing#generating-factories) method to generate models with fake data.
If you want to use ORM for integration testing only, you should enable the framework module with only the `ORM` part enabled:
@@ -196,7 +194,7 @@ modules:
- Db
```
-### DataMapper
+### Doctrine
Doctrine is also a popular ORM, unlike some others it implements the DataMapper pattern and is not bound to any framework.
The [Doctrine2](http://codeception.com/docs/modules/Doctrine2) module requires an `EntityManager` instance to work with.
@@ -272,6 +270,66 @@ modules:
DataFactory provides a powerful solution for managing data in integration/functional/acceptance tests.
Read the [full reference](http://codeception.com/docs/modules/DataFactory) to learn how to set this module up.
+## Testing Dynamic Data with Snapshots
+
+What if you deal with data which you don't own? For instance, the page look depends on number of categories in database,
+and categories are set by admin user. How would you test that the page is still valid?
+
+There is a way to get it tested as well. Codeception allows you take a snapshot of a data on first run and compare with on next executions.
+This principle is so general that it can work for testing APIs, items on a web page, etc.
+
+Let's check that list of categories on a page is the same it was before.
+Create a snapshot class:
+
+```
+php vendor/bin/codecept g:snapshot Categories
+```
+
+Inject an actor class via constructor and implement `fetchData` method which should return a data set from a test.
+
+```php
+i = $I;
+ }
+
+ protected function fetchData()
+ {
+ // fetch texts from all 'a.category' elements on a page
+ return $this->i->grabMultiple('a.category');
+ }
+}
+```
+
+Inside a test you can inject the snapshot class and call `assert` method on it:
+
+```php
+amOnPage('/categories');
+ // if previously saved array of users does not match current set, test will fail
+ // to update data in snapshot run test with --debug flag
+ $snapshot->assert();
+}
+```
+
+On the first run, data will be obtained via `fetchData` method and saved to `tests/_data` directory in json format.
+On next execution the obtained data will be compared with previously saved snapshot.
+
+> To update a snapshot with a new data run tests in `--debug` mode.
+
+By default Snapshot uses `assertEquals` assertion, however this can be customized by overriding `assertData` method.
+
+
## Conclusion
Codeception also assists the developer when dealing with data. Tools for database population
diff --git a/docs/10-WebServices.md b/docs/10-APITesting.md
similarity index 70%
rename from docs/10-WebServices.md
rename to docs/10-APITesting.md
index 7c5556294d..2c912e7ad3 100644
--- a/docs/10-WebServices.md
+++ b/docs/10-APITesting.md
@@ -1,16 +1,16 @@
-# Testing WebServices
+# API Testing
The same way we tested a web site, Codeception allows you to test web services. They are very hard to test manually, so it's a really good idea to automate web service testing. We have SOAP and REST as standards, which are represented in corresponding modules, which we will cover in this chapter.
You should start by creating a new test suite, (which was not provided by the `bootstrap` command). We recommend calling it **api** and using the `ApiTester` class for it.
```bash
-$ php codecept generate:suite api
+php vendor/bin/codecept generate:suite api
```
We will put all the api tests there.
-## REST
+## REST API
The REST web service is accessed via HTTP with standard methods: `GET`, `POST`, `PUT`, `DELETE`. They allow users to receive and manipulate entities from the service. Accessing a WebService requires an HTTP client, so for using it you need the module `PhpBrowser` or one of framework modules set up. For example, we can use the `Symfony` module for Symfony2 applications in order to ignore web server and test web service internally.
@@ -51,80 +51,95 @@ modules:
Once we have configured our new testing suite, we can create the first sample test:
```bash
-$ php codecept generate:cept api CreateUser
+php vendor/bin/codecept generate:cest api CreateUser
```
-It will be called `CreateUserCept.php`. We can use it to test the creation of a user via the REST API.
+It will be called `CreateUserCest.php`.
+We need to implement a public method for each test. Let's make `createUserViaAPI` to test creation of a user via the REST API.
```php
wantTo('create a user via API');
-$I->amHttpAuthenticated('service_user', '123456');
-$I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded');
-$I->sendPOST('/users', ['name' => 'davert', 'email' => 'davert@codeception.com']);
-$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
-$I->seeResponseIsJson();
-$I->seeResponseContains('{"result":"ok"}');
-
+class CreateUserCest
+{
+ // tests
+ public function createUserViaAPI(\ApiTester $I)
+ {
+ $I->amHttpAuthenticated('service_user', '123456');
+ $I->haveHttpHeader('Content-Type', 'application/x-www-form-urlencoded');
+ $I->sendPOST('/users', [
+ 'name' => 'davert',
+ 'email' => 'davert@codeception.com'
+ ]);
+ $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
+ $I->seeResponseIsJson();
+ $I->seeResponseContains('{"result":"ok"}');
+
+ }
+}
```
We can use HTTP code constants from `Codeception\Util\HttpCode` instead of numeric values to check response code in `seeResponseCodeIs` and `dontSeeResponseCodeIs` methods.
-### Testing JSON Responses
+Let's see what the test consist of.
-The last line of the previous example verified that the response contained the provided string. However we shouldn't rely on it, as depending on content formatting we can receive different results with the same data. What we actually need is to check that the response can be parsed and it contains some of the values we expect. In the case of JSON we can use the `seeResponseContainsJson` method
+### Authorization
-``` php
-seeResponseContainsJson(['result' => 'ok']);
-// it can match tree-like structures as well
-$I->seeResponseContainsJson([
- 'user' => [
- 'name' => 'davert',
- 'email' => 'davert@codeception.com',
- 'status' => 'inactive'
- ]
-]);
+To authorize requests to external resources, usually provider requires you to authorize using headers. Additional headers can be set before request using `haveHttpHeader` command:
+
+```php
+haveHttpHeader('api_key', 'special-key');
```
-You may want to perform even more complex assertions on a response. This can be done by writing your own methods in the [Helper](http://codeception.com/docs/06-ReusingTestCode#Modules-and-Helpers) classes. To access the latest JSON response you will need to get the `response` property of the `REST` module. Let's demonstrate it with the `seeResponseIsHtml` method:
+For common authorization patterns use one of the following methods:
+
+* `amAWSAuthenticated`
+* `amBearerAuthenticated`
+* `amDigestAuthenticated`
+* `amHttpAuthenticated`
+* `amNTLMAuthenticated`
+
+### Sending Requests
+
+The real action in a test happens only when a request is sent. Before a request you may provide additional http headers which will be used in a next request to set authorization or expected content format.
```php
haveHttpHeader('accept', 'application/json');
+$I->haveHttpHeader('content-type', 'application/json');
+```
-class Api extends \Codeception\Module
-{
- public function seeResponseIsHtml()
- {
- $response = $this->getModule('REST')->response;
- $this->assertRegExp('~^.*?<\/html>~m', $response);
- }
-}
+When headers are set, you can send a request. To obtain data use `sendGET`:
+```php
+sendGET('/posts', [ 'status' => 'pending' ]);
+$I->seeResponseCodeIs(200);
+$I->seeResponseIsJson();
```
-The same way you can receive request parameters and headers.
+> `sendGET` won't return any value. However, you can access data from a response and perform assertions using other available methods of REST module.
+
+To create or update data you can use other common methods:
-### Validate JSON structures
+* `sendPOST`
+* `sendPUT`
+* `sendDELETE`
+* `sendPATCH`
-It is pretty common for API tests to not only validate the received data but to check the structure of the response. Response data is not usually considered to be consistent, and may change on each request, however the JSON/XML structure should be kept the same for an API version. In order to check response structure the REST module has some useful methods.
+### JSON Structure Validation
If we expect a JSON response to be received we can check its structure with [JSONPath](http://goessner.net/articles/JsonPath/). It looks and sounds like XPath but is designed to work with JSON data, however we can convert JSON into XML and use XPath to validate the structure. Both approaches are valid and can be used in the REST module:
```php
wantTo('validate structure of GitHub api responses');
$I->sendGET('/users');
$I->seeResponseCodeIs(HttpCode::OK); // 200
$I->seeResponseIsJson();
$I->seeResponseJsonMatchesJsonPath('$[0].user.login');
$I->seeResponseJsonMatchesXpath('//user/login');
-
```
More detailed check can be applied if you need to validate the type of fields in a response.
@@ -148,6 +163,54 @@ $I->seeResponseMatchesJsonType([
Codeception uses this simple and lightweight definitions format which can be [easily learned and extended](http://codeception.com/docs/modules/REST#seeResponseMatchesJsonType).
+### Taking Data From Responses
+
+When you need to obtain a value from a response and use it in next requests you can use `grab*` methods. For instance, use `grabDataFromResponseByJsonPath` allows to query JSON for a value.
+
+```php
+grabDataFromResponseByJsonPath('$.id');
+$I->sendGET('/pet/' . $id);
+```
+
+### Validating Data JSON Responses
+
+The last line of the previous example verified that the response contained the provided string. However we shouldn't rely on it, as depending on content formatting we can receive different results with the same data. What we actually need is to check that the response can be parsed and it contains some of the values we expect. In the case of JSON we can use the `seeResponseContainsJson` method
+
+``` php
+seeResponseContainsJson(['result' => 'ok']);
+// it can match tree-like structures as well
+$I->seeResponseContainsJson([
+ 'user' => [
+ 'name' => 'davert',
+ 'email' => 'davert@codeception.com',
+ 'status' => 'inactive'
+ ]
+]);
+
+```
+
+You may want to perform even more complex assertions on a response. This can be done by writing your own methods in the [Helper](http://codeception.com/docs/06-ReusingTestCode#Modules-and-Helpers) classes. To access the latest JSON response you will need to get the `response` property of the `REST` module. Let's demonstrate it with the `seeResponseIsHtml` method:
+
+```php
+getModule('REST')->response;
+ $this->assertRegExp('~^.*?<\/html>~m', $response);
+ }
+}
+
+```
+
+The same way you can receive request parameters and headers.
+
### Testing XML Responses
In case your REST API works with XML format you can use similar methods to test its data and structure.
@@ -155,31 +218,25 @@ There is `seeXmlResponseIncludes` method to match inclusion of XML parts in resp
```php
wantTo('validate structure of GitHub api responses');
$I->sendGET('/users.xml');
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
$I->seeResponseIsXml();
$I->seeXmlResponseMatchesXpath('//user/login');
-$I->seeXmlResponseIncludes(XmlUtils::toXml(
+$I->seeXmlResponseIncludes(\Codeception\Util\Xml::toXml([
'user' => [
'name' => 'davert',
'email' => 'davert@codeception.com',
'status' => 'inactive'
]
-));
+]));
```
-We are using XmlUtils class which allows us to build XML structures in a clean manner. The `toXml` method may accept a string or array and returns \DOMDocument instance. If your XML contains attributes and so can't be represented as a PHP array you can create XML using the [XmlBuilder](http://codeception.com/docs/reference/XmlBuilder) class. We will take a look at it a bit more in next section.
+We are using `Codeception\Util\Xml` class which allows us to build XML structures in a clean manner. The `toXml` method may accept a string or array and returns \DOMDocument instance. If your XML contains attributes and so can't be represented as a PHP array you can create XML using the [XmlBuilder](http://codeception.com/docs/reference/XmlBuilder) class. We will take a look at it a bit more in next section.
-
-Use `\Codeception\Util\Xml::build()` to create XmlBuilder instance.
-
+> Use `\Codeception\Util\Xml::build()` to create XmlBuilder instance.
-## SOAP
+## SOAP API
SOAP web services are usually more complex. You will need PHP [configured with SOAP support](http://php.net/manual/en/soap.installation.php). Good knowledge of XML is required too. `SOAP` module uses specially formatted POST request to connect to WSDL web services. Codeception uses `PhpBrowser` or one of framework modules to perform interactions. If you choose using a framework module, SOAP will automatically connect to the underlying framework. That may improve the speed of a test execution and will provide you with more detailed stack traces.
@@ -189,17 +246,16 @@ Let's configure `SOAP` module to be used with `PhpBrowser`:
actor: ApiTester
modules:
enabled:
- - SOAP:
- depends: PhpBrowser
- endpoint: http://serviceapp/api/v1/
+ - SOAP:
+ depends: PhpBrowser
+ endpoint: http://serviceapp/api/v1/
```
SOAP request may contain application specific information, like authentication or payment. This information is provided with SOAP header inside the `` element of XML request. In case you need to submit such header, you can use `haveSoapHeader` action. For example, next line of code
```php
haveSoapHeader('Auth', array('username' => 'Miles', 'password' => '123456'));
-
+$I->haveSoapHeader('Auth', ['username' => 'Miles', 'password' => '123456']);
```
will produce this XML header
@@ -247,14 +303,10 @@ In the next example we will use `XmlBuilder` instead of regular XML.
```php
wantTo('create user');
$I->haveSoapHeader('Session', array('token' => '123456'));
$I->sendSoapRequest('CreateUser', Xml::build()
->user->email->val('miles@davis.com'));
-$I->seeSoapResponseIncludes(Xml::build()
+$I->seeSoapResponseIncludes(\Codeception\Util\Xml::build()
->result->val('Ok')
->user->attr('id', 1)
);
@@ -270,11 +322,11 @@ You may extend current functionality by using `SOAP` module in your helper class
namespace Helper;
class Api extends \Codeception\Module {
- public function seeResponseIsValidOnSchema($schema)
- {
- $response = $this->getModule('SOAP')->response;
- $this->assertTrue($response->schemaValidate($schema));
- }
+ public function seeResponseIsValidOnSchema($schema)
+ {
+ $response = $this->getModule('SOAP')->response;
+ $this->assertTrue($response->schemaValidate($schema));
+ }
}
```
diff --git a/docs/11-Codecoverage.md b/docs/11-Codecoverage.md
index 97cf54ba25..3127c14881 100644
--- a/docs/11-Codecoverage.md
+++ b/docs/11-Codecoverage.md
@@ -5,7 +5,13 @@ Just for this case the [CodeCoverage](http://en.wikipedia.org/wiki/Code_coverage
you will receive statistics of all classes, methods, and lines triggered by these tests.
The ratio between all lines in script and all touched lines is a main coverage criterion. In the ideal world you should get 100% code coverage, but in reality 80% is really enough. Because even 100% code coverage rate doesn't save you from fatal errors and crashes.
-*To collect coverage information `xdebug` is required**.
+The required information is provided by [SebastianBergmann\CodeCoverage](https://github.com/sebastianbergmann/php-code-coverage), and you can use any of the supported drivers.
+
+| Driver | Description |
+| --- | --- |
+| [Xdebug](https://github.com/xdebug/xdebug) | Great for debugging, but too slow when collecting coverage |
+| [phpdbg](https://www.php.net/manual/en/book.phpdbg.php) | Faster than `Xdebug` but inaccurate |
+| [pcov](https://github.com/krakjoe/pcov) | Fast and accurate, but no debug functionality — perfect for CI |

@@ -47,6 +53,24 @@ coverage:
high_limit: 60
```
+By default, show all whitelisted files in `--coverage-text` output not just the ones with coverage information is set to false, config option:
+
+```yaml
+coverage:
+ enabled: true
+ show_uncovered: false
+```
+
+By default, show only the coverage report summary in `--coverage-text` output is set to false, config option:
+
+```yaml
+coverage:
+ enabled: true
+ show_only_summary: false
+```
+
+For further information please refer to the [PHPUnit configuration docs](https://phpunit.readthedocs.io/en/latest/configuration.html)
+
## Local CodeCoverage
The basic codecoverage can be collected for functional and unit tests.
@@ -95,6 +119,19 @@ In case you execute your application locally there is nothing to be changed in c
All codecoverage reports will be collected as usual and merged afterwards.
Think of it: Codeception runs remote coverage in the same way as local.
+#### Custom cookie domain
+
+It's possible to override the cookie domain set by Codeception during code coverage. Typical case for that is when you
+have several subdomains that your acceptance tests are visiting, e.g. `mysite.com` and `admin.mysite.com`. By default,
+Codeception will run code coverage only for the domain set in the url of the `WebDriver/url` (or `c3_url` if defined),
+thus leaving out other subdomains from code coverage. To avoid that and to include all relevant subdomains in code
+covereage, it's advised to set `.mysite.com` as the cookie domain option:
+
+```yaml
+coverage:
+ cookie_domain: ".mysite.com"
+```
+
### Remote Server
But if you run tests on different server (or your webserver doesn't use code from current directory) a single option `remote` should be added to config.
@@ -111,6 +148,24 @@ But in case of running tests on a remote server we are not sure of it.
CodeCoverage results from remote server will be saved to `tests/_output` directory. Please note that remote codecoverage results won't be displayed in console by the reason mentioned above: local and remote results can't be merged, and console displays results for local codecoverage.
+### Working Directory (Docker/Shared Mounts)
+
+If your remote server is accessed through a shared mount, or a mounted folder (IE: Docker Volumes), you can still get merged coverage details.
+Use the `work_dir` option to specify the work directory. When CodeCoverage runs, Codeception will update any path that matches the `work_dir` option to match the local current project directory.
+
+Given a docker command similar to:
+```bash
+docker run -v $(pwd):/workdir -w /workdir...
+```
+
+Use the below configuration to allow coverage mergers.
+```yaml
+coverage:
+ remote: false
+ work_dir: /workdir
+
+```
+
### Remote Context Options
HTML report generation can at times take a little more time than the default 30 second timeout. Or maybe you want to alter SSL settings (verify_peer, for example)
diff --git a/docs/12-ContinuousIntegration.md b/docs/12-ContinuousIntegration.md
index bf077354af..3bee8d60d9 100644
--- a/docs/12-ContinuousIntegration.md
+++ b/docs/12-ContinuousIntegration.md
@@ -31,7 +31,7 @@ At first we need to create build project. Depending on your needs you can set up
We need to define build steps. The most simple setup may look like this:
```
-php codecept run
+php vendor/bin/codecept run
```

@@ -47,7 +47,7 @@ But we don't want to analyze console output for each failing build. Especially I
Now let's update our build step to generate xml:
```
-php codecept run --xml
+php vendor/bin/codecept run --xml
```
and ask Jenkins to collect resulted XML. This can be done as part of Post-build actions. Let's add *Publish xUnit test result report* action and configure it to use with PHPUnit reports.
@@ -65,7 +65,7 @@ Now for all builds we will see results trend graph that shows us percentage of p
To get more details on steps executed you can generate HTML report and use Jenkins to display them.
```
-php codecept run --html
+php vendor/bin/codecept run --html
```
Now we need HTML Publisher plugin configured to display generated HTML files. It should be added as post-build action similar way we did it for XML reports.
@@ -93,7 +93,7 @@ As an alternative you can use 3rd-party [TeamCity extension](https://github.com/
After you create build project you should define build step with Codeception which is
```
-php codecept run --report
+php vendor/bin/codecept run --report
```

@@ -109,10 +109,10 @@ Once you execute your first build you should see detailed report inside TeamCity
Travis CI is popular service CI with good GitHub integration. Codeception is self-tested with Travis CI. There nothing special about configuration. Just add to the bottom line of travis configuration:
```yaml
-php codecept run
+php vendor/bin/codecept run
```
-More details on configuration can be learned from Codeception's [`.travis.yml`](https://github.com/Codeception/Codeception/blob/master/.travis.yml).
+More details on configuration can be learned from Codeception's [`.travis.yml`](https://github.com/Codeception/Codeception/blob/3.0/.travis.yml).
Travis doesn't provide visualization for XML or HTML reports so you can't view reports in format any different than console output. However, Codeception produces nice console output with detailed error reports.
diff --git a/docs/12-ParallelExecution.md b/docs/12-ParallelExecution.md
index fce0be60c9..c873633f6e 100644
--- a/docs/12-ParallelExecution.md
+++ b/docs/12-ParallelExecution.md
@@ -28,7 +28,7 @@ Run official Codeception image from DockerHub:
docker run codeception/codeception
Running tests from a project, by mounting the current path as a host-volume into the container.
-The default working directory in the container is `/project`.
+The **default working directory in the container is `/project`**.
docker run -v ${PWD}:/project codeception/codeception run
@@ -36,20 +36,18 @@ To prepare application and tests to be executed inside containers you will need
Define all required services in `docker-compose.yml` file. Make sure to follow Docker philisophy: 1 service = 1 container. So each process should be defined as its own service. Those services can use official Docker images pulled from DockerHub. Directories with code and tests should be mounted using `volume` directive. And exposed ports should be explicitly set using `ports` directive.
-We prepared a sample config with codeception, web server, database, and selenium with firefox to be executed together.
+We prepared a sample config with codeception, web server, database, and selenium with Chrome to be executed together.
```yaml
-version: '2'
+version: '3'
services:
codecept:
image: codeception/codeception
depends_on:
- - firefox
+ - chrome
- web
volumes:
- - ./src:/src
- - ./tests:/tests
- - ./codeception.yml:/codeception.yml
+ - .:/project
web:
image: php:7-apache
depends_on:
@@ -58,13 +56,8 @@ services:
- .:/var/www/html
db:
image: percona:5.6
- ports:
- - '3306'
- firefox:
- image: selenium/standalone-firefox-debug:2.53.0
- ports:
- - '4444'
- - '5900'
+ chrome:
+ image: selenium/standalone-chrome
```
Codeception service will execute command `codecept run` but only after all services are started. This is defined using `depends_on` parameter.
@@ -145,7 +138,7 @@ $ composer require codeception/codeception
### Preparing Robo
-Intitalizes basic RoboFile in the root of your project
+Initializes basic RoboFile in the root of your project
```bash
$ robo init
@@ -293,8 +286,8 @@ public function parallelRun()
for ($i = 1; $i <= 5; $i++) {
$parallel->process(
$this->taskExec('docker-compose run --rm codecept run')
- ->opt('group', "paracept_$i") // run for groups paracept_*
- ->opt('xml', "tests/_log/result_$i.xml"); // provide xml report
+ ->option('group', "paracept_$i") // run for groups paracept_*
+ ->option('xml', "tests/_log/result_$i.xml") // provide xml report
);
}
return $parallel->run();
diff --git a/docs/modules/AMQP.md b/docs/modules/AMQP.md
index c7e332eea4..3e00ee9fce 100644
--- a/docs/modules/AMQP.md
+++ b/docs/modules/AMQP.md
@@ -1,5 +1,6 @@
# AMQP
+
This module interacts with message broker software that implements
the Advanced Message Queuing Protocol (AMQP) standard. For example, RabbitMQ (tested).
@@ -37,7 +38,7 @@ To use this module with Composer you need "php-amqplib/php-amqplib": "~2.4"<
## Actions
### bindQueueToExchange
-
+
Binds a queue to an exchange
This is an alias of method `queue_bind` of `PhpAmqpLib\Channel\AMQPChannel`.
@@ -59,8 +60,9 @@ $I->bindQueueToExchange(
* `param int` $ticket
* `return` mixed|null
-### declareExchange
+### declareExchange
+
Declares an exchange
This is an alias of method `exchange_declare` of `PhpAmqpLib\Channel\AMQPChannel`.
@@ -84,8 +86,9 @@ $I->declareExchange(
* `param int` $ticket
* `return` mixed|null
-### declareQueue
+### declareQueue
+
Declares queue, creates if needed
This is an alias of method `queue_declare` of `PhpAmqpLib\Channel\AMQPChannel`.
@@ -107,8 +110,23 @@ $I->declareQueue(
* `param int` $ticket
* `return` mixed|null
-### grabMessageFromQueue
+### dontSeeQueueIsEmpty
+
+Checks if queue is not empty.
+
+``` php
+pushToQueue('queue.emails', 'Hello, davert');
+$I->dontSeeQueueIsEmpty('queue.emails');
+?>
+```
+
+ * `param string` $queue
+
+
+### grabMessageFromQueue
+
Takes last message from queue.
``` php
@@ -120,8 +138,9 @@ $message = $I->grabMessageFromQueue('queue.emails');
* `param string` $queue
* `return` \PhpAmqpLib\Message\AMQPMessage
-### purgeAllQueues
+### purgeAllQueues
+
Purge all queues defined in config.
``` php
@@ -130,8 +149,9 @@ $I->purgeAllQueues();
?>
```
-### purgeQueue
+### purgeQueue
+
Purge a specific queue defined in config.
``` php
@@ -142,8 +162,9 @@ $I->purgeQueue('queue.emails');
* `param string` $queueName
-### pushToExchange
+### pushToExchange
+
Sends message to exchange by sending exchange name, message
and (optionally) a routing key
@@ -159,8 +180,9 @@ $I->pushToExchange('exchange.emails', new AMQPMessage('Thanks!'), 'severity');
* `param string|\PhpAmqpLib\Message\AMQPMessage` $message
* `param string` $routing_key
-### pushToQueue
+### pushToQueue
+
Sends message to queue
``` php
@@ -173,8 +195,16 @@ $I->pushToQueue('queue.jobs', new AMQPMessage('create'));
* `param string` $queue
* `param string|\PhpAmqpLib\Message\AMQPMessage` $message
-### seeMessageInQueueContainsText
+### scheduleQueueCleanup
+
+Add a queue to purge list
+
+ * `param string` $queue
+
+
+### seeMessageInQueueContainsText
+
Checks if message containing text received.
**This method drops message from queue**
@@ -190,4 +220,35 @@ $I->seeMessageInQueueContainsText('queue.emails','davert');
* `param string` $queue
* `param string` $text
-
diff --git a/docs/modules/AngularJS.md b/docs/modules/AngularJS.md
deleted file mode 100644
index e8396ce3de..0000000000
--- a/docs/modules/AngularJS.md
+++ /dev/null
@@ -1,1760 +0,0 @@
-# AngularJS
-
-Module for AngularJS testing, based on [WebDriver module](http://codeception.com/docs/modules/WebDriver) and [Protractor](http://angular.github.io/protractor/).
-
-Performs **synchronization to ensure that page content is fully rendered**.
-Uses Angular's and Protractor internals methods to synchronize with the page.
-
-## Configuration
-
-The same as for [WebDriver](http://codeception.com/docs/modules/WebDriver#Configuration), but few new options added:
-
-* `el` - element where Angular application is defined (default: `body`)
-* `script_timeout` - for how long in seconds to wait for angular operations to finish (default: 5)
-
-### Example (`acceptance.suite.yml`)
-
- modules:
- enabled:
- - AngularJS:
- url: 'http://localhost/'
- browser: firefox
- script_timeout: 10
-
-### Additional Features
-
-Can perform matching elements by model. In this case you should provide a strict locator with `model` set.
-
-Example:
-
-```php
-$I->selectOption(['model' => 'customerId'], '3');
-```
-
-## Actions
-
-### _backupSession
-
-*hidden API method, expected to be used from Helper classes*
-
-Returns current WebDriver session for saving
-
- * `return` RemoteWebDriver
-
-### _capabilities
-
-*hidden API method, expected to be used from Helper classes*
-
-Change capabilities of WebDriver. Should be executed before starting a new browser session.
-This method expects a function to be passed which returns array or [WebDriver Desired Capabilities](https://github.com/facebook/php-webdriver/blob/community/lib/Remote/DesiredCapabilities.php) object.
-Additional [Chrome options](https://github.com/facebook/php-webdriver/wiki/ChromeOptions) (like adding extensions) can be passed as well.
-
-```php
-getModule('WebDriver')->_capabilities(function($currentCapabilities) {
- // or new \Facebook\WebDriver\Remote\DesiredCapabilities();
- return \Facebook\WebDriver\Remote\DesiredCapabilities::firefox();
- });
-}
-```
-
-to make this work load `\Helper\Acceptance` before `WebDriver` in `acceptance.suite.yml`:
-
-```yaml
-modules:
- enabled:
- - \Helper\Acceptance
- - WebDriver
-```
-
-For instance, [**BrowserStack** cloud service](https://www.browserstack.com/automate/capabilities) may require a test name to be set in capabilities.
-This is how it can be done via `_capabilities` method from `Helper\Acceptance`:
-
-```php
-getMetadata()->getName();
- $this->getModule('WebDriver')->_capabilities(function($currentCapabilities) use ($name) {
- $currentCapabilities['name'] = $name;
- return $currentCapabilities;
- });
-}
-```
-In this case, please ensure that `\Helper\Acceptance` is loaded before WebDriver so new capabilities could be applied.
-
- * `param \Closure` $capabilityFunction
-
-### _closeSession
-
-*hidden API method, expected to be used from Helper classes*
-
-Manually closes current WebDriver session.
-
-```php
-getModule('WebDriver')->_closeSession();
-
-// close a specific session
-$webDriver = $this->getModule('WebDriver')->webDriver;
-$this->getModule('WebDriver')->_closeSession($webDriver);
-```
-
- * `param` $webDriver (optional) a specific webdriver session instance
-
-### _findClickable
-
-*hidden API method, expected to be used from Helper classes*
-
-Locates a clickable element.
-
-Use it in Helpers or GroupObject or Extension classes:
-
-```php
-getModule('WebDriver');
-$page = $module->webDriver;
-
-// search a link or button on a page
-$el = $module->_findClickable($page, 'Click Me');
-
-// search a link or button within an element
-$topBar = $module->_findElements('.top-bar')[0];
-$el = $module->_findClickable($topBar, 'Click Me');
-
-```
- * `param` $page WebDriver instance or an element to search within
- * `param` $link a link text or locator to click
- * `return` WebDriverElement
-
-### _findElements
-
-*hidden API method, expected to be used from Helper classes*
-
-Locates element using available Codeception locator types:
-
-* XPath
-* CSS
-* Strict Locator
-
-Use it in Helpers or GroupObject or Extension classes:
-
-```php
-getModule('AngularJS')->_findElements('.items');
-$els = $this->getModule('AngularJS')->_findElements(['name' => 'username']);
-
-$editLinks = $this->getModule('AngularJS')->_findElements(['link' => 'Edit']);
-// now you can iterate over $editLinks and check that all them have valid hrefs
-```
-
-WebDriver module returns `Facebook\WebDriver\Remote\RemoteWebElement` instances
-PhpBrowser and Framework modules return `Symfony\Component\DomCrawler\Crawler` instances
-
- * `param` $locator
- * `return` array of interactive elements
-
-### _getCurrentUri
-
-*hidden API method, expected to be used from Helper classes*
-
-Uri of currently opened page.
- * `return` string
-@throws ModuleException
-
-### _getUrl
-
-*hidden API method, expected to be used from Helper classes*
-
-Returns URL of a host.
-
-@throws ModuleConfigException
-
-### _initializeSession
-
-*hidden API method, expected to be used from Helper classes*
-
-Manually starts a new browser session.
-
-```php
-getModule('WebDriver')->_initializeSession();
-```
-
-### _loadSession
-
-*hidden API method, expected to be used from Helper classes*
-
-Loads current RemoteWebDriver instance as a session
-
- * `param RemoteWebDriver` $session
-
-### _restart
-
-*hidden API method, expected to be used from Helper classes*
-
-Restarts a web browser.
-Can be used with `_reconfigure` to open browser with different configuration
-
-```php
-getModule('WebDriver')->_restart(); // just restart
-$this->getModule('WebDriver')->_restart(['browser' => $browser]); // reconfigure + restart
-```
-
- * `param array` $config
-
-### _savePageSource
-
-*hidden API method, expected to be used from Helper classes*
-
-Saves HTML source of a page to a file
- * `param` $filename
-
-### _saveScreenshot
-
-*hidden API method, expected to be used from Helper classes*
-
-Saves screenshot of current page to a file
-
-```php
-$this->getModule('AngularJS')->_saveScreenshot(codecept_output_dir().'screenshot_1.png');
-```
- * `param` $filename
-
-### acceptPopup
-
-Accepts the active JavaScript native popup window, as created by `window.alert`|`window.confirm`|`window.prompt`.
-Don't confuse popups with modal windows,
-as created by [various libraries](http://jster.net/category/windows-modals-popups).
-
-### amInsideAngularApp
-
-Enables Angular mode (enabled by default).
-Waits for Angular to finish rendering after each action.
-
-### amOnPage
-
-Opens the page for the given relative URI.
-
-``` php
-amOnPage('/');
-// opens /register page
-$I->amOnPage('/register');
-```
-
- * `param string` $page
-
-### amOnSubdomain
-
-Changes the subdomain for the 'url' configuration parameter.
-Does not open a page; use `amOnPage` for that.
-
-``` php
-amOnSubdomain('user');
-$I->amOnPage('/');
-// moves to http://user.mysite.com/
-?>
-```
-
- * `param` $subdomain
-
-### amOnUrl
-
-Open web page at the given absolute URL and sets its hostname as the base host.
-
-``` php
-amOnUrl('http://codeception.com');
-$I->amOnPage('/quickstart'); // moves to http://codeception.com/quickstart
-?>
-```
-
-### amOutsideAngularApp
-
-Disabled Angular mode.
-
-Falls back to original WebDriver, in case web page does not contain Angular app.
-
-### appendField
-
-Append the given text to the given element.
-Can also add a selection to a select box.
-
-``` php
-appendField('#mySelectbox', 'SelectValue');
-$I->appendField('#myTextField', 'appended');
-?>
-```
-
- * `param string` $field
- * `param string` $value
-@throws \Codeception\Exception\ElementNotFound
-
-### attachFile
-
-Attaches a file relative to the Codeception `_data` directory to the given file upload field.
-
-``` php
-attachFile('input[@type="file"]', 'prices.xls');
-?>
-```
-
- * `param` $field
- * `param` $filename
-
-### cancelPopup
-
-Dismisses the active JavaScript popup, as created by `window.alert`, `window.confirm`, or `window.prompt`.
-
-### checkOption
-
-Ticks a checkbox. For radio buttons, use the `selectOption` method instead.
-
-``` php
-checkOption('#agree');
-?>
-```
-
- * `param` $option
-
-### click
-
-Perform a click on a link or a button, given by a locator.
-If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
-For buttons, the "value" attribute, "name" attribute, and inner text are searched.
-For links, the link text is searched.
-For images, the "alt" attribute and inner text of any parent links are searched.
-
-The second parameter is a context (CSS or XPath locator) to narrow the search.
-
-Note that if the locator matches a button of type `submit`, the form will be submitted.
-
-``` php
-click('Logout');
-// button of form
-$I->click('Submit');
-// CSS button
-$I->click('#form input[type=submit]');
-// XPath
-$I->click('//form/*[@type=submit]');
-// link in context
-$I->click('Logout', '#nav');
-// using strict locator
-$I->click(['link' => 'Login']);
-?>
-```
-
- * `param` $link
- * `param` $context
-
-### clickWithLeftButton
-
-Performs click with the left mouse button on an element.
-If the first parameter `null` then the offset is relative to the actual mouse position.
-If the second and third parameters are given,
-then the mouse is moved to an offset of the element's top-left corner.
-Otherwise, the mouse is moved to the center of the element.
-
-``` php
-clickWithLeftButton(['css' => '.checkout']);
-$I->clickWithLeftButton(null, 20, 50);
-$I->clickWithLeftButton(['css' => '.checkout'], 20, 50);
-?>
-```
-
- * `param string` $cssOrXPath css or xpath of the web element (body by default).
- * `param int` $offsetX
- * `param int` $offsetY
-
-@throws \Codeception\Exception\ElementNotFound
-
-### clickWithRightButton
-
-Performs contextual click with the right mouse button on an element.
-If the first parameter `null` then the offset is relative to the actual mouse position.
-If the second and third parameters are given,
-then the mouse is moved to an offset of the element's top-left corner.
-Otherwise, the mouse is moved to the center of the element.
-
-``` php
-clickWithRightButton(['css' => '.checkout']);
-$I->clickWithRightButton(null, 20, 50);
-$I->clickWithRightButton(['css' => '.checkout'], 20, 50);
-?>
-```
-
- * `param string` $cssOrXPath css or xpath of the web element (body by default).
- * `param int` $offsetX
- * `param int` $offsetY
-
-@throws \Codeception\Exception\ElementNotFound
-
-### closeTab
-
-Closes current browser tab and switches to previous active tab.
-
-```php
-closeTab();
-```
-
-Can't be used with PhantomJS
-
-### debugWebDriverLogs
-
-Print out latest Selenium Logs in debug mode
-
- * `param TestInterface` $test
-
-### dontSee
-
-Checks that the current page doesn't contain the text specified (case insensitive).
-Give a locator as the second parameter to match a specific region.
-
-```php
-dontSee('Login'); // I can suppose user is already logged in
-$I->dontSee('Sign Up','h1'); // I can suppose it's not a signup page
-$I->dontSee('Sign Up','//body/h1'); // with XPath
-$I->dontSee('Sign Up', ['css' => 'body h1']); // with strict CSS locator
-```
-
-Note that the search is done after stripping all HTML tags from the body,
-so `$I->dontSee('strong')` will fail on strings like:
-
- - `
Home`
- - ``
-
-For checking the raw source code, use `seeInSource()`.
-
- * `param string` $text
- * `param string` $selector optional
-
-### dontSeeCheckboxIsChecked
-
-Check that the specified checkbox is unchecked.
-
-``` php
-dontSeeCheckboxIsChecked('#agree'); // I suppose user didn't agree to terms
-$I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user didn't check the first checkbox in form.
-?>
-```
-
- * `param` $checkbox
-
-### dontSeeCookie
-
-Checks that there isn't a cookie with the given name.
-You can set additional cookie params like `domain`, `path` as array passed in last argument.
-
- * `param` $cookie
-
- * `param array` $params
-
-### dontSeeCurrentUrlEquals
-
-Checks that the current URL doesn't equal the given string.
-Unlike `dontSeeInCurrentUrl`, this only matches the full URL.
-
-``` php
-dontSeeCurrentUrlEquals('/');
-?>
-```
-
- * `param string` $uri
-
-### dontSeeCurrentUrlMatches
-
-Checks that current url doesn't match the given regular expression.
-
-``` php
-dontSeeCurrentUrlMatches('~$/users/(\d+)~');
-?>
-```
-
- * `param string` $uri
-
-### dontSeeElement
-
-Checks that the given element is invisible or not present on the page.
-You can also specify expected attributes of this element.
-
-``` php
-dontSeeElement('.error');
-$I->dontSeeElement('//form/input[1]');
-$I->dontSeeElement('input', ['name' => 'login']);
-$I->dontSeeElement('input', ['value' => '123456']);
-?>
-```
-
- * `param` $selector
- * `param array` $attributes
-
-### dontSeeElementInDOM
-
-Opposite of `seeElementInDOM`.
-
- * `param` $selector
- * `param array` $attributes
-
-### dontSeeInCurrentUrl
-
-Checks that the current URI doesn't contain the given string.
-
-``` php
-dontSeeInCurrentUrl('/users/');
-?>
-```
-
- * `param string` $uri
-
-### dontSeeInField
-
-Checks that an input field or textarea doesn't contain the given value.
-For fuzzy locators, the field is matched by label text, CSS and XPath.
-
-``` php
-dontSeeInField('Body','Type your comment here');
-$I->dontSeeInField('form textarea[name=body]','Type your comment here');
-$I->dontSeeInField('form input[type=hidden]','hidden_value');
-$I->dontSeeInField('#searchform input','Search');
-$I->dontSeeInField('//form/*[@name=search]','Search');
-$I->dontSeeInField(['name' => 'search'], 'Search');
-?>
-```
-
- * `param` $field
- * `param` $value
-
-### dontSeeInFormFields
-
-Checks if the array of form parameters (name => value) are not set on the form matched with
-the passed selector.
-
-``` php
-dontSeeInFormFields('form[name=myform]', [
- 'input1' => 'non-existent value',
- 'input2' => 'other non-existent value',
-]);
-?>
-```
-
-To check that an element hasn't been assigned any one of many values, an array can be passed
-as the value:
-
-``` php
-dontSeeInFormFields('.form-class', [
- 'fieldName' => [
- 'This value shouldn\'t be set',
- 'And this value shouldn\'t be set',
- ],
-]);
-?>
-```
-
-Additionally, checkbox values can be checked with a boolean.
-
-``` php
-dontSeeInFormFields('#form-id', [
- 'checkbox1' => true, // fails if checked
- 'checkbox2' => false, // fails if unchecked
-]);
-?>
-```
-
- * `param` $formSelector
- * `param` $params
-
-### dontSeeInPageSource
-
-Checks that the page source doesn't contain the given string.
-
- * `param` $text
-
-### dontSeeInPopup
-
-Checks that the active JavaScript popup,
-as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string.
-
- * `param` $text
-
-@throws \Codeception\Exception\ModuleException
-
-### dontSeeInSource
-
-Checks that the current page contains the given string in its
-raw source code.
-
-```php
-dontSeeInSource('
Green eggs & ham
');
-```
-
- * `param` $raw
-
-### dontSeeInTitle
-
-Checks that the page title does not contain the given string.
-
- * `param` $title
-
-### dontSeeLink
-
-Checks that the page doesn't contain a link with the given string.
-If the second parameter is given, only links with a matching "href" attribute will be checked.
-
-``` php
-dontSeeLink('Logout'); // I suppose user is not logged in
-$I->dontSeeLink('Checkout now', '/store/cart.php');
-?>
-```
-
- * `param string` $text
- * `param string` $url optional
-
-### dontSeeOptionIsSelected
-
-Checks that the given option is not selected.
-
-``` php
-dontSeeOptionIsSelected('#form input[name=payment]', 'Visa');
-?>
-```
-
- * `param` $selector
- * `param` $optionText
-
-### doubleClick
-
-Performs a double-click on an element matched by CSS or XPath.
-
- * `param` $cssOrXPath
-@throws \Codeception\Exception\ElementNotFound
-
-### dragAndDrop
-
-Performs a simple mouse drag-and-drop operation.
-
-``` php
-dragAndDrop('#drag', '#drop');
-?>
-```
-
- * `param string` $source (CSS ID or XPath)
- * `param string` $target (CSS ID or XPath)
-
-### executeInSelenium
-
-Low-level API method.
-If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly:
-
-``` php
-$I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
- $webdriver->get('http://google.com');
-});
-```
-
-This runs in the context of the
-[RemoteWebDriver class](https://github.com/facebook/php-webdriver/blob/master/lib/remote/RemoteWebDriver.php).
-Try not to use this command on a regular basis.
-If Codeception lacks a feature you need, please implement it and submit a patch.
-
- * `param callable` $function
-
-### executeJS
-
-Executes custom JavaScript.
-
-This example uses jQuery to get a value and assigns that value to a PHP variable:
-
-```php
-executeJS('return $("#myField").val()');
-?>
-```
-
- * `param` $script
-
-### fillField
-
-Fills a text field or textarea with the given string.
-
-``` php
-fillField("//input[@type='text']", "Hello World!");
-$I->fillField(['name' => 'email'], 'jon@mail.com');
-?>
-```
-
- * `param` $field
- * `param` $value
-
-### grabAttributeFrom
-
-Grabs the value of the given attribute value from the given element.
-Fails if element is not found.
-
-``` php
-grabAttributeFrom('#tooltip', 'title');
-?>
-```
-
- * `param` $cssOrXpath
- * `param` $attribute
-
-### grabCookie
-
-Grabs a cookie value.
-You can set additional cookie params like `domain`, `path` in array passed as last argument.
-
- * `param` $cookie
-
- * `param array` $params
-
-### grabFromCurrentUrl
-
-Executes the given regular expression against the current URI and returns the first capturing group.
-If no parameters are provided, the full URI is returned.
-
-``` php
-grabFromCurrentUrl('~$/user/(\d+)/~');
-$uri = $I->grabFromCurrentUrl();
-?>
-```
-
- * `param string` $uri optional
-
-### grabMultiple
-
-Grabs either the text content, or attribute values, of nodes
-matched by $cssOrXpath and returns them as an array.
-
-```html
-First
-Second
-Third
-```
-
-```php
-grabMultiple('a');
-
-// would return ['#first', '#second', '#third']
-$aLinks = $I->grabMultiple('a', 'href');
-?>
-```
-
- * `param` $cssOrXpath
- * `param` $attribute
- * `return` string[]
-
-### grabPageSource
-
-Grabs current page source code.
-
-@throws ModuleException if no page was opened.
-
- * `return` string Current page source code.
-
-### grabTextFrom
-
-Finds and returns the text contents of the given element.
-If a fuzzy locator is used, the element is found using CSS, XPath,
-and by matching the full page source by regular expression.
-
-``` php
-grabTextFrom('h1');
-$heading = $I->grabTextFrom('descendant-or-self::h1');
-$value = $I->grabTextFrom('~
-```
-
- * `param` $cssOrXPathOrRegex
-
-### grabValueFrom
-
-Finds the value for the given form field.
-If a fuzzy locator is used, the field is found by field name, CSS, and XPath.
-
-``` php
-grabValueFrom('Name');
-$name = $I->grabValueFrom('input[name=username]');
-$name = $I->grabValueFrom('descendant-or-self::form/descendant::input[@name = 'username']');
-$name = $I->grabValueFrom(['name' => 'username']);
-?>
-```
-
- * `param` $field
-
-### loadSessionSnapshot
-
- * `param string` $name
- * `return` bool
-
-### makeScreenshot
-
-Takes a screenshot of the current window and saves it to `tests/_output/debug`.
-
-``` php
-amOnPage('/user/edit');
-$I->makeScreenshot('edit_page');
-// saved to: tests/_output/debug/edit_page.png
-$I->makeScreenshot();
-// saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png
-```
-
- * `param` $name
-
-### maximizeWindow
-
-Maximizes the current window.
-
-### moveBack
-
-Moves back in history.
-
-### moveForward
-
-Moves forward in history.
-
-### moveMouseOver
-
-Move mouse over the first element matched by the given locator.
-If the first parameter null then the page is used.
-If the second and third parameters are given,
-then the mouse is moved to an offset of the element's top-left corner.
-Otherwise, the mouse is moved to the center of the element.
-
-``` php
-moveMouseOver(['css' => '.checkout']);
-$I->moveMouseOver(null, 20, 50);
-$I->moveMouseOver(['css' => '.checkout'], 20, 50);
-?>
-```
-
- * `param string` $cssOrXPath css or xpath of the web element
- * `param int` $offsetX
- * `param int` $offsetY
-
-@throws \Codeception\Exception\ElementNotFound
-
-### openNewTab
-
-Opens a new browser tab (wherever it is possible) and switches to it.
-
-```php
-openNewTab();
-```
-Tab is opened by using `window.open` javascript in a browser.
-Please note, that adblock can restrict creating such tabs.
-
-Can't be used with PhantomJS
-
-### pauseExecution
-
-Pauses test execution in debug mode.
-To proceed test press "ENTER" in console.
-
-This method is useful while writing tests,
-since it allows you to inspect the current page in the middle of a test case.
-
-### performOn
-
-Waits for element and runs a sequence of actions inside its context.
-Actions can be defined with array, callback, or `Codeception\Util\ActionSequence` instance.
-
-Actions as array are recommended for simple to combine "waitForElement" with assertions;
-`waitForElement($el)` and `see('text', $el)` can be simplified to:
-
-```php
-performOn($el, ['see' => 'text']);
-```
-
-List of actions can be pragmatically build using `Codeception\Util\ActionSequence`:
-
-```php
-performOn('.model', ActionSequence::build()
- ->see('Warning')
- ->see('Are you sure you want to delete this?')
- ->click('Yes')
-);
-```
-
-Actions executed from array or ActionSequence will print debug output for actions, and adds an action name to
-exception on failure.
-
-Whenever you need to define more actions a callback can be used. A WebDriver module is passed for argument:
-
-```php
-performOn('.rememberMe', function (WebDriver $I) {
- $I->see('Remember me next time');
- $I->seeElement('#LoginForm_rememberMe');
- $I->dontSee('Login');
-});
-```
-
-In 3rd argument you can set number a seconds to wait for element to appear
-
- * `param` $element
- * `param` $actions
- * `param int` $timeout
-
-### pressKey
-
-Presses the given key on the given element.
-To specify a character and modifier (e.g. ctrl, alt, shift, meta), pass an array for $char with
-the modifier as the first element and the character as the second.
-For special keys use key constants from WebDriverKeys class.
-
-``` php
-
-$I->pressKey('#page','a'); // => olda
-$I->pressKey('#page',array('ctrl','a'),'new'); //=> new
-$I->pressKey('#page',array('shift','111'),'1','x'); //=> old!!!1x
-$I->pressKey('descendant-or-self::*[@id='page']','u'); //=> oldu
-$I->pressKey('#name', array('ctrl', 'a'), \Facebook\WebDriver\WebDriverKeys::DELETE); //=>''
-?>
-```
-
- * `param` $element
- * `param` $char string|array Can be char or array with modifier. You can provide several chars.
-@throws \Codeception\Exception\ElementNotFound
-
-### reloadPage
-
-Reloads the current page.
-
-### resetCookie
-
-Unsets cookie with the given name.
-You can set additional cookie params like `domain`, `path` in array passed as last argument.
-
- * `param` $cookie
-
- * `param array` $params
-
-### resizeWindow
-
-Resize the current window.
-
-``` php
-resizeWindow(800, 600);
-
-```
-
- * `param int` $width
- * `param int` $height
-
-### saveSessionSnapshot
-
- * `param string` $name
-
-### scrollTo
-
-Move to the middle of the given element matched by the given locator.
-Extra shift, calculated from the top-left corner of the element,
-can be set by passing $offsetX and $offsetY parameters.
-
-``` php
-scrollTo(['css' => '.checkout'], 20, 50);
-?>
-```
-
- * `param` $selector
- * `param int` $offsetX
- * `param int` $offsetY
-
-### see
-
-Checks that the current page contains the given string (case insensitive).
-
-You can specify a specific HTML element (via CSS or XPath) as the second
-parameter to only search within that element.
-
-``` php
-see('Logout'); // I can suppose user is logged in
-$I->see('Sign Up', 'h1'); // I can suppose it's a signup page
-$I->see('Sign Up', '//body/h1'); // with XPath
-$I->see('Sign Up', ['css' => 'body h1']); // with strict CSS locator
-```
-
-Note that the search is done after stripping all HTML tags from the body,
-so `$I->see('strong')` will return true for strings like:
-
- - `
I am Stronger than thou
`
- - ``
-
-But will *not* be true for strings like:
-
- - `Home`
- - `
Home`
- - ``
-
-For checking the raw source code, use `seeInSource()`.
-
- * `param string` $text
- * `param string` $selector optional
-
-### seeCheckboxIsChecked
-
-Checks that the specified checkbox is checked.
-
-``` php
-seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms
-$I->seeCheckboxIsChecked('#signup_form input[type=checkbox]'); // I suppose user agreed to terms, If there is only one checkbox in form.
-$I->seeCheckboxIsChecked('//form/input[@type=checkbox and @name=agree]');
-?>
-```
-
- * `param` $checkbox
-
-### seeCookie
-
-Checks that a cookie with the given name is set.
-You can set additional cookie params like `domain`, `path` as array passed in last argument.
-
-``` php
-seeCookie('PHPSESSID');
-?>
-```
-
- * `param` $cookie
- * `param array` $params
-
-### seeCurrentUrlEquals
-
-Checks that the current URL is equal to the given string.
-Unlike `seeInCurrentUrl`, this only matches the full URL.
-
-``` php
-seeCurrentUrlEquals('/');
-?>
-```
-
- * `param string` $uri
-
-### seeCurrentUrlMatches
-
-Checks that the current URL matches the given regular expression.
-
-``` php
-seeCurrentUrlMatches('~$/users/(\d+)~');
-?>
-```
-
- * `param string` $uri
-
-### seeElement
-
-Checks that the given element exists on the page and is visible.
-You can also specify expected attributes of this element.
-
-``` php
-seeElement('.error');
-$I->seeElement('//form/input[1]');
-$I->seeElement('input', ['name' => 'login']);
-$I->seeElement('input', ['value' => '123456']);
-
-// strict locator in first arg, attributes in second
-$I->seeElement(['css' => 'form input'], ['name' => 'login']);
-?>
-```
-
- * `param` $selector
- * `param array` $attributes
-@return
-
-### seeElementInDOM
-
-Checks that the given element exists on the page, even it is invisible.
-
-``` php
-seeElementInDOM('//form/input[type=hidden]');
-?>
-```
-
- * `param` $selector
- * `param array` $attributes
-
-### seeInCurrentUrl
-
-Checks that current URI contains the given string.
-
-``` php
-seeInCurrentUrl('home');
-// to match: /users/1
-$I->seeInCurrentUrl('/users/');
-?>
-```
-
- * `param string` $uri
-
-### seeInField
-
-Checks that the given input field or textarea *equals* (i.e. not just contains) the given value.
-Fields are matched by label text, the "name" attribute, CSS, or XPath.
-
-``` php
-seeInField('Body','Type your comment here');
-$I->seeInField('form textarea[name=body]','Type your comment here');
-$I->seeInField('form input[type=hidden]','hidden_value');
-$I->seeInField('#searchform input','Search');
-$I->seeInField('//form/*[@name=search]','Search');
-$I->seeInField(['name' => 'search'], 'Search');
-?>
-```
-
- * `param` $field
- * `param` $value
-
-### seeInFormFields
-
-Checks if the array of form parameters (name => value) are set on the form matched with the
-passed selector.
-
-``` php
-seeInFormFields('form[name=myform]', [
- 'input1' => 'value',
- 'input2' => 'other value',
-]);
-?>
-```
-
-For multi-select elements, or to check values of multiple elements with the same name, an
-array may be passed:
-
-``` php
-seeInFormFields('.form-class', [
- 'multiselect' => [
- 'value1',
- 'value2',
- ],
- 'checkbox[]' => [
- 'a checked value',
- 'another checked value',
- ],
-]);
-?>
-```
-
-Additionally, checkbox values can be checked with a boolean.
-
-``` php
-seeInFormFields('#form-id', [
- 'checkbox1' => true, // passes if checked
- 'checkbox2' => false, // passes if unchecked
-]);
-?>
-```
-
-Pair this with submitForm for quick testing magic.
-
-``` php
- 'value',
- 'field2' => 'another value',
- 'checkbox1' => true,
- // ...
-];
-$I->submitForm('//form[@id=my-form]', $form, 'submitButton');
-// $I->amOnPage('/path/to/form-page') may be needed
-$I->seeInFormFields('//form[@id=my-form]', $form);
-?>
-```
-
- * `param` $formSelector
- * `param` $params
-
-### seeInPageSource
-
-Checks that the page source contains the given string.
-
-```php
-seeInPageSource('seeInSource('
Green eggs & ham
');
-```
-
- * `param` $raw
-
-### seeInTitle
-
-Checks that the page title contains the given string.
-
-``` php
-seeInTitle('Blog - Post #1');
-?>
-```
-
- * `param` $title
-
-### seeLink
-
-Checks that there's a link with the specified text.
-Give a full URL as the second parameter to match links with that exact URL.
-
-``` php
-seeLink('Logout'); // matches Logout
-$I->seeLink('Logout','/logout'); // matches Logout
-?>
-```
-
- * `param string` $text
- * `param string` $url optional
-
-### seeNumberOfElements
-
-Checks that there are a certain number of elements matched by the given locator on the page.
-
-``` php
-seeNumberOfElements('tr', 10);
-$I->seeNumberOfElements('tr', [0,10]); // between 0 and 10 elements
-?>
-```
- * `param` $selector
- * `param mixed` $expected int or int[]
-
-### seeNumberOfElementsInDOM
-__not documented__
-
-### seeOptionIsSelected
-
-Checks that the given option is selected.
-
-``` php
-seeOptionIsSelected('#form input[name=payment]', 'Visa');
-?>
-```
-
- * `param` $selector
- * `param` $optionText
-
-### selectOption
-
-Selects an option in a select tag or in radio button group.
-
-``` php
-selectOption('form select[name=account]', 'Premium');
-$I->selectOption('form input[name=payment]', 'Monthly');
-$I->selectOption('//form/select[@name=account]', 'Monthly');
-?>
-```
-
-Provide an array for the second argument to select multiple options:
-
-``` php
-selectOption('Which OS do you use?', array('Windows','Linux'));
-?>
-```
-
-Or provide an associative array for the second argument to specifically define which selection method should be used:
-
-``` php
-selectOption('Which OS do you use?', array('text' => 'Windows')); // Only search by text 'Windows'
-$I->selectOption('Which OS do you use?', array('value' => 'windows')); // Only search by value 'windows'
-?>
-```
-
- * `param` $select
- * `param` $option
-
-### setCookie
-
-Sets a cookie with the given name and value.
-You can set additional cookie params like `domain`, `path`, `expires`, `secure` in array passed as last argument.
-
-``` php
-setCookie('PHPSESSID', 'el4ukv0kqbvoirg7nkp4dncpk3');
-?>
-```
-
- * `param` $name
- * `param` $val
- * `param array` $params
-
-### submitForm
-
-Submits the given form on the page, optionally with the given form
-values. Give the form fields values as an array. Note that hidden fields
-can't be accessed.
-
-Skipped fields will be filled by their values from the page.
-You don't need to click the 'Submit' button afterwards.
-This command itself triggers the request to form's action.
-
-You can optionally specify what button's value to include
-in the request with the last parameter as an alternative to
-explicitly setting its value in the second parameter, as
-button values are not otherwise included in the request.
-
-Examples:
-
-``` php
-submitForm('#login', [
- 'login' => 'davert',
- 'password' => '123456'
-]);
-// or
-$I->submitForm('#login', [
- 'login' => 'davert',
- 'password' => '123456'
-], 'submitButtonName');
-
-```
-
-For example, given this sample "Sign Up" form:
-
-``` html
-
-```
-
-You could write the following to submit it:
-
-``` php
-submitForm(
- '#userForm',
- [
- 'user[login]' => 'Davert',
- 'user[password]' => '123456',
- 'user[agree]' => true
- ],
- 'submitButton'
-);
-```
-Note that "2" will be the submitted value for the "plan" field, as it is
-the selected option.
-
-Also note that this differs from PhpBrowser, in that
-```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment.
-Named array keys *must* be included in the name as above.
-
-Pair this with seeInFormFields for quick testing magic.
-
-``` php
- 'value',
- 'field2' => 'another value',
- 'checkbox1' => true,
- // ...
-];
-$I->submitForm('//form[@id=my-form]', $form, 'submitButton');
-// $I->amOnPage('/path/to/form-page') may be needed
-$I->seeInFormFields('//form[@id=my-form]', $form);
-?>
-```
-
-Parameter values must be set to arrays for multiple input fields
-of the same name, or multi-select combo boxes. For checkboxes,
-either the string value can be used, or boolean values which will
-be replaced by the checkbox's value in the DOM.
-
-``` php
-submitForm('#my-form', [
- 'field1' => 'value',
- 'checkbox' => [
- 'value of first checkbox',
- 'value of second checkbox,
- ],
- 'otherCheckboxes' => [
- true,
- false,
- false
- ],
- 'multiselect' => [
- 'first option value',
- 'second option value'
- ]
-]);
-?>
-```
-
-Mixing string and boolean values for a checkbox's value is not supported
-and may produce unexpected results.
-
-Field names ending in "[]" must be passed without the trailing square
-bracket characters, and must contain an array for its value. This allows
-submitting multiple values with the same name, consider:
-
-```php
-$I->submitForm('#my-form', [
- 'field[]' => 'value',
- 'field[]' => 'another value', // 'field[]' is already a defined key
-]);
-```
-
-The solution is to pass an array value:
-
-```php
-// this way both values are submitted
-$I->submitForm('#my-form', [
- 'field' => [
- 'value',
- 'another value',
- ]
-]);
-```
-
-The `$button` parameter can be either a string, an array or an instance
-of Facebook\WebDriver\WebDriverBy. When it is a string, the
-button will be found by its "name" attribute. If $button is an
-array then it will be treated as a strict selector and a WebDriverBy
-will be used verbatim.
-
-For example, given the following HTML:
-
-``` html
-
-```
-
-`$button` could be any one of the following:
- - 'submitButton'
- - ['name' => 'submitButton']
- - WebDriverBy::name('submitButton')
-
- * `param` $selector
- * `param` $params
- * `param` $button
-
-### switchToIFrame
-
-Switch to another frame on the page.
-
-Example:
-``` html
-