Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Mink Session inside FeatureContext

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

In search.feature:

... lines 1 - 9
When I fill in "searchTerm" with "<term>"
And I press "search_submit"
... lines 12 - 18

This searchTerm is the name attribute of the search box. And search_submit is the id of its submit button. Well, listen up y'all, I'm about to tell you one of the most important things about working with Behat: Almost every built-in definitions finds elements using the "named" selector, not CSS.

Cardinal Rule: Avoid CSS in your Scenarios

For example, look at the definition for

I fill in "field" with "value"

To use this, you should pass the label text to "field", not the id, name attribute or CSS selector. Clicking a link is the same. That's done with the

I follow "link"

Where the link must be the text of the link. If you pass a CSS selector, it's not going to work. If I changed search_submit to be a CSS selector, it'll fail. Believe me, I've tried it a bunch of times.

Got it? Ok: in reality, the named selector lets you cheat a little bit. In addition to the true "text" of a field, it also searches for the name attribute and the id attribute. That's why our scenario works.

But please please - don't use the name or id. In fact, The cardinal rule in Behat is that you should never use CSS selectors or other technical things in your scenario. Why? Because the person who is benefiting from the feature is a web user, and we're writing this from their point of view. A web user doesn't understand what searchTerm or search_submit means. That makes your scenario less useful: it's technical jargon instead of behavior descriptions.

So why did we cheat? Well, the search field doesn't have a label and the button doesn't have any text. I can't use the named selector to find these, unless I cheat.

Custom Mink Definition

Whenever you want to cheat or can only find something via CSS, there's a simple solution: use new language and create a custom definition. Change the first line to:

... lines 1 - 9
When I fill in the search box with "<term>"
... lines 11 - 18

If I can't target it with real text, I'll just use some natural language. PhpStorm highlights the line because we don't have a definition function matching this text. For the second problem line, use

... lines 1 - 10
And I press the search button
... lines 12 - 18

You know the drill: it's time to run the scenario. It prints out the two functions we need to fill in:

... lines 1 - 85
/**
* @When I fill in the search box with :arg1
*/
public function iFillInTheSearchBoxWith($arg1)
{
throw new PendingException();
}
/**
* @When I press the search button
*/
public function iPressTheSearchButton()
{
throw new PendingException();
}
... lines 101 - 102

Getting the Mink Session

Filling these in shouldn't be hard: we're pretty good with Mink. But, how can we access the Mink Session? There's a couple ways to get it, but the easiest is to make FeatureContext extend RawMinkContext:

... lines 1 - 6
use Behat\MinkExtension\Context\RawMinkContext;
... lines 9 - 13
class FeatureContext extends RawMinkContext implements Context, SnippetAcceptingContext
... lines 15 - 115

This gives us access to a bunch of functions: the most important being getSession() and another called visitPath() that we'll use later:

... lines 1 - 105
public function getSession($name = null)
{
return $this->getMink()->getSession($name);
}
... lines 111 - 128
public function visitPath($path, $sessionName = null)
{
$this->getSession($sessionName)->visit($this->locatePath($path));
}
... lines 133 - 166

On the first method, change arg1 to term:

... lines 1 - 86
/**
* @When I fill in the search box with :term
*/
public function iFillInTheSearchBoxWith($term)
... lines 91 - 115

Once you're inside of FeatureContext it's totally OK to use CSS selectors to get your work done.

Back in the browser, inspect the search box element. It doesn't have an id but it does have a name attribute - let's find it by that. Start with $searchBox = $this->getSession()->getPage(). Then, to drill down via CSS, add ->find('css', '[name="searchTerm"]');. I'm going to add an assertNotNull() in case the search box isn't found for some reason. Fill that in with $searchBox, 'The search box was not found':

... lines 1 - 91
$searchBox = $this->getSession()
->getPage()
->find('css', 'input[name="searchTerm"]');
assertNotNull($searchBox, 'Could not find the search box!');
... lines 97 - 115

Now that we have the individual element, we can take action on it with one of the cool functions that come with being an individual element, like attachFile, blur, check, click and doubleClick. One of them is setValue() that works for field. Set the value to $term.

... lines 1 - 97
$searchBox->setValue($term);
... lines 99 - 115

This is a perfect step definition: find an element and do something with it.

To press the search button, we can do the exact same thing. $button = $this->getSession()->getPage()->find('css', '#search_submit');. And assertNotNull($button, 'The search button could not be found'). It's always a good idea to code defensively. This time, use the press() method:

... lines 1 - 103
public function iPressTheSearchButton()
{
$button = $this->getSession()
->getPage()
->find('css', '#search_submit');
assertNotNull($button, 'Could not find the search button!');
$button->press();
}
... lines 114 - 115

We're ready to run the scenario again. It passes!

