Make CSS Selector (first-child) work in Behat 3 with Sahi Driver

2.6k views Asked by At

I was just wondering if you could point out where the problem is here, if it's Behat, the CSS selector, or the Sahi Driver.

We have just upgraded to Behat 3 and are using Sahi Driver (most recent open source version). We have found that any Behat test that uses the pseudo-element first-child now seems to break.

Example:

Step:

And I follow "#the-list tr:first-child .row-title"

(which contains an anchor element with the class row-title on it, see HTML)

Error:

Link with id|title|alt|text "#the-list tr:first-child .row-title" not found. (Behat\Mink\Exception\ElementNotFoundException)

HTML:

<tbody id="the-list">
    <tr id="post-162382" class="post-162382 type-post status-publish format-standard hentry category-uncategorized alternate iedit author-other level-0">
        <th class="check-column" scope="row"></th>
        <td class="post-title page-title column-title">
            <strong>
                <a class="row-title" title="Edit “Post Title”" href="https://domain.com"></a>
            </strong>
            <div class="locked-info"></div>
            <div class="row-actions"></div>
            <div id="inline_162382" class="hidden"></div>
        </td>

CSSSelector.php (override we used with our old Behat, we left this file in)

/**
 * Makes all of the form field related steps allow CSS selectors.
 */
class CSSSelectorContext extends MinkContext
{
    /**
     * Finds an element via CSS or fails with a given error
     * 
     * @param $element string   A CSS selector string
     * @param $error Exception  An error to throw in case the element can not be found
     * @return object           An Element object
     */
    protected function findOrFail($element, $error){
        $element = $this->getSession()->getPage()->find('css', $element);
        if (!isset($element)){     
            throw $error;
        }
        return $element;
    }

    public function clickLink($link) {
        try {
            parent::clickLink($link);
            return;
        }catch (Exception $error){
            $link = $this->fixStepArgument($link);
            $link = $this->findOrFail($link, $error);
            $link->press();
        }
    }

When using the css selector in the Chrome console with jquery it selects the appropriate element. I went through the code and looked at the css -> xpath translations and then validated the xpath against the html that is produced on the site we are testing and it seems to be valid as well. The css selector also works with Goutte driver.

Generated XPath:

    find(function(){
        var count = 0;
        while (_sahi._byXPath("("+"\/\/html\/descendant-or-self::*[@id = 'the-list']\/descendant-or-self::*\/*[name() = 'tr' and (position() = 1)]\/descendant-or-self::*\/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' row-title ')]"+")["+(count+1)+"]")) count++;
        return count;
    })()

descendant-or-self::*[@id = 'the-list']/descendant-or-self::*/*[name() = 'tr' and (position() = 1)]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' row-title ')]

//html/descendant-or-self::*[@id = 'the-list']/descendant-or-self::*/*[name() = 'tr' and (position() = 1)]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' row-title ')]

When I change the CSS to:

Step:

And I follow "#the-list tr .row-title"

It works because I believe it just picks the first tr from a list of them anyway, but we want to be able to use first-child of course.

Thanks for your help!

1

There are 1 answers

0
KyleFairns On

Sorry for being late to the party

Your problem here is the fact that Minks own step "I follow"/"clickLink" doesn't accept the following:

  1. "#"s for ID's
  2. Anything other than an ID, Text, Title or Alternate Value.

I suggest using a "click on the" step instead of a "follow", something like this:

    /**
     * @Then /^(?:|I )click (?:|on )(?:|the )"([^"]*)"(?:|.*)$/
     */
    public
    function iClickOn($arg1)
    {
        $findName = $this->getSession()->getPage()->find("css", $arg1);
        if (!$findName) {
            throw new Exception($arg1 . " could not be found");
        } else {
            $findName->click();
        }
    }

You're using CSS right here, which will allow for this to be written:

Then I click on the "#the-list tr:first-child .row-title" link

It is a mistake I also made, and this is the solution we decided on then, and we haven't had to look back.