Codeception

Entwicklungshilfe

entwicklungshilfe.nrw / @help_for_devs / FB/entwicklungshilfe.nrw
Black White EH

Codeception

  • Why codeception
  • Installation
  • Tests
  • Reports
  • Extended

Why codeception

Codeception cost of a bug

Benefits of automated testing

70% faster than manuel testing

Wider test coverage of application features

Saves time and costs

Improves accuracy

Increases efficiency

Better speed in executing tests

Repeatable

Quality / Costs automated testing

Quality / Costs automated testing

Codeception is a testing framework

Acceptance

PHPUnit

Functional

API

Codeception Installation

Codeception composer installation

Installation Codeception

brew install composer

brew install codeception

brew install selenium-server-standalone

brew install chromedriver

brew install phantomjs

Starting from scratch

Codeception start tutorial

Starting from scratch

composer init
composer require --dev codeception/codeception
composer install

php ./vendor/bin/codecept bootstrap
php ./vendor/bin/codecept generate:cest acceptance Welcome

php ./vendor/bin/codecept generate:env phantom
php ./vendor/bin/codecept generate:env firefox
php ./vendor/bin/codecept generate:env chrome

codeception.yml root folder


actor: Tester
paths:
  tests: tests
  log: tests/_output
  data: tests/_data
  support: tests/_support
  envs: tests/_envs
settings:
  bootstrap: _bootstrap.php
  colors: true
  memory_limit: 1024M
extensions:
  enabled:
    - Codeception\Extension\RunFailed
modules:
  config:
    Db:
      dsn: 'mysql:host=127.0.0.1;dbname=hilfe_live'
      user: 'root'
      password: '1234'
      dump: tests/_data/dump.sql
      populate: true
      cleanup: true
                        

RunFailed: Saves failed tests into tests/log/_output/failed in order to rerun failed tests.
php codecept run -g failed

acceptance.suite.yml test folder


class_name: AcceptanceTester
  modules:
    enabled:
      - WebDriver
      - \Helper\Acceptance
    config:
        WebDriver:
          window_size: 1024x768
          url: 'http://typo3.org'
          browser: 'phantomjs'
    env:
      mobile:
        modules:
          config:
            WebDriver:
              window_size: 400x480
      chrome:
        modules:
          config:
            WebDriver:
              browser: 'chrome'
                        

merge --env mobile,chrome | seperate --env firefox --env chrome | best practice --env mobilefirefox

Tests

Codeception testing

Navigation cest


class navigationCest
{
    public function _before(Acceptance $I) {
        $I->amOnPage('/');
        $I->waitForElement('.header');
    }

    public function checkLinksToAnchor(Acceptance $I) {
        $links = $I->grabMultiple('.header-navigation > li > a', 'href');
        foreach ($links as $link) {
            $I->assertContains('#', $link, 'Link on main navigation contains #: ' . $link);
        }
    }
}
                            

RWD cest


public function resizeNavigation(Acceptance $I) {
    $burgerMenu = '.mobi-toggler'; $headerNavigation = '.header-navigation';
    $I->dontSeeElement($burgerMenu);
    $I->resizeWindow(400, 1000);
    $I->wait(1);
    $I->waitForElement($burgerMenu);
    $I->cantSeeElement($headerNavigation);
    $I->click($burgerMenu);
    $I->waitForElement($headerNavigation);
    $I->click($burgerMenu);
    $I->waitForElementNotVisible($headerNavigation);
    $I->resizeWindow(1600, 1000);
    $I->wait(1);
    $I->waitForElementNotVisible($burgerMenu);
}
                            

Wait for elements. On JS sometimes is easier to wait a second than to check changing of elements.

TYPO3 login


$I->wantTo('check if we can login to typo3 demo backend');
$I->amOnPage('/typo3/index.php');
$I->see('Login');
$I->wantTo('want to login');
$I->fillField('#t3-username', 'admin');
$I->fillField('#t3-password', 'password');
$I->click('Login');
$I->waitForElement('.nav');
$I->seeLink('Extension Manager');
                        

Use the deeplink sometimes on local env there are no .htaccess working.
Here you test the login and not the url handling.

Slider test cest


class ModuleMenuSliderCest {
  public function _before(Actor $I){ $I->loginAsAdmin(); }
  public function _after(Actor $I){ $I->logout(); }