That was more work, but it's a better solution. With no CSS inside of our scenarios, they're less dependent on the markup on our site and this is a heck of a lot easier to understand than before with the cryptic name and ids.

Create a getPage() Shortcut

To save time in the future, create a private function getPage() and return $this->getSession()->getPage();:

... lines 1 - 112
/**
* @return \Behat\Mink\Element\DocumentElement
*/
private function getPage()
{
return $this->getSession()->getPage();
}
... lines 120 - 121

I'll put a little PHPDoc above this so next month we'll remember what this is.

Now we can shorten both definition functions a bit with $this->getPage():

... lines 1 - 89
public function iFillInTheSearchBoxWith($term)
{
$searchBox = $this->getPage()
->find('css', 'input[name="searchTerm"]');
... lines 94 - 97
}
... lines 99 - 102
public function iPressTheSearchButton()
{
$button = $this->getPage()
->find('css', '#search_submit');
... lines 107 - 110
}
... lines 112 - 121

Test the final scenarios out. Perfect! Now we have access to Mink inside of FeatureContext and we know that including CSS inside of scenarios is not the best way to make friends.

The assertSession

One more quick shortcut. Thanks to the RawMinkContext base class, we also have access to a cool object called WebAssert through the assertSession() method. Replace getPage() with assertSession() and find() with elementExists(). Now, remove the assertNotNull() call:

... lines 1 - 13
class FeatureContext extends RawMinkContext implements Context, SnippetAcceptingContext
{
... lines 16 - 89
public function iFillInTheSearchBoxWith($term)
{
$searchBox = $this->assertSession()
->elementExists('css', 'input[name="searchTerm"]');
$searchBox->setValue($term);
}
... lines 97 - 115
}

The elementExists finds the element and asserts that it exists all at once. Nice! Make the same changes for pressing the button:

... lines 1 - 100
public function iPressTheSearchButton()
{
$button = $this->assertSession()
->elementExists('css', '#search_submit');
$button->press();
}
... lines 108 - 117

The WebAssert class has a bunch of other handy methods on it - check them out.

Leave a comment!

4
Login or Register to join the conversation
Default user avatar

When i use MinkContext (Behat\MinkExtension\Context\MinkContext;) instead of RawMinkContext it works for me otherwise I get

FeatureContext has missing steps. Define them with these snippets:

/**
* @Given I am on :arg1
*/
public function iAmOn($arg1)
{
throw new PendingException();
}

/**
* @Then I should see :arg1
*/
public function iShouldSee($arg1)
{
throw new PendingException();
}

Reply

Hey Sumeet!

Actually, this makes *perfect* sense, and it illustrates the (only) difference between MinkContext and RawMinkContext:

A) RawMinkContext gives you access to the Mink session (via $this->getSession())
B) MinkContext also gives you access to the Mink session (via $this->getSession()). AND, it gives you a big list of built-in definitions (like Given I am on *, and Then I should see *).

If you look at MinkContext (https://github.com/Behat/Mi... you'll see that it extends RawMinkContext and then also adds a bunch of these "definitions".

In our tutorial, we use RawMinkContext... but in the next chapter (https://knpuniversity.com/s... we tell Behat to load *our* FeatureContext file and ALSO MinkContext. There's not really any big advantage of doing it this way (versus making your FeatureContext extend MinkContext), it's just 2 different ways of importing the definitions from MinkContext.

I hope that helps!

Cheers!

1 Reply
Default user avatar
Default user avatar lindsay macvean | posted 5 years ago

What do I do if there are 2 duplicate buttons on the page in different areas. How can I choose one over the other.

Reply

Hey Lindsay!

GREAT question - it's one of the trickiest things, but ultimately has an easy solution. First, there's basically an example here: https://knpuniversity.com/s.... Basically, the trick is to use CSS. You could use CSS to select the specific button, but I usually just use CSS to select some wrapper element that is unique to only *one* of the buttons. THEN, I call pressButton('Button Name') on that element.

I hope that helps!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses a very old version of Symfony. The fundamentals of Behat are still valid, but integration with Symfony will be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.4.0, <7.3.0",
        "symfony/symfony": "^2.7", // v2.7.4
        "twig/twig": "^1.22", // v1.22.1
        "sensio/framework-extra-bundle": "^3.0", // v3.0.16
        "doctrine/doctrine-bundle": "^1.5", // v1.5.1
        "doctrine/orm": "^2.5", // v2.5.1
        "doctrine/doctrine-fixtures-bundle": "^2.2", // v2.2.1
        "behat/symfony2-extension": "^2.0" // v2.0.0
    },
    "require-dev": {
        "behat/mink-extension": "^2.0", // v2.0.1
        "behat/mink-goutte-driver": "^1.1", // v1.1.0
        "behat/mink-selenium2-driver": "^1.2", // v1.2.0
        "phpunit/phpunit": "^4.8" // 4.8.18
    }
}
userVoice