Course Overview
BDD, Behat (version 2.5), Mink and other Wonderful Things
Ready to revolutionize the way you develop? Harness the power of BDD, Behat 2.5, and Mink, and upgrade your approach to coding.
About this course
If you own this tutorial and want to upgrade to the latest Behat 3 version, send us an email/contact message and we'll be happy to give you a coupon code to order the 3.0 version absolutely free :). Yay upgrading!
Ready to revolutionize how you develop? In this tutorial, you'll internalize Behavior-Driven Development and how thinking about behavior will increase the quality and efficiency of your code. You'll become an expert in Behat - the BDD PHP framework - and Mink - your key to testing web applications in sophisticated ways that include the ability to test your JavaScript pages. We'll also explore the pitfalls of testing with Behat, such as data sanitization, bootstrapping your code, and other challenges. By the end, you'll be well on your way to writing high-quality tests for your application and - more importantly - approaching development in a totally new light.
Behat works great with any PHP project, but if you're using Symfony2, we'll give you some special tips to get you rolling.
64 Comments
Hey Sai!
So you're using something like a custom jQuery date-picker? The solution depends. First, you'll of course need a custom step for this, e.g.:
And I fill in the "Birthday" date picker with "20/12/1985"
For the implementation, you have 2 choices. First, you'll find the correct field based on the label - something like this (assuming $fieldLabel is the value passed to your function for "Birthday"):
$field = $this->getSession()->getPage()->findField($fieldLabel);
Then, you have 2 choices:
1) Use a bunch of CSS to find different physical elements of your calendar and click those. In other words, do exactly what the user does - click some icon, then click to change the month, etc).
2) Use JavaScript directly. Most of the time, a date-picker has some JS API you can use to set the date. I'm inventing this API, but for example:
$this->getSession()->executeJavaScript(sprintf(
'$("#%s").datepicker("set", "%s");',
$field->attr('id'),
$dateString
);
I hope that helps!
Hi there! Is there any way to configure selenium to open Google Chrome, or Chromium, under Linux? Because I just cant do it work, and everytime selenium opens firefox to try a feature, it takes a lot!
Thanks for your help!
HI Pablo!
What issues are you having? I *have* gotten Chrome to work in Linux before, but sometimes using Chrome takes a bit more work. Specifically, you'll need the ChromeDriver (https://code.google.com/p/c.... If I remember correctly, you basically need to download this, execute it (it runs like a daemon, like Selenium), then also start Selenium in another terminal, then try things again.
Let me know if that works out! And if you have any errors, put them here!
Cheers!
Hi Ryan!
Actually, if you move the driver into /usr/local/bin/ it should work out-of-the-box, without starting the driver manually, just starting selenium.
I've followed the steps that you mentioned in your anwser, and it did work! So, i've tried moving it into /usr/local/bin/ again.. and did work this time! Finally, i've tried to execute the driver I was using until today and... it turned out that it was corrupted :S So that was the real problem :S
Thanks for your response! Appreciate!
Hi Pablo!
I'm glad you worked it out and thanks for posting the extra details here! Bad luck that it was corrupted :).
Cheers!
Kind of a pesky bit of feedback but since the FeatureContext seems to be constructed before evaluating the feature files and then again to run the tests your example code ends up creating, and changing into the 'test' directory twice… first time I checked this out to see what was going on I thought I was doing something wrong. I see the utility of using the constructor and then introducing the BeforeScenario tag, but as a curious novice it did give off a code smell seeing the consequence of using the constructor in that situation.
Hi Mark!
Haha, so it's a little teaching trick that maybe didn't work so well in this case :). The idea is that people understand the purpose of __construct, so it helps them get the idea that this is "setup" stuff. But of course, as you outlined perfectly with the double-instantiation, it's not the "right" way.
Anyways, I'm glad you figured it out - you probably also understand more about when a FeatureContext is instantiated than most people do. I've also added a small note to the script about this (https://github.com/knpunive....
Thanks!
I agree. You do a great job of showing the progression and getting hung up on the details is not important to learning the concepts. The comment you added in your commit should help anyone who goes step by step and likes to poke around like I did.
Here's a little error. In your script for the "behat" chapter you use "!array_seach(…)" which evaluates to false on the zero indexed item (since "0" will be cast as a boolean) — in the video you correctly used "array_searc(…) === false" so your test worked and passed just fine ;-)
Awesome, thanks for the head's up Mark - I've fixed it at https://github.com/knpunive...
Cheers!
array_search() seems to have a bug in it for any value which is in key '0' I am not sure how yours worked. ref :http://php.net/manual/en/fu...
Hi Vishal!
I don't think it's a bug - it's just that if you find something in the zero position, it returns 0, which == false. So you're right that the original code in the text never worked :) - the code in the actual video (and now in the code download) uses === false and works nicely now.
Cheers!
Everything worked!
Some points to consider in making everything easier:
1. If I change the font size in my Chrome browser, clicking on a new video position goes crazy. I struggled for a while with it.
2. Since the screencast has several sections and we need to go back, it would be great to have some section heading slides to clearly mark the beginning of a new section/task. Jumping directly to a section would also be great.
All in all it is a great effort and will surely have a huge impact in how we develop.
Hi Felipe!
Ah, thanks for the kind words and taking the time with the notes here :).
1. I have never seen this before, but you're right! In Firefox, changing the font size makes the video player act out. Not cool - I've added this as a bug in our tracker.
2. I agree - it would be helpful to leverage the different sections (which already exist) to organize things even better. If I understand you correctly, you're proposing showing a 1-2 second "slide" in the middle of the video as you move from section to section. Is that right? I'd also like to mark the different sections on the video or have an in-video TOC that's clickable - it's something we have on our minds :).
Any other thoughts are always warmly appreciated.
Thanks again!
Will these procedures still work with Behat 3, despite their recent changes? Looking for a few good Behat tutorials but want to make sure they work with Behat 3
Hi there!
For the most part, yes. The vast majority of things are not changing: the scenarios, philosophy, mapping to functions, Mink, etc. There will be some changes - setup/configuration, subcontexts and chained steps - but these are small pieces of the full Behat picture :). We'll also upgrade the tutorial once Behat 3 is stable, and you can come back then to see those updates :).
I hope that helps!
Hey Ryan, I've read and seen a lot about TDD and BDD now. My question is, how do I start now implementing a new feature. Let's say I have a dvd store and want to be able to add new dvds to the inventar. This process requires maybe different methods. My approach would be:
[1] Create BDD behat scenario / functional test for adding a new dvd
[2] Create a unit test with phpunit for each method that is required for adding a new dvd
[3] Create the actual code for adding a new dvd
For me, this would be a combination of BDD first and TDD afterwards? Or could you please explain me the correct "theoretical" way if I misunderstood something.
Regards, Patrick
Hey Patrick!
Theoretically, I think you've nailed it (though, you should take a look at PhpSpec for unit tests - we'll have a tutorial on that soon). However, I am not a huge fan of unit testing. I mean, yes, I do them - but only when I'm writing a function that "scares" me. In other words, if a function is pretty straightforward, then I rely on my Behat scenarios (tests) to see that the whole system is working. Your 1/2/3 order is perfect, but I would only do 2 when you feel you need to. Though, with PhpSpec, (2) becomes less about "testing" and more about "helping you design your classes", which is worthwhile on its own.
Cheers!
Nice, thanks I will have a look at phpspec and your upcoming screencast on it. Regards
Apparently, doing iAmLoggedIn functionality with:
$page = $this->getMink()->getSession()->getPage();
$username = $page->find('xpath', '//input[@id="username"]');
$username->setValue('Ryan');
$password = $page->find('xpath', '//input[@id="password"]');
$password->setValue('foobar');
$loginButton = $page->find('xpath', '//button[@id="login"]');
$loginButton->press();
would be much faster than
return array(
new Given('I am on "login"'),
new Given('I fill in "Username" with "Ryan"'),
new Given('I fill in "Password" with "foobar"'),
new Given('I press "Login"'),
);
It looks a bit more uglier and needs some knowledge about manipulating DOM but on my setup, with my own tests, I get 10-20 less execution time.
I'm using Behat/Mink + Sahi Driver
Hey Maksim!
That's very interesting! I haven't benchmarked this, but there could be 2 reasons:
1) Obviously, you skip the parsing of the English. This will take some time, but I'm not sure how much. It wouldn't seem like too much, but it could be.
2) The built-in code uses a big (but awesome) xpath statement internally to find the fields based on their label or a bunch of other things. Your xpath uses only the id, which is quite fast.
I'd be careful to not do too much of this, because you'll not have as much code-reuse and the xpaths used here aren't as flexible as the ones used internally. Plus, you're probably going to lose most of your time opening up a browser if your tests use JavaScript.
That being said, since logging in is really common, making a tweak like this could save time. I'd probably do it the easy way first, then modify things later if you want to. But regardless, this is an interesting comment!
Thanks!
Hi, everything worked, but the browser didn't load up. What am I missing here?
Hi there!
Hmm. If you're not running the Selenium server in the background, that would certainly cause the browser to not load. BUT, you'll also receive a huge error :).
If it *is* running and you're in Behat, make sure you have the `@javascript` above your scenario. If you're using Mink directly, make sure you're using the `Selenium2Driver`, not the `GoutteDriver` - one of these two is probably the cause if you don't see a big error.
Good luck!
You deleted the wrong question, and that was a legitimate one with the --dl query.
Great talk at DrupalCon Portland, Ryan! I saw lots of animated discussions afterwards. You lit some fires.
From one of the two people in the audience who knew who Leanna is.
I was at the DrupalconPDX talk also (front row w/glasses). I had to leave during the Q&A and did not get a chance to say thank you. One of the developers in my team was in the audience as well. He later told me that your talk clarified some concepts and as a result will alter the planned approach for an upcoming project. I'm sure that saved us some time. Great talk!
I was there at the talk as well. You have really inspired me to start using and practicing BDD with my applications (PHP and beyond), and to promote others to do the same as well. It definitely lit some fires under some of the dev's feet, as Dave had said. I'll also be talking a little bit about the talk in a company blog, so look out for it on http://www.activestate.com/...
From the other person in the audience who knew Leanna. :)
When I try to run, php bin/behat --dl, I get the error below:
[RuntimeException]
The "--dl" option does not exist.
behat [--init] [-f|--format="..."] [--out="..."] [--lang="..."] [--[no-]ansi] [--[no-]time] [--[no-]paths] [--[no-]snippets] [--[no-]snippets-paths] [--[no-]multiline] [--[no-]expand] [--story-syntax] [-d|--definitions="..."] [--name="..."] [--tags="..."] [--cache="..."] [--strict] [--dry-run] [--rerun="..."] [--append-snippets] [features]
Hi Femi Fet!
It's just *1* dash - "php bin/behat -dl". But that's my fault - I had a typo with two dashes in the intro chapter. I've added a quick issue, which I'll fix and deploy today: https://github.com/knpunive...
Thanks for asking the question!
Thank's for that Ryan. I'm kinda concerned if I might be asking too much questions. I'm not from a developer background, but I've also bought the PHP to help with the B§8§§ehat course. But right now I'm struggling with the method functions that helps identify Xpath of fields to edit, and click. I've tried the video and the PDF files but it seems like plenty of alternatives were tried and it looks distracting.
Can you please identify the best path to follow from the video and the pfd, e.g. page no and stage in video.
Thanks
Hi theMan
No worries :). The PDF download is simply a written version of what the spoken audio is in the video. So both are the same. But, I'm not sure if that's what you're talking about exactly. So that we can get you going, can you email me - ryan[at]knplabs.com?
Thanks!
I try to follow your example on searchTerm on a different website (wikipedia) but failed. Here is the error:
Scenario: Search for a word that exists # features/search.feature:6
Given I am on "/wiki/MainPage" # FeatureContext::visit()
When I fill in "searchTerm" with "Velociraptor" # FeatureContext::fillField()
Form field with id|name|label|value|placeholder "searchTerm" not found. (Behat\Mink\Exception\ElementNotFoundException)
Hey Andrew!
Try changing "searchTerm" by only "search", remember that behat will look up for any input field (id, name, label or value) that matches that text
Cheers!
Is there a list of keywords or objects to understand the functions of these?
Hey Andrew!
If you are using MinkContext, then you can use "print last response" step in your features, so you can check how looks like the HTML for the page you currently on. In your case you need to check what's the name of the search field in wiki main page
Cheers!
Anyone can tell me why I am getting this error:
composer require --dev behat/behat behat/mink-extension behat/mink-goutte-driver behat/mink-selenium2-driver
[Seld\JsonLint\ParsingException]
"./composer.json" does not contain valid JSON
Parse error on line 1:
"name": "andrew.pang2/goku
---------^
Expected one of: 'EOF', '}', ',', ']'
Hey Andrew!
JSON format is very picky, I think you are missing a "closing curly brace" but, could you show me how your composer.json file looks like ?
Cheers!
"name": "andrew.pang2/goku_automation",
"description": "Andrew automation suite",
"require": {
"behat/behat": "^3.3",
"behat/mink": "^1.7",
"behat/mink-selenium2-driver": "^1.3",
"behat/mink-zombie-driver": "^1.4",
"behat/mink-browserkit-driver": "^1.3",
"behat/mink-goutte-driver": "^1.2"
"behat/mink-extension": "*"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "vendor/bin/"
"authors": [
{
"name": "andrew.pang2",
"email": "andrew.pang2@workday.com"
}
]
}
Hey Andrew!
Look's like you are missing the starting curly brace and a couple of comas
This is how it should looks like (feel free to copy and paste it into your code, I just reformatted it)
{
"name": "andrew.pang2/goku_automation",
"description": "Andrew automation suite",
"require": {
"behat/behat": "^3.3",
"behat/mink": "^1.7",
"behat/mink-selenium2-driver": "^1.3",
"behat/mink-zombie-driver": "^1.4",
"behat/mink-browserkit-driver": "^1.3",
"behat/mink-goutte-driver": "^1.2",
"behat/mink-extension": "*"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "vendor/bin/",
"authors": [
{
"name": "andrew.pang2",
"email": "andrew.pang2@workday.com"
}
]
}
}
Cheers!
"name": "andrew.pang2/goku_automation",
"description": "Andrew automation suite",
"require": {
"behat/behat": "^3.3",
"behat/mink": "^1.7",
"behat/mink-selenium2-driver": "^1.3",
"behat/mink-zombie-driver": "^1.4",
"behat/mink-browserkit-driver": "^1.3",
"behat/mink-goutte-driver": "^1.2"
"behat/mink-extension": "*"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "vendor/bin/"
"authors": [
{
"name": "andrew.pang2",
"email": "andrew.pang2@workday.com"
}
]
}
Hi, Ryan!
Does it normal to use both PHPUnit and Behat tests in one project? Or better keep up only one testing framework for all tests?
I started to use Behat and began to wonder why I need a PHPUnit anymore. It seems a bit confused to use them both in one project, doesn't it?
Hey!
Yep, I *do* use both: Behat for functional tests and PHPUnit for unit tests. I end up writing mostly functional tests, so I use Behat quite a bit more. That may be how you're feeling as well :).
For anyone else using windows. behat-ls feature won't work for us. I created an issue on github and hopefully we can figure it out. :) https://github.com/knpunive...
I did find out the command in windows that works like rm -r try:
`system('rd /S /Q '.realpath('test'));`
It worked well for me.
I am a little disappointed with this screencast. It did teach a lot about behat and mink and I learned a lot. But this page https://knpuniversity.com/s... in the description it says if I am using Symfony2 there'll be special tips to get me rolling. There were none. Instead, the framework used was Silex, which is based on Symfony2, I understand, but I was expecting information directly with Symfony2.
I am also having a hell of a time getting PHPUnit to work with Behat. I commented out the require_once lines and then the tests don't run at all. I have PHPUnit installed and even gave the absolute path starting with the drive letter on my windows box and it doesn't seem to work at all. It just displays the Scenario line of the feature and doesn't move on. It doesn't even say that so many steps were run or anything.
I'm trying to select the date from datepicker/calendar for Date of Birth field. However, unable to do it. Can anyone suggest me with the possible working code for "selecting a required date from the calendar for DOB field.