Download - Behat: Beyond the Basics
![Page 1: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/1.jpg)
Behat: Beyond the Basics@jessicamauerhan
10-13-15 Dallas PHP User Group
http://joind.in/event/view/4808
![Page 2: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/2.jpg)
My Introduction to Behat
![Page 3: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/3.jpg)
Our Admin Panel
![Page 4: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/4.jpg)
Moving Forward
![Page 5: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/5.jpg)
Topics● Writing Better .feature Files
● Drivers
● Hooks
● Step Definitions
● Page Objects & Elements
● Unusual Interactions
![Page 6: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/6.jpg)
Code Examples● Mostly Behat 2.5
● Some Behat 3.0
![Page 7: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/7.jpg)
Writing Better .Feature FilesScenario: Visit Seminar Page before Broadcast Time Given I want to watch the video called "Future Seminar" When I visit that seminar's page Then I should see "Future Seminar" on the page And I should see "Future Seminar Author" on the page And I should see "This seminar begins at 6:00 pm EST" on the page And I should see a countdown timer
![Page 8: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/8.jpg)
“What is behavior-driven development, you ask? It’s the idea that you start by writing human-readable sentences that describe a feature of your application and how it should work, and only then implement this behavior in software.”
- Behat Documentation
![Page 9: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/9.jpg)
Writing Better .Feature FilesScenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled for the future When I visit that seminar's page Then I should see the seminar's name And I should see the seminar's author’s name And I should see "This seminar begins at" And I should see the seminar’s start time in EST And I should see a countdown timer
![Page 10: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/10.jpg)
Why?● Easier for Programmers to Understand
● Helps Prevent Regression
● Easier for Business Users to Understand
● Easier for Business Users to Write
● Can Identify Bad Code!
![Page 11: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/11.jpg)
How Better .Feature Files Can Identify Bad CodeOriginal:
Scenario: Display Local Services in Product Catalog Given I view the catalog When I select "Texas" from the states list Then I should see the list of services offered
Rewritten:
Scenario: Display Local Services in Product Catalog Given I view the catalog When I select a state from the states list Then I should see the list of services offered
![Page 12: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/12.jpg)
How Better .Feature Files Can Identify Bad CodeOriginal:
Scenario: Display Local Services in Product Catalog Given I view the catalog When I select "Texas" from the states list Then I should see the list of services offered
Rewritten:
Scenario: Display Local Services in Product Catalog Given we have a regional office offering local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state
![Page 13: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/13.jpg)
Feature Description - As A, In Order To, I Need...Feature: Display Texas-Specific Local Services As a company, we offer specific services only in Texas In order to sell these services to the right people We need to display the services when users are browsing our catalog for Texas
Scenario: Display Local Services When Texas is Selected Given I view the catalog When I select Texas from the states list Then I should see the list of services offered
![Page 14: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/14.jpg)
When I select Texas from the states list
/*** @When /^I select Texas from the states list$/*/public function iSelectTexasFromTheStatesList(){}
When I select "Texas" from the states list
/*** @When /^I select "([^"]*)" from the states list$/*/public function iSelectFromTheStatesList($arg1){}
A Clear Behavior
![Page 15: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/15.jpg)
Negative CasesScenario: Display Product Catalog Given I view the catalog When I select a state from the list Then I should see the list of products for sale in that state
Scenario: Display Local Services Given we have a regional office that offers local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state
![Page 16: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/16.jpg)
Negative Casesecho '<h1>Products</h1>';foreach ($products AS $product) { echo '<p>' . $product->getName() . '</p>';}
echo '<h1>Services</h1>';foreach ($services AS $service) { echo '<p>' . $service->getName() . '</p>';}
![Page 17: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/17.jpg)
Negative CasesScenario: Display Product Catalog Given I view the catalog When I select a state from the list Then I should see the list of products for sale in that state
Scenario: Display Local Services Given we have a regional office that offers local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state
Scenario: Don’t Display Local Services When No Regional Office Given a state has no regional office offering local services When I view the catalog And I select that state from the states list Then I should not see a list of services
![Page 18: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/18.jpg)
Negative Casescatalogecho '<h1>Products</h1>';foreach ($products AS $product) { echo '<p>' . $product->getName() . '</p>';}
if(count($services) > 0) { echo '<h1>Services</h1>'; foreach ($services AS $service) { echo '<p>' . $service->getName() . '</p>'; }}
![Page 19: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/19.jpg)
Gerkhin
![Page 20: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/20.jpg)
Auto-Generated StepsYou can implement step definitions for undefined steps with these snippets: /** * @When /^I select Texas from the states list$/ */ public function iSelectTexasFromTheStatesList() { throw new PendingException(); } /** * @Then /^I should see the list of services offered$/ */ public function iShouldSeeTheListOfServicesOffered() { throw new PendingException(); }
![Page 21: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/21.jpg)
GerkhinGiven: Set up
When: Action
Then: Outcome
But/And: More of the same...
![Page 22: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/22.jpg)
IDEs & Plugins
![Page 23: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/23.jpg)
Writing Better .Features● Don’t Make Assumptions
● Scenarios should run independently
● Follow the Flow: Given, When, Then
![Page 24: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/24.jpg)
Drivers
![Page 25: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/25.jpg)
Driver Capabilitieshttp://mink.behat.org/en/latest
- Drivers
- Driver Feature Support
![Page 26: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/26.jpg)
Hooks
![Page 27: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/27.jpg)
Capturing Screenshot on Error/** @AfterScenario */public function afterScenario($event){ if ($event->getResult() == Event\StepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); }}
![Page 28: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/28.jpg)
Hooks & Tags/** @AfterScenario @javascript */public function afterScenario($event){ if ($event->getResult() == Event\StepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); }}
![Page 29: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/29.jpg)
Hooks & Tags (Multiple Tags)<?php
/** @AfterScenario @javascript,@screenshot */public function afterScenario($event){ if ($event->getResult() == Event\StepEvent::FAILED) {
$imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents ($imagePath, $imageData);
}}
/** @AfterScenario @javascript,@screenshot*/public function afterScenario($event){ if ($event->getResult() == Event\StepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); }}
![Page 30: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/30.jpg)
Dealing with AJAX (jQuery & Angular)/** @BeforeStep @javascript */public function beforeStep($event){ $waitTime = 5000; $jqDefined = "return (typeof jQuery != 'undefined')" ; $active = '(0 === jQuery.active && 0 === jQuery( \':animated\').length)'; if ($this->getSession()->evaluateScript($jqDefined)) { $this->getSession()->wait($waitTime, $active); }}
//Angular: https://gist.github.com/jmauerhan/f839926ea527ff5e74e7
![Page 31: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/31.jpg)
Fixture Datanamespace Acme\AppBundle\DataFixtures;
use Doctrine\Common\Persistence\ObjectManager;use Doctrine\Common\DataFixtures\FixtureInterface;
class UserFixtureLoader implements FixtureInterface{ public function load(ObjectManager $manager) { $user = new User(); $user->setUsername('admin'); $user->setPassword('password');
$manager->persist($user); $manager->flush(); }}
![Page 32: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/32.jpg)
Load Fixture Data Hook/** @BeforeFeature */public function beforeFeatureReloadDatabase($event){ $loader = new Loader(); $directory = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'DataFixtures'; $loader->loadFromDirectory($directory);
$entityManager = $this->getEntityManager(); $purger = new ORMPurger(); $executor = new ORMExecutor($entityManager, $purger); $executor->execute($loader->getFixtures());}
![Page 33: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/33.jpg)
Steps
![Page 34: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/34.jpg)
Multiple Regular Expressions/*** @Given /^I view the catalog$/* @Given /^I am viewing the catalog$/*/public function iViewTheCatalog(){ $this->getPage('Catalog')->open();}
![Page 35: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/35.jpg)
Case Insensitive - FlagWhen I view the catalogWhen I view the Catalog
/*** @Given /^I view the catalog$/i*/public function iViewTheCatalog(){ $this->getPage('Catalog')->open();}
![Page 36: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/36.jpg)
Case Insensitive - InlineWhen I view the catalogWhen I view the Catalog
/*** @Given /^I view the (?i)catalog$/*/public function iViewTheCatalog(){ $this->getPage('Catalog')->open();}
![Page 37: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/37.jpg)
Unquoted VariablesThen I should see an "error" message
/*** @Given /^I should see an "([^"])" message$/*/public function iShouldSeeAnMessage($arg1){
}
![Page 38: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/38.jpg)
Unquoted VariablesThen I should see an error message
/*** @Given /^I should see an (.*) message$/*/public function iShouldSeeAnMessage($arg1){
}
![Page 39: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/39.jpg)
Unquoted Variables with List of OptionsThen I should see an error messageThen I should see a success messageThen I should see a warning message
/*** @Given /^I should see an? (error|success|warning) message$/*/public function iShouldSeeAnMessage($messageType){ $class = '.alert-'.$messageType; $this->assertElementExists($class, 'css');}
![Page 40: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/40.jpg)
Optional VariablesThen I should see an error messageThen I should see an error message that says " Stop!"
/*** @Given /^I should see an? (error|success|warning) message$/* @Given /^I should see an? (error|success|warning) message that says "([^"])"$/*/public function iShouldSeeAnMessageThatSays ($messageType, $message = null){ $class = '.alert -' . $messageType; $this->assertElementExists ($class, 'css'); if ($message !== null) { $this->assertElementContainsText ($class, 'css', $message); }}
![Page 41: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/41.jpg)
Non-Capturing GroupsThen I view the catalog for "Texas"Then I am viewing the catalog for "Texas"
/*** @Given /^I view the catalog for "([^"]*)"$/* @Given /^I am viewing the catalog "([^"]*)"$/*/public function iViewTheCatalogForState($stateName){ $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args);}
![Page 42: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/42.jpg)
Non-Capturing GroupsGiven I am viewing the catalog for “Texas”Given I viewing the catalog for “Texas”
<?php
/** * @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/ */public function iViewTheCatalogForState($stateName){ $this->getPage(‘Catalog’)->open([‘stateName’=>$stateName);}
Then I view the catalog for "Texas"Then I am viewing the catalog for "Texas"
/*** @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/*/public function iViewTheCatalogForState($stateName){ $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args);}
![Page 43: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/43.jpg)
Step Definition Changes in Behat 3.xGiven I am viewing the catalog for “Texas”Given I viewing the catalog for “Texas”
<?php
/** * @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/ */public function iViewTheCatalogForState($stateName){ $this->getPage(‘Catalog’)->open([‘stateName’=>$stateName);}
Then I view the catalog for "Texas"Then I view the catalog for Texas
/*** @Given I view the catalog for :stateName*/public function iViewTheCatalogForState($stateName){ $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args);}
![Page 44: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/44.jpg)
Contexts
![Page 45: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/45.jpg)
SubContext (Behat 2.x)<?php
namespace Acme\AppBundle\Context;
use Behat\MinkExtension\Context\MinkContext;
class FeatureContext extends MinkContext{ public function __construct(array $parameters) { $this->parameters = $parameters; $this->useContext('MessageContext', new MessageContext()); }}
![Page 46: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/46.jpg)
Several SubContexts (Behat 2.x)[...]
public function __construct(array $parameters){ $this->parameters = $parameters; $this->useContext('AdminContext', new AdminContext()); $this->useContext('FormContext', new FormContext()); $this->useContext('EditUserContext', new EditUserContext()); $this->useContext('ApiContext', new ApiContext());}
![Page 47: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/47.jpg)
Alias All SubContexts Automatically (Behat 2.x)private function loadSubContexts(){ $finder = new Finder(); $finder->name('*Context.php') ->notName('FeatureContext.php' ) ->notName('CoreContext.php' ); $finder->files()->in(__DIR__);
![Page 48: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/48.jpg)
Alias All SubContexts Automatically (Behat 2.x)private function loadSubContexts(){ $finder = new Finder(); $finder->name('*Context.php') ->notName('FeatureContext.php' ) ->notName('CoreContext.php' ); $finder->files()->in(__DIR__); foreach ($finder as $file) { $className = $file->getBaseName('.php');
$namespace = __NAMESPACE__ . '\\' . $file->getRelativePath(); if (substr($namespace, -1) !== '\\') { $namespace .= '\\'; } $reflectionClass = new ReflectionClass($namespace . $className); $this->useContext($className, $reflectionClass->newInstance()); }}
![Page 49: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/49.jpg)
<?php
namespace Acme\AppBundle\Context;
class FeatureContext extends CoreContext{ /** @Given /^I should see an? (error|success|warning) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $element = $this->getPage()->find('css', $class);
$actualMessage = $element->getText(); $this->assertEqual($actualMessage, $message); }}
Message Context
![Page 50: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/50.jpg)
Find Required Element Shortcutpublic function findRequiredElement ($locator, $selector = 'xpath', $parent = null){ if (null === $parent) { $parent = $this->getPage(); }
$element = $parent->find($selector, $locator); if (null === $element) { throw new ElementNotFoundException ($this->getSession(), null, $selector, $locator); }
return $element;}
![Page 51: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/51.jpg)
Message Context<?php
namespace Acme\AppBundle\Context;
class FeatureContext extends CoreContext{ /** @Given /^I should see an? (error|success|warning) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $element = $this->findRequiredElement ($class, 'css');
$actualMessage = $element->getText(); $this->assertEqual($actualMessage, $message); }}
![Page 52: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/52.jpg)
CoreContext with Step Annotation causes Error [Behat\Behat\Exception\RedundantException] Step "/^I should be redirected to "([^"]*)"$/" is already defined in Acme\AppBundle\Context\FeatureContext::iShouldBeRedirectedTo() Acme\AppBundle\Context\FeatureContext::iShouldBeRedirectedTo() Acme\AppBundle\Context\MessageContext::iShouldBeRedirectedTo()
![Page 53: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/53.jpg)
Reusing Multiple Steps
![Page 54: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/54.jpg)
Reusing Multiple StepsThen I should see an error messageThen I should see a success messageThen I should see a warning message
<?php
/** * @Given /^I should see an? (error|success|warning) message$/ */public function iShouldSeeAnMessage($messageType){ $class = ‘.alert-’.$messageType; $this->assertElementExists($class, ‘css’);}
Scenario: Upload a csv file Given I am viewing the csv import form When I attach a csv to "Import File" And I submit the form Then I should see a success message And I should see the file review screen
Scenario: Review and Confirm the csv file Given I have uploaded a csv And I am viewing the file review screen When I select a property for each column And I submit the form Then I should see a success message
![Page 55: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/55.jpg)
Meta-Stepsuse Behat\Behat\Context\Step;
class FeatureContext{ /** * @Given /^I have uploaded a csv$/ */ public function iHaveUploadedACsv() { return [ new Step\Given('I am viewing the csv import form'), new Step\When('I attach a csv to "Import File"'), new Step\When('I submit the form') ]; }
![Page 56: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/56.jpg)
Meta-Steps With Multi-line Argumentsuse Behat\Behat\Context\Step;use Behat\Gherkin\Node\PyStringNode;
class FeatureContext{ /** * @Given /^I should see the file review screen$/ */ public function iShouldSeeTheFileReviewScreen() { $content = 'Please review your file .' . PHP_EOL . 'Press Submit to continue'; $pyString = new PyStringNode($content); return new Step\Given('I should see', $pyString); }
![Page 57: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/57.jpg)
Direct Method Call - Same Context/*** @Given /^I should see an error about the file type$/*/public function iShouldSeeAnErrorAboutTheFileType (){ $message = 'This file type is invalid' ; $this->iShouldSeeAnMessageThatSays ('error', $message);}/*** @Given /^I should see an? (.*) message that says "([^"])"$/*/public function iShouldSeeAnMessageThatSays ($messageType, $message = null){ $class = '.alert -' . $messageType; $this->assertElementExists ($class, 'css'); if ($message !== null) { $this->assertElementContainsText ($class, 'css', $message); }}
![Page 58: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/58.jpg)
Direct Method Call to Another Context (Behat 2.x)/*** @Given /^I should see an error about the file type$/*/public function iShouldSeeAnErrorAboutTheFileType(){ $message = "This file type is invalid"; $this->getMainContext() ->getSubContext('MessageContext') ->iShouldSeeAnMessageThatSays('error', $message);}
![Page 59: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/59.jpg)
Direct Method Call to Another Context (Behat 2.x)/*** @return MessageContext*/public function getMessageContext (){ return $this->getMainContext()->getSubContext('messageContext');}
/*** @Given /^I should see an error about the file type$/*/public function iShouldSeeAnErrorAboutTheFileType (){ $message = "This file type is invalid" ; $this->getMessageContext ()->iShouldSeeAnMessageThatSays ('error', $message);}
![Page 60: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/60.jpg)
Suite Contexts (Behat 3.x)default:
suites:
default:
paths: [ %paths.base%/features/core ]
contexts: [FeatureContext, MessageContext]
![Page 61: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/61.jpg)
Store Other Contexts (Behat 3.x)use Behat\Behat\Context\Context;use Behat\Behat\Hook\Scope\BeforeScenarioScope ;
class FeatureContext implements Context{ /** @var MessageContext */ private $messageContext;
/** @BeforeScenario */ public function gatherContexts(BeforeScenarioScope $scope) { $environment = $scope->getEnvironment(); $this->messageContext = $environment->getContext('MessageContext'); }}// http://docs.behat.org/en/v3.0/cookbooks/context_communication.html
![Page 62: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/62.jpg)
Direct Method Call to Another Context (Behat 3.x)use Behat\Behat\Context\Context ;
use Behat\Behat\Hook\Scope\BeforeScenarioScope ;
class FeatureContext implements Context
{
/** @var \Behat\MinkExtension\Context\MinkContext */
private $minkContext;
/** @BeforeScenario */
public function gatherContexts (BeforeScenarioScope $scope)
{
$environment = $scope->getEnvironment ();
$this->minkContext = $environment->getContext('Behat\MinkExtension\Context\MinkContext' );
}
}
// http://docs.behat.org/en/v3.0/cookbooks/context_communication.html
/*** @Given /^I should see an error about the file type$/*/public function iShouldSeeAnErrorAboutTheFileType (){ $message = "This file type is invalid" ; $this->messageContext->iShouldSeeAnMessageThatSays ('error', $message);}
![Page 63: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/63.jpg)
Reusing StepsMeta-Steps
● Return a Step or array of
Steps
● Hooks will fire
(could be slow)
● Moving Step definitions
does not break
● Removed in 3.0
Calling Methods
● Like any other method
call
● Hooks do not fire
(typically faster)
● Moving Step definitions
might require refactor
![Page 64: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/64.jpg)
Page Objects
![Page 65: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/65.jpg)
Page Objects Extensionphp composer require "sensiolabs/behat-page-object-extension"
default: extensions: SensioLabs\Behat\PageObjectExtension\Extension: ~
![Page 66: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/66.jpg)
Seminar Page ObjectScenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled for the future When I visit that seminar's page Then I should see the seminar's name And I should see the seminar's author’s name And I should see "This seminar begins at " And I should see the seminar’s start time in EST And I should see a countdown timer
Scenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled When I visit that seminar's page during the broadcast time Then I should see the seminar's name And I should see the seminar video And I should not see a countdown timer
![Page 67: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/67.jpg)
Seminar Page Object<?php
namespace Acme\AppBundle\PageObjects;
use SensioLabs\Behat\PageObjectExtension\PageObject\Page;
class Seminar extends Page{ protected $path = '/seminar/{id}';
}
![Page 68: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/68.jpg)
Seminar Page Object<?php
namespace Acme\AppBundle\PageObjects;
use SensioLabs\Behat\PageObjectExtension \PageObject\Page;
class Seminar extends Page{ protected $path = '/seminar/{id}';
protected $elements = [ 'Author Info' => ['css' => "#author"], 'Video' => ['xpath' => "//div[contains(@class, 'video')]" ], 'Countdown Timer' => ['css' => ".timer"], ];}
![Page 69: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/69.jpg)
Element <?php
namespace Acme\AppBundle\PageObjects\Elements;
use SensioLabs\Behat\PageObjectExtension\PageObject\Element;
class AuthorInformation extends Element{ protected $selector = ['css' => "#author"];
public function getAuthorName(){ return $this->find('css', '.name');}
public function getAuthorPhoto(){ return $this->find('xpath', '//img');}
![Page 70: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/70.jpg)
Interactions
![Page 71: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/71.jpg)
CSV Report@javascriptScenario: View Summary Report Given a user in a group has registered for a seminar with a company And I am logged in as an admin And I am viewing the reports area When I download the "Summary" report Then I should see the following columns: | column | | Group | | Company | | Total | And I should see that user in the report
![Page 72: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/72.jpg)
File Download Test/*** @When /^I download the "([^"]*)" report$/*/public function iDownloadTheReport ($reportName){ $xpath = "//a[normalize-space()=' {$reportName}']"; $link = $this->findRequiredElement ($xpath); $this->getSession()->visit('view-source:' . $link->getAttribute('href')); $content = $this->getSession()->getPage()->getContent(); $lines = explode(PHP_EOL, $content);
$this->csvRows = []; foreach ($lines as $line) { if (strlen(trim($line))) { $this->csvRows[] = str_getcsv($line); } }}
![Page 73: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/73.jpg)
Zip File Download@javascriptScenario: View Large Report Given I am viewing the reports area When I click "Export" for the "Extremely Large Report" report Then a zip file should be downloaded When I unzip the file and I open the extracted csv file Then I should see the following columns: | column | | Group | | Company | | Total |
![Page 74: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/74.jpg)
File Download Test/*** @When /^I click "Export" for the "([^"]*)" report$/*/public function iExportTheReport($reportName){ $this->file = $this->getArtifactsDir() . 'download-' . time() . '.zip'; file_put_contents($this->file, $this->getSession()->getDriver() > getContent());}
/*** @Then /^a zip file should be downloaded$/*/public function aZipFileShouldBeDownloaded(){ $header = $this->getSession()->getDriver()->getResponseHeaders(); $this->assertContains($header['Content-Type'][0], 'application/forced-download'); $this->assertContains($header['Content-Disposition'][0], "zip");}
![Page 75: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/75.jpg)
File Download Test/*** @When /^I unzip the file and I open the extracted csv file$/*/public function iUnzipTheFileAndOpenCsvFile (){ $zip = new ZipArchive; $unzipped = $zip->open($this->file);
$csv = $zip->getNameIndex(1);$zip->extractTo($this->getArtifactsDir() );$zip->close();$fileRef = fopen($this->getArtifactsDir().$csv , 'r');
$this->csvContents = [];while (($data = fgetcsv($fileRef)) !== false) { $this->csvContents[] = $data;}fclose($fileRef);
}
![Page 76: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/76.jpg)
Confirm Text In PDF
/*** @Given /^I should see a PDF with the order total$/*/public function iShouldSeeAPdfWithTheOrderTotal(){ $total = 'Order Total: ' . $this->orderTotal; $this->getMainContext()->assertPageContainsText($total);}
@javascriptScenario: View PDF Receipt Given I am viewing my order history When I click "View Receipt" for an order Then I should see a PDF with the order total
![Page 77: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/77.jpg)
Testing a Command Line Process
![Page 78: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/78.jpg)
Testing a Command Line Process with BehatScenario: Test User Import With a Large Data Set Given the system already has 100000 users And there is a company with 10000 of the users assigned to it And an admin has uploaded a spreadsheet for the company with 10000 rows When the system has begun to process the spreadsheet And I have waited 1 minute Then the batch process status should be set to "Running" or "Completed" And I should see at least 100 new users in the company
![Page 79: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/79.jpg)
The System Already Has 100000 Users /** @Given /^the system already has (\d+) users$/ */public function theSystemAlreadyHasUsers ($numUsers){ $userSQL = "INSERT INTO `user`(firstname, lastname, username) VALUES" ; $userValues = []; $faker = $this->getFaker();
for ($i = 0; $i < $numUsers; $i++) { $firstname = addslashes($faker->firstName); $lastname = addslashes($faker->lastName); $username = $faker->username . $i; //unique $userValues[] = "('{$firstname}', '{$lastname}', '{$username}')"; } $userQuery = $userSQL . implode(', ', $userValues); $this->getEntityManager()->getConnection()->exec($userQuery);}
![Page 80: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/80.jpg)
There is a company with 10000 users assigned to it/** @Given /^there is a company with (\d+) of the users assigned to it$/ */public function thereIsACompanyWithOfTheUsersAssignedToIt ($num){ $company = $this->generateCompany(); $conn = $this->getEntityManager()->getConnection();
$userCompanySQL = "INSERT INTO `user_company`(user_id, company_id) SELECT `user`.id, {$company->getId()} FROM `user` LIMIT {$num}"; $conn->exec($userCompanySQL);
$this->getEntityManager()->refresh($company); $companyUsersCount = $company ->getUserCompanies()->count(); $this->assertGreaterThanOrEqual ($num, $companyUsersCount );
$this->company = $company; $this->companyNumUsers = $companyUsersCount ;}
![Page 81: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/81.jpg)
An Admin Has Uploaded a Spreadsheet/** @Given /^an admin has uploaded a spreadsheet for the company with (\d*) rows$/ */
public function adminHasUploadedSpreadsheetForTheCompanyWithRows($numRows){ $faker = $this->getFaker(); $this->filePath = $this->getUploadsDirectory() . 'import -' . $numRows . '.csv'; $fh = fopen($this->filePath, "w"); $rows = 'firstname, lastname, username' . PHP_EOL; for ($i = 0; $i < $numRows; $i++) { $firstname = addslashes($faker->firstName); $lastname = addslashes($faker->lastName); $username = $faker->username . $i; //add $i to force unique $rows .= "{$firstname}, {$lastname}, {$username}" . PHP_EOL; } fwrite($fh, $rows); fclose($fh); $repository = $this->getRepository('BatchProcess'); $this->batchProcess = $repository->create()->setFilename($this->filePath); $repository->save($this->batchProcess);}
![Page 82: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/82.jpg)
The System Has Begun To Process The Spreadsheet/*** @When /^the system has begun to process the spreadsheet$/i*/public function theSystemHasBegunToProcessTheSpreadsheet(){ $command = 'php app' . DIRECTORY_SEPARATOR; $command .= 'console batch:process --batch_id='; $command .= $this->batchProcess->getId();
if (substr(php_uname(), 0, 7) == "Windows") { return pclose(popen("start /B " . $command, "r")); } return exec($command . " > /dev/null &");}
![Page 83: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/83.jpg)
I Have Waited 1 Minute/*** @When /^I have waited (\d+) minutes?$/*/public function iHaveWaitedSomeMinutes($num){ $seconds = 60; $outputEvery = 30; $cycles = ($num * $seconds) / $outputEvery; for ($i = 0; $i < $cycles; $i++) { sleep($outputEvery); echo '.'; } echo PHP_EOL;}
![Page 84: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/84.jpg)
The Batch Process Status Should Be/*** @Given /^the batch process status should be set to "(.*)" or "(.*)"$/*/public function theBatchProcessStatusShouldBeSetTo($statusA, $statusB){ $this->getEntityManager()->refresh($this->batchProcess); $statusName = $this->batchProcess->getStatus()->getName(); if ($statusName !== $statusA && $statusName !== $statusB) { throw new \Exception("Status is currently: {$statusName}"); }}
![Page 85: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/85.jpg)
I should see at least 100 new users/*** @Then /^I should see at least (\d+) new users in the company$/*/public function iShouldSeeAtLeastNewUsersInTheCompany($num){ $company = $this->company; $this->getEntityManager()->refresh($company);
$companyNumUsersNow = $company->getUserCompanies()->count(); $originalNumUsers = $this->companyNumUsers; $difference = ($companyNumUsersNow - $originalNumUsers);
$this->assertGreaterThanOrEqual($num, $difference);}
![Page 86: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/86.jpg)
Thank You!@jessicamauerhan
10-13-15 Dallas PHP User Group
http://joind.in/event/view/4808
![Page 87: Behat: Beyond the Basics](https://reader030.vdocument.in/reader030/viewer/2022021506/587fd3901a28ab58248b51fd/html5/thumbnails/87.jpg)
Resources & ToolsDrivers: http://mink.behat.org/en/latest/guides/drivers.html
Angular AJAX check: https://gist.github.com/jmauerhan/f839926ea527ff5e74e7
Doctrine Data Fixtures: https://github.com/doctrine/data-fixtures
Faker: https://github.com/fzaninotto/Faker
Symfony Finder: http://symfony.com/doc/current/components/finder.html
Page Object Extension: https://github.com/sensiolabs/BehatPageObjectExtension
PHP Zip Archive: http://php.net/manual/en/class.ziparchive.php