  public function tryToTest(\AcceptanceTester $I) {
    $ids = ['#web', '#file', '#tools', '#system'];
    $sees = ['Page', 'Filelist', 'Extensions', 'Access'];
    $typo3Menu = '#typo3-menu';

    $I->wantTo('check the slider in the module menu');

    foreach ($ids as $id) {
      $I->waitForElement($id);

      // we close all
      $classString = $I->grabAttributeFrom($id, 'title');
      if (strpos($classString, 'expanded') !== false) {
        $I->click($id . ' > div');
        $I->wait(2);
      }
    }

    foreach ($sees as $see) {
      $I->cantSee($see);
    }

    // we open all
    foreach ($ids as $id) {
      $I->click($id . ' > div');
      $I->wait(2);
    }

    foreach ($sees as $see) {
      $I->see($see);
    }
  }
}
                    

Better to use cest from beginning. ModuleMenuSliderCest GitHub

Injection


class SignUpCest {
  /**
  * @var Helper\SignUp
  */
  protected $signUp;

  /**
  * @var Helper\NavBarHelper
  */
  protected $navBar;

  protected function _inject(\Helper\SignUp $signUp, \Helper\NavBar $navBar) {
    $this->signUp = $signUp;
    $this->navBar = $navBar;
  }

  public function signUp(\AcceptanceTester $I) {
    $I->wantTo('sign up');

    $this->navBar->click('Sign up');
    $this->signUp->register([
      'first_name'            => 'Joe',
      'last_name'             => 'Jones',
      'email'                 => 'joe@jones.com',
      'password'              => '1234',
      'password_confirmation' => '1234'
    ]);
  }
}
                  
Use helpers for the HTML selectors http://codeception.com/docs/07-AdvancedUsage

Advanced testing

Advanced testing codeception

Codeception wiki example url and database


$I = new AcceptanceTester($scenario);
$I->wantTo('create wiki page');
$I->amOnPage('/');
$I->click('Pages');
$I->click('New');
$I->see('New Page');
$I->fillField('title', 'Hobbit');
$I->fillField('body', 'By Peter Jackson');
$I->click('Save');
$I->see('page created'); // notice generated
$I->see('Hobbit','h1'); // head of page of is our title
$I->seeInCurrentUrl('pages/hobbit');
$I->seeInDatabase('pages', array('title' => 'Hobbit'));
                

http://codeception.com/

Codeception steps


namespace Step\Acceptance;
class Actor extends \AcceptanceTester
{
    public function loginAsAdmin() {
        $I = $this;
        $I->amOnPage('/admin');
        $I->fillField('username', 'admin');
        $I->fillField('password', '123456');
        $I->click('Login');
    }
}

class UserCest
{
    function showUserProfile(\Step\Acceptance\Actor $I) {
        $I->loginAsAdmin();
        $I->amOnPage('/admin/profile');
        $I->see('Admin Profile', 'h1');
    }
}

                

Use steps for functional code and for all other stuff you like Respect Readme

Respect validation example


// Respect
$userValidator = v::attribute('name', v::stringType()->length(1,32))
                  ->attribute('birthdate', v::date()->age(18));
$userValidator->validate($user); // true
                

Codeception page objects


namespace Page;
class Login
{
    public static $URL = '/login';
    public static $usernameField = '#mainForm #username';
    public static $passwordField = '#mainForm input[name=password]';
    public static $loginButton = '#mainForm input[type=submit]';
}

class UserCest
{
    function showUserProfile(AcceptanceTester $I, \Page\Login $loginPage) {
        $I->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');
    }
}
                

Use page objects for your global html selectors

Run commands

Codeception run commands

Run commands

Run selenium
selenium-server -p 4444

Add test
php ./vendor/bin/codecept generate:cest acceptance Startpage/Slider

All tests acceptance
php ./vendor/bin/codecept run acceptance
Single test
php ./vendor/bin/codecept run acceptance Startpage/SliderCest

Params
--env firefox --env chrome | --env mobile,firefox | --env mobilechrome
--steps --xml --html -vvv

Codeception features

Codeception features

Recorder

Codeception recorder

http://codeception1.rssing.com/chan-5825079/all_p11.html

Codeception screenshot


$scenario->current('name');    // returns current test name
$scenario->current('modules'); // returns current modules
$scenario->current('env');     // returns environment

$I->makeScreenshot($scenario->current('env').'_screenshot_imprint.png');
// saved to: tests/_output/debug/firefox_screenshot_imprint.png
                

See elements


