Codeception: Keep a logged in state

六眼飞鱼酱① 提交于 2019-11-29 11:49:22

问题


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);
    }

}

回答1:


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.




回答2:


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.




回答3:


<?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.




回答4:


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
}



回答5:


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();
}



回答6:


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



来源:https://stackoverflow.com/questions/20333240/codeception-keep-a-logged-in-state

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!