Frage

I want to keep or run the login before most of my tests. But if I try to move the login code to _before it doesn't work since there is no webguy instance available to me.

What is the best way to keep the session between multiple tests? This is my code so far, would be glad to receive some help. I have googled and checked the documentation but I cannot find anything about session stuff.

<?php
use \WebGuy;

class ProductCest
{

    private $product_id = '1';

    public function _before()
    {
    }

    public function _after()
    {
    }

    // tests
    public function login(WebGuy $I) {
        $I->seeInCurrentUrl('/auth/login');
        $I->fillField("//input[@type='email']", "username@email.com");
        $I->fillField("//input[@type='password']", "1234");
        $I->click('#signIn .submit');
        $I->wait(500);

        $I->seeInCurrentUrl('/account');
    }

    /**
     * @depends login
     */
    public function chooseProduct(WebGuy $I) {
        $I->wantTo('go to products and choose one');
        $I->amOnPage('/?product=' . $this->client_id);
    }

}
War es hilfreich?

Lösung

I think that the accepted answer is a way of accomplish it, but not the best. Doing that you'll always have to reproduce all the steps to log in into the system, when you only need to share the user session. I think that grabbing the session cookie and pass it through the required user logged tests is better. To do that, you create a login function, get the cookie, and make your tests depends on the login test first:

<?php
use \AcceptanceTester;

class testingCest
{
    private $cookie = null;

    public function _before(AcceptanceTester $I)
    {
    }

    public function _after(AcceptanceTester $I)
    {
    }


    // tests
    public function login(AcceptanceTester $I)
    {
        $I->wantTo('entrar al sistema');
        $I->amOnPage('/');
        $I->seeInCurrentUrl('/admin/login');
        $I->fillField('user','perry');
        $I->fillField('pass','pass-perry');
        $I->click('Login');
        $I->see('You\'re logged!');
        $this->cookie   = $I->grabCookie('your-session-cookie-name');
    }

    /**
     * @depends login
     */
    public function listUsers(AcceptanceTester $I)
    {
        $I->setCookie( 'your-session-cookie-name', $this->cookie );
        $I->amOnPage('/admin/users');
        $I->seeInCurrentUrl('/admin/users/1');
    }

    /**
     * @depends login
     */
    public function listRols(AcceptanceTester $I)
    {
        $I->setCookie( 'your-session-cookie-name', $this->cookie );
        $I->amOnPage('/admin/rols');
        $I->seeInCurrentUrl('/admin/rols/1');
    }
}

That way, if the login test fails, you won't get the cookie, and you won't pass the other tests.

I prefer this @depends annotation instead of the @before proposed in the other answer, because if you use @depends you'll ALWAYS execute the code in the test before, and the tests will be only executed after the login.

UPDATE

There exists another answer bellow, https://stackoverflow.com/a/41109855/1168804 that may also help you, as the framework for Codeception has evolved since the writing of this answer.

Andere Tipps

All earlier answers are old, now it's done directly in _before method which takes Actor class as argument.

<?php

namespace Test\Api;

use ApiTester;

class TrainingCest
{
    public function _before(ApiTester $I)
    {
        $I->amLoggedInAs('kgkg');
    }

    public function _after(ApiTester $I)
    {
    }

    // tests
    public function testForLoggedInUser(ApiTester $I)
    {
    }

    public function anotherTestForLoggedInUser(ApiTester $I)
    {
    }

}

And if you want to log in just once for all CEST files, you can use global Registry class implementing Registry design pattern (see https://dzone.com/articles/practical-php-patterns/basic/practical-php-patterns-0) along with some lazyloading. Here is working code for my api integration tests defined in Actor class (in my case ApiTester):

public function amLoggedInAs($userLogin)
{
    $I = $this;

    if (Registry::getInstance()->exists($userLogin)) {
        // get data from registry
        $storedUserData = Registry::getInstance()->get($userLogin);
        $newAccessToken = $storedUserData['accessToken'];
        $playerId = $storedUserData['playerId'];
    }
    else {
        // no registry data - log in and save data in registry
        $I->tryToLogin($userLogin);

        $newAccessToken = $I->grabDataFromResponseByJsonPath('data.newToken');
        $playerId = (int)$I->grabDataFromResponseByJsonPath('data.userId');
        Registry::getInstance()->set($userLogin, [
            'accessToken' => $newAccessToken,
            'playerId' => $playerId
        ]);
    }

    // finally set headers and some other data
    $I->haveHttpHeader('X-Token', $newAccessToken);
    $I->havePlayerId($playerId);
}

protected function tryToLogin($userLogin)
{
    $I = $this;

    $I->wantTo('login into api');
    $I->amGoingTo('try to log to API using login and password');
    $I->sendPOST('/system/login', ['login' => $userLogin, 'password' => self::getPassword($userLogin)]);

    // ...some other checking if user was correctly logged in ...
}

This code basically stores accessToken along with some additional data in Registry after user logged for first time. If another call to $I->amLoggedInAs('kgkg') is invoked, it gets these values from registry. You can have many logged users this way, each of them logged only once per suite.

You can use another method for autorization instead of custom token, logic will still be the same.

Also if you're using WebDriver (not PhpBrowser), you can use loadSessionSnapshot and saveSessionSnapshot instead of Registry to get quite the same result.

Right now Codeception takes care of this thanks to saveSessionSnapshot and loadSessionSnapshot methods.

<?php
// inside AcceptanceTester class:

public function login()
{
     // if snapshot exists - skipping login
     if ($I->loadSessionSnapshot('login')) return;

     // logging in
     $I->amOnPage('/login');
     $I->fillField('name', 'jon');
     $I->fillField('password', '123345');
     $I->click('Login');

     // saving snapshot
     $I->saveSessionSnapshot('login');
}
?>

then in your test classes you just do it like this

public function _before(AcceptanceTester $I)
{
    $I->login();
}
<?php
use \WebGuy;

class ProductCest
{

    private $product_id = '1';

    public function _before()
    {
    }

    public function _after()
    {
    }

    private function executeLogin(WebGuy $I){
        $I->seeInCurrentUrl('/auth/login');
        $I->fillField("//input[@type='email']", "username@email.com");
        $I->fillField("//input[@type='password']", "1234");
        $I->click('#signIn .submit');
        $I->wait(500);
        return $I;
    }

    // tests
    public function login(WebGuy $I) {
        $I = $this->executeLogin($I);

        $I->seeInCurrentUrl('/account');
    }

    /**
     * @depends login
     */
    public function chooseProduct(WebGuy $I) {
        $I = $this->executeLogin($I);
        $I->wantTo('go to products and choose one');
        $I->amOnPage('/?product=' . $this->client_id);
    }
}

This should work.

I think @Sinisa answer is a "working" one, but not the "correct" one.

What you want is not @depends annotation, but @before.

The difference is that with @depends the current context is not kept, while using @before keep the context.

public function foo(WebGuy $I)
{
    $I->amOnPage('/foobar');
}

/**
 * @depends foo
 */
public function bar(WebGuy $I)
{
    $I->seeInCurrentUrl('foobar'); // Wil fail
}

/**
 * @before foo
 */
public function baz(WebGuy $I)
{
    $I->seeInCurrentUrl('foobar'); // Will pass
}

It should be noted that if you're testing WordPress, the WP-browser module has "sugar" methods for logging in:

loginAsAdmin();
loginAs($username, $password);

https://github.com/lucatume/wp-browser

Keep in mind, that if you work with SaveSessionSnapshot, you have to make sure that the function is called after your login is completely finished. Which means if your login takes some time (ldap-login or security checks) the SaveSessionSnapshot is executed before the login is finished and you save an empty session. If so you have to tell your programm to wait ($I->wait()), or check if the login is finished completly.

In 2021 in my case it was as simple as setting clear_cookies to false in .yml file..

Codeception deletes cookies between tests because you should stay logged in between tests as best practice.

But if you want to persist the cookie simply declare this:

actor: AcceptanceTester
modules:
    enabled:
        - WebDriver:
              clear_cookies: false
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top