$I->seeInSource('

Green eggs & ham

'); $I->seeInTitle('Blog - Post #1'); $I->seeLink('Logout'); // matches Logout $I->seeLink('Logout','/logout'); // matches Logout $I->seeNumberOfElements('tr', 10); $I->seeNumberOfElements('tr', [0,10]); //between 0 and 10 elements $I->seeOptionIsSelected('#form input[name=payment]', 'Visa');

Dont see elements


$I->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->dontSeeCurrentUrlEquals('/'); // current url is not root
$I->dontSeeCurrentUrlMatches('~$/users/(\d+)~'); // to match root url
$I->dontSeeElement('.error');
$I->dontSeeElement('//form/input[1]');
$I->dontSeeElement('input', ['name' => 'login']);
$I->dontSeeElement('input', ['value' => '123456']);
                

Shorter syntax


$I->wantTo('log in as regular user');
$I->amOnPage('/login');
$I->fillField('Username','davert');
$I->fillField('Password','qwerty');
$I->click('Login');
$I->see('Hello, davert');

$I->fillField('form#login input[name=login]','davert');
$I->fillField('form#login input[name=password]','qwerty');
$I->click('form#login input[type=submit]');

$I->submitForm('form#login', array('login' => 'davert', 'password' => 'qwerty'));
                

http://codeception1.rssing.com/chan-5825079/all_p1.html

Codeception upload files


$I->attachFile('input[ * `type="file"]',`  'prices.xls');
                

http://codeception.com/docs/modules/WebDriver

Codeception checkboxes


$I->dontSeeCheckboxIsChecked('#agree');

// I suppose user agreed to terms
$I->seeCheckboxIsChecked('#agree');
// I suppose user agreed to terms, If there is only one checkbox in form.
$I->seeCheckboxIsChecked('#signup_form input[type=checkbox]');
$I->seeCheckboxIsChecked('//form/input[ * `type=checkbox`  and  * `name=agree]');`
                

http://codeception.com/docs/modules/WebDriver

Codeception execute js


                    $myVar = $I->executeJS('return $("#myField").val()');
                

http://codeception.com/docs/modules/WebDriver

Sometimes you have more power with javascript

Codeception grap


$I->grabAttributeFrom('#tooltip', 'title');
$user_id = $I->grabFromCurrentUrl('~$/user/(\d+)/~');
$uri = $I->grabFromCurrentUrl();

$heading = $I->grabTextFrom('h1');
$name = $I->grabValueFrom('Name');
$name = $I->grabValueFrom('input[name=username]');

First
Second
// would return ['First', 'Second']
$aLinkText = $I->grabMultiple('a');
// would return ['#first', '#second']
$aLinks = $I->grabMultiple('a', 'href');
                

Codeception resize


$I->resizeWindow(800, 600);
                

Codeception cookie


$I->setCookie('auth', '123345');
$I->grabCookie('auth');
$I->seeCookie('auth');
$I->resetCookie("test");
                

Codeception Iframes and window


# switch to iframe
$I->switchToIFrame("another_frame");
# switch to parent page
$I->switchToIFrame();

$I->click("Open window");
# switch to another window
$I->switchToWindow("another_window");
# switch to parent window
$I->switchToWindow();
                

Codeception wait


$I->waitForElement('#agree_button', 30); // secs

use \Facebook\WebDriver\WebDriverElement
$I->waitForElementChange('#menu', function(WebDriverElement $el) {
  return $el->isDisplayed();
}, 100);

$I->waitForElementNotVisible('#agree_button', 30); // secs
$I->waitForElementVisible('#agree_button', 30); // secs

$I->waitForJS("return $.active == 0;", 60);

$I->waitForText('foo', 30, '.title'); // secs .title optional
                

Codeception reporting

Codeception reporting

Codeception reporting command line

php ./vendor/bin/codecept run
Codeception run all tests

Codeception reporting command line

php codecept.phar run acceptance --steps
Codeception run steps

Codeception reports

php codecept.phar run --steps --xml --html

Codeception report

Report .html .xml

report.html report.xml

Fail png

Codeception Autocomplete fail

Fail html



                
AutoCompleteCept.fail.html

Questions?

Clean Code are you to busy to improve

Sources

Images

http://blog.pdark.de/2012/07/21/software-development-costs-bugfixing/ https://styde.net/instalacion-de-codeception-con-composer-en-laravel/
https://pixabay.com
https://www.braune-digital.com/blog/schnelleinstieg-tests-mit-codeception-selenium-und-chrome/

Code

https://github.com/Entwicklungshilfe-NRW/codeception

Facts

http://www.guru99.com/automation-testing.html

Thanks

Follow us!