Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

canSee - PHP Fatal error: Call to a member function addFailure() on a non-object #1981

Closed
wasmithee opened this issue May 27, 2015 · 9 comments
Labels

Comments

@wasmithee
Copy link

When a canSee assertion doesn't find the specified element it throws a PHP Fatal error: Call to a member function addFailure() on a non-object error.

* I can see "_QA_AUTO_DeptView","//*[@id="yw0"]//tr/th[text()="User Name"]/following-sibling::td"
  key/value: //*[@id="yw0"]//tr/th[text()="Account"]/following-sibling::td/BTT
* I can see "BTT","//*[@id="yw0"]//tr/th[text()="Account"]/following-sibling::td"
  key/value: //*[@id="yw0"]//tr/th[text()="Departments"]/following-sibling::td/BTT
* I can see "BTT","//*[@id="yw0"]//tr/th[text()="Departments"]/following-sibling::td"
PHP Fatal error:  Call to a member function addFailure() on a non-object in /home/will/Seafile/qa/NetBeansProjects/Codeception_Project/vendor/codeception/codeception/src/Codeception/TestCase/Shared/Actor.php on line 86

Perhaps I don't understand the intent of the function... I understood it to check for the existence of an element and then validate its text. If the object doesn't exist it should fail the assertion, but not stop the test, much less throw a fatal exception.

Am I misunderstanding this behavior, or is this a bug?

Here's a code snippet from my test:

...
        // Validate the user details table
        foreach ($userTableValidationValues as $key => $value) {
            if (!$key == NULL) {
                \Codeception\Util\Debug::debug('key/value: ' . $key . '/' . $value);
                $I->canSee($value, $key);
            }
        }
...
@zbateson
Copy link
Member

Hi @wasmithee - this was fixed in #1834 and released in 2.0.12.

@wasmithee
Copy link
Author

This error is occurring in Codeception version 2.1.0-beta.

@zbateson
Copy link
Member

@wasmithee aah, I guess it wasn't merged into the 2.1 branch.

@DavertMik not sure what your process is normally, do you rebase to 2.0 eventually?

@zbateson zbateson reopened this May 27, 2015
@zbateson zbateson added the 2.1 label May 27, 2015
@DavertMik
Copy link
Member

Everything from 2.0.13 was merged into master. Probably that was bad merge (

@zbateson
Copy link
Member

Hmm... all is looking okay to me in 2.1, and the can/cant tests we have are passing.

@wasmithee if you have time to debug it and see what's causing the issue that would be great... otherwise I'll need to find a way to cause the issue to happen first since it's not failing in the tests we have.

@wasmithee
Copy link
Author

I created a project that reproduces the issue. It appears that this bug doen't reproduce in the first test that is run, but the subsequent test. I wonder if it doen't have something to do with the session obeject or something else related to the objects that persist between tests. The odd thing is that the passing validations work in the second test, just when one fails due to an object not being found.

I do not see a way to attach a zip file with code. So I will paste what I think are the pertinent portions here:
FatalErrorTestCest.php

<?php

//use \AcceptanceTester;
use Step\Acceptance\UserSteps;

class FatalErrorTestCest {

    private $parametersArr_departmentAdmin = array(
        'username'      => '_PhpFatalError_adm',
        'password'      => 'foobar',
        'deptname'      => 'BTT Demo2',
        'email'         => 'PhpFatalError_adm@gmail.com',
        'status'        => 'Active',
        'firstname'     => 'First Name Text',
        'lastname'      => 'Last Name Text',
        'timezone'      => 'America/New_York',
        'granularity'   => '1.0',
        );
    private $parametersArr_departmentViewer = array(
        'username'      => '_PhpFatalError_view',
        'password'      => 'foobar',
        'deptname'      => 'BTT Demo2',
        'email'         => '_PhpFatalError_adm@gmail.com',
        'status'        => 'Active',
        'firstname'     => 'First Name Text',
        'lastname'      => 'Last Name Text',
        'timezone'      => 'America/New_York',
        'granularity'   => '1.0',
        );

    protected function login_DeptAdmin(UserSteps $I) {
        // if snapshot exists - skip login
        if($I->loadSessionSnapshot('logged_in')){
            return;
        }
        $I->login($this->parametersArr_departmentAdmin['username'], $this->parametersArr_departmentAdmin['password']);        
        $I->saveSessionSnapshot('logged_in');
    }

    // tests
    /**
     * @param UserSteps $I
     * @group fatalerrortest
     * @before login_DeptAdmin
     */
    public function validateDeptAdmin(UserSteps $I) {
        $I->amGoingTo('Validate a department administrator user details.');
        $I->selectMainMenuItem('Users', 'Manage Users');
        $I->viewUserDetails($this->parametersArr_departmentAdmin['username']);        
        $I->validateUserDetailTable($this->parametersArr_departmentAdmin);
    }

    /**
     * @param UserSteps $I
     * @group fatalerrortest
     * @before login_DeptAdmin
     */
    public function validateDeptViewer(UserSteps $I) {
        $I->amGoingTo('Validate a department viewer user details.');
        $I->selectMainMenuItem('Users', 'Manage Users');
        $I->viewUserDetails($this->parametersArr_departmentViewer['username']);        
        $I->validateUserDetailTable($this->parametersArr_departmentViewer);
    }

}

acceptance.suite.yml

class_name: AcceptanceTester
paths:
    # where the modules stored
    tests: tests

    # directory for fixture data    
    data: tests/_data

    # directory for custom modules (helpers)
    helpers: tests/_support

settings:
    # name of bootstrap that will be used
    # each bootstrap file should be 
    # inside a suite directory.
    bootstrap: _bootstrap.php

modules:
    enabled:
        - WebDriver
#        - AcceptanceHelper
    config:
        WebDriver:
            url: 'http://portalqa.bluetriangletech.com'
            window_size: 'maximize'
            browser: 'chrome'
            clear_cookies: true
#            wait: 1

env:
    phantom:
         modules:
            config:
                WebDriver:
                    browser: 'phantomjs'

    chrome:
         modules:
            config:
                WebDriver:
                    browser: 'chrome'

    firefox:
         modules:
            config:
                WebDriver:
                    browser: 'firefox' 

UserSteps.php

<?php

namespace Step\Acceptance;

use Page\LoginPage;
use Page\ManageUsers;
use Page\ViewUser;

//namespace AcceptanceTester;

class UserSteps extends \AcceptanceTester {

    /**
     * 
     * @param type $uid
     * @param type $pwd
     */
    public function login($uid, $pwd) {
        $I = $this;
        $I->amGoingTo('Log in using: UID=\'' . $uid . '\'; PWD=\'' . $pwd . '\'');
        $I->amOnPage(LoginPage::$URL);
        $I->fillField(LoginPage::$usernameField, $uid);
        $I->fillField(LoginPage::$passwordField, $pwd);
        $I->click(LoginPage::$loginButton);

        // Validate that the correct user has logged in; profile menu item shold match the user name;  we may have to adjust for case sensitivity here as it may fail if the case is differerent.
//        $I->comment($I->grabTextFrom('#nav > li > a[href$="user/profile"] > span'));
        $I->canSeeLink($uid, '/BTTPortal/index.php?r=user/profile');
//        $I->canSeeElement('#nav > li > a[href$="user/profile"] > span', ['textContent' => $uid]); // this doesn't work with PhpBrowser
    }

    public function viewUserDetails($uid) {
        $I = $this;
        $I->amGoingTo('VIew User Details for: ' . $uid . '\'');

        // Check to see if the current page is the Manage Users page
        if (!substr($I->grabFromCurrentUrl(), 0 - strlen(ManageUsers::$URL)) == ManageUsers::$URL) {
            $I->comment('    Navigating to the Manage Users page.');
            $I->selectMainMenuItem('Users', 'Manage Users');
        } else {
            $I->comment('    Already on the Manage Users page.');
        }

        // TODO: ADD LOGIC TO CHECK ON ALL PAGES... probably a while or a foreach logic to check all pages
        // Check to see if the User already exists
        $count = count($I->grabMultiple(['xpath' => "//table//a[.='" . $uid . "']"]));
//        $count = $I->executeInSelenium(function(\WebDriver $webdriver) use($uid) {
//            return count($webdriver->findElements(\WebDriverBy::linkText($uid)));
//        });
        if ($count > 0) {
            $I->comment('    User exists.');
            // Get the URL for the User to be viewed
            $href = $I->grabAttributeFrom(['xpath' => "//table//a[.='" . $uid . "']"], 'href');

            // Extract the user ID from the URL
            $query = parse_url($href, PHP_URL_QUERY);
            parse_str($query, $params);
            codecept_debug($params);
            $id = $params['id'];
            $I->comment('    User ID = ' . $id);

            // Check that the view icon button for this user is visible
            $I->SeeElement(['css' => 'a[href$="view&id=' . $id . '"] > img']);

            // Scroll to the bottom of the page (this is done so that the Groove support widget doesn't obscure the delete button
            $I->executeJS("window.scrollTo(0, document.body.scrollHeight);");

            // Click the element (view icon)
            $I->click(['css' => 'a[href$="view&id=' . $id . '"] > img']);
        } else {
            $I->comment("    User does not exist.");
        }
    }

    /**
     * @param type $mainMenuItem
     * @param type $subMenuItem
     */
    public function selectMainMenuItem($mainMenuItem, $subMenuItem = NULL) {
        $I = $this;
        $mmxpath = ['xpath' => '//*[@id="nav"]/li/a/span[text()="' . $mainMenuItem . '"]'];
//      $I->seeElement('#nav > li > a > span', ['textContent' => $mainMenuItem]); // this doesn't work with PhpBrowser
        $I->seeElement($mmxpath);
        if ($subMenuItem == NULL) {
//            $I->click($mainMenuItem);// this doesn't work with PhantomJS
            $I->click($mmxpath);
        } else {
            $smxapth = ['xpath' => '//*[@id="nav"]/li/ul/li/a/span[text()="' . $subMenuItem . '"]'];
//          $I->moveMouseOver('#nav > li > a > span', ['textContent' => $mainMenuItem]); // this doesn't work with PhpBrowser
            $I->moveMouseOver($mmxpath);
            //      $I->seeElement('#nav > li > a > span', ['textContent' => $subMenuItem]); // this doesn't work with PhpBrowser
            $I->seeElement($smxapth);
//            $I->click($subMenuItem);// this doesn't work with PhantomJS
            $I->click($smxapth);
        }
    }

    /**
     * @param type $parametersArr
     */
    public function validateUserDetailTable($parametersArr) {
        $I = $this;
        // Create array for validation
        $userTableValidationValues = $I->cust_array_combine(ViewUser::getUserDetailTableCol1(), $parametersArr);

        // Validate the user details table
        foreach ($userTableValidationValues as $key => $value) {
            if (!$key == NULL) {
                \Codeception\Util\Debug::debug('key/value: ' . $key . '/' . $value);
                $I->canSee($value, $key);
            }
        }
    }

    /**
     * @param type $keyArr
     * @param type $valArr
     * @return type
     */
    private function cust_array_combine($keyArr, $valArr) {
        $resultArr = [];
        foreach ($keyArr as $key => $value) {
            if (array_key_exists($key, $valArr)) {
                $resultArr[$value] = $valArr[$key];
            }
        }
        return $resultArr;
    }
}

LoginPage.php

<?php
namespace Page;

class LoginPage
{
    // include url of current page
    public static $URL = '/BTTPortal/index.php?r=user/login';

    /**
     * Declare UI map for this page here. CSS or XPath allowed.
     * public static $usernameField = '#username';
     * public static $formSubmitButton = "#mainForm input[type=submit]";
     */
    public static $usernameField      = '#UserLogin_username';
    public static $passwordField      = '#UserLogin_password';
    public static $loginButton        = 'input[type=submit]';
    public static $remeberMeChkbx     = '#UserLogin_rememberMe';
    public static $forgotPasswordLink = 'a[href$=\'recovery\']';

    /**
     * Basic route example for your current URL
     * You can append any additional parameter to URL
     * and use it in tests like: Page\Edit::route('/123-post');
     */
    public static function route($param)
    {
        return static::$URL.$param;
    }
}

ManageUsers.php

<?php

namespace Page;

class ManageUsers {

    // include url of current page
    public static $URL = '/BTTPortal/index.php?r=user/admin';

    /**
     * Declare UI map for this page here. CSS or XPath allowed.
     * public static $usernameField = '#username';
     * public static $formSubmitButton = "#mainForm input[type=submit]";
     */

    public static $usersTbl             = '#yw0 > table';
    public static $userstblHeaderRow    = '#yw0 > table > thead > tr';
//    public static $userstblBodyRows     = '#yw0 > table > tbody > tr';
    public static $userstblBodyRows     = '//*[@id=\'#yw0\']/descendant-or-self::table/tbody/tr';

    /**
     * Basic route example for your current URL
     * You can append any additional parameter to URL
     * and use it in tests like: Page\Edit::route('/123-post');
     */
    public static function route($param) {
        return static::$URL . $param;
    }
}

ViewUser.php

<?php

namespace Page;

class ViewUser {

    // include url of current page
    public static $URL = '/BTTPortal/index.php?r=user/admin/view&id=';

    /**
     * Declare UI map for this page here. CSS or XPath allowed.
     * public static $usernameField = '#username';
     * public static $formSubmitButton = "#mainForm input[type=submit]";
     */
    public static $viewUserHeading = '#content > div > h1';
    public static $userDetailTable = '#yw0';

    public static $usernamecell    = '//*[@id="yw0"]//tr/th[text()="User Name"]/following-sibling::td';
    public static $accountcell     = '//*[@id="yw0"]//tr/th[text()="Account"]/following-sibling::td';
    public static $deptnamecell    = '//*[@id="yw0"]//tr/th[text()="Departments"]/following-sibling::td';
    public static $emailcell       = '//*[@id="yw0"]//tr/th[text()="E-mail"]/following-sibling::td';
    public static $statuscell      = '//*[@id="yw0"]//tr/th[text()="Status"]/following-sibling::td';
    public static $firstnamecell   = '//*[@id="yw0"]//tr/th[text()="First Name"]/following-sibling::td';
    public static $lastnamecell    = '//*[@id="yw0"]//tr/th[text()="Last Name"]/following-sibling::td';
    public static $timezonecell    = '//*[@id="yw0"]//tr/th[text()="Time Zone"]/following-sibling::td';
    public static $granularitycell = '//*[@id="yw0"]//tr/th[text()="Granularity Default"]/following-sibling::td';
    public static $accesslevelcell = '//*[@id="yw0"]//tr/th[text()="Access Level"]/following-sibling::td';
    public static $whitelablecell  = '//*[@id="yw0"]//tr/th[text()="Whitelabel"]/following-sibling::td';

    /**
     * Basic route example for your current URL
     * You can append any additional parameter to URL
     * and use it in tests like: Page\Edit::route('/123-post');
     */
    public static function route($param) {
        return static::$URL . $param;
    }

    public static function getUserDetailTableCol1(){
        $userDetailTablecol1 = [
        'username'    => ViewUser::$usernamecell,
        'account'     => ViewUser::$accountcell,
        'deptname'    => ViewUser::$deptnamecell,
        'email'       => ViewUser::$emailcell,
        'status'      => ViewUser::$statuscell,
        'firstname'   => ViewUser::$firstnamecell,
        'lastname'    => ViewUser::$lastnamecell,
        'timezone'    => ViewUser::$timezonecell,
        'granularity' => ViewUser::$granularitycell,
        'accesslevel' => ViewUser::$accesslevelcell,
        'whitelable'  => ViewUser::$whitelablecell];

        return $userDetailTablecol1;
    }
}

@wasmithee
Copy link
Author

I appreciate both of your help @zbateson and @DavertMik. Please let me know if there is more information you need to reproduce this issue.

@zbateson zbateson added the Bug label Jun 4, 2015
@zbateson
Copy link
Member

zbateson commented Jun 4, 2015

I've narrowed this down so far to being related to using step objects. I'm able to reproduce it with two test methods in a single cest, each with a Step class as a parameter, and each calling a failing can (or cant), e.g. canSee directly. Looks like it's related to cleanup performed when Step objects are used (but not yet certain of that.)

@wasmithee
Copy link
Author

You guys rock! (@zbateson, @DavertMik)

Thanks for fixing this and being so responsive. So far I have no regrets choosing Codeception. Keep up the great work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants