Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Behat+Mink+PhantomJS = Test ALL THE THINGS!

Behat+Mink+PhantomJS = Test ALL THE THINGS!

Sometimes simple unit testing is not enough. There is more to it than that when you want to test JavaScript and the rendering of your page. In this talk I will show you how you can use Behat+Mink+PhantomJS to accomplish screenshot comparison, test your JavaScript implementations and your PHP code using the same human-readable language: Behat’s Gherkin.

Michelle Sanver

April 18, 2015
Tweet

More Decks by Michelle Sanver

Other Decks in Programming

Transcript

  1. @michellesanver I would have a picture of grumpy programmer here,

    but I didn’t want to pay the royalties.
  2. @michellesanver Gherkin Feature: List
 In order to use the shoppinglist


    As a website user
 I need to be able to see the shoppinglist and use its elements
 
 @anonymous
 Scenario: Visit shoppinglist page as anonymous
 Given I am not logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “I don’t have an M-connect account yet“
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see "My shopping lists"
 And I should see "I don’t have an M-connect account yet"
  3. @michellesanver Gherkin: Feature Feature: List
 In order to use the

    shoppinglist
 As a website user
 I need to be able to see the shoppinglist and use its elements
 
 @anonymous
 Scenario: Visit shoppinglist page as anonymous
 Given I am not logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “I don’t have an M-connect account yet“
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “Active lists"
  4. @michellesanver Gherkin: Scenario Feature: List
 In order to use the

    shoppinglist
 As a website user
 I need to be able to see the shoppinglist and use its elements
 
 Scenario: Larry garfield at a conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
 
 @loggedin
 Scenario: See the shoppinglist
 Given I am logged in
 And I am on profile “shopping lists"
 Then I should see “My shopping lists"
 And I should see “Active lists"
  5. @michellesanver Gherkin Given, Whens, Thens Scenario: Larry garfield at a

    conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
  6. @michellesanver Gherkin And + But Scenario: Larry garfield at a

    conference
 Given I am Larry Garfield
 And I am at a conference
 Then I should wear “Blue shirt”
 And I should wear “Leather vest"
  7. @michellesanver Gherkin: Background Feature: Multiple site support
 
 Background:
 Given

    a global administrator named "Greg"
 And a blog named "Greg's anti-tax rants"
 And a customer named "Wilson"
 And a blog named "Expensive Therapy" owned by "Wilson"
 
 Scenario: Wilson posts to his own blog
 Given I am logged in as Wilson
 When I try to post to "Expensive Therapy"
 Then I should see "Your article was published."
 
 Scenario: Greg posts to a client's blog
 Given I am logged in as Greg
 When I try to post to "Expensive Therapy"
 Then I should see "Your article was published."
  8. @michellesanver Gherkin: Scenario Outlines Scenario: Drink 5 out of 12


    Given there are 12 beers
 When I drink 5 beers
 Then I should have 7 beers
 
 Scenario: Drink 5 out of 20
 Given there are 20 beers
 When I drink 5 beers
 Then I should have 15 beers
  9. @michellesanver Gherkin: Scenario Outlines Scenario Outline: Lonestar social
 Given there

    are <start> beers
 When I drink <drink> beers
 Then I should have <left> beers
 
 Examples:
 | start | drink | left |
 | 12 | 5 | 7 |
 | 20 | 5 | 15 | Keep it DRY
  10. @michellesanver Steps use Behat\Behat\Context\SnippetAcceptingContext;
 use Behat\Gherkin\Node\PyStringNode;
 use Behat\Gherkin\Node\TableNode;
 
 class

    FeatureContext implements SnippetAcceptingContext
 {
 /**
 * Initializes context.
 */
 public function __construct()
 {
 }
 }
  11. @michellesanver Hooks /** 
 * @BeforeScenario
 */
 public function before(BeforeScenarioScope

    $scope)
 {
 if($this->getMink()->getDefaultSessionName() == 'selenium2') {
 $this->getSession()->resizeWindow(1200, 768);
 } else {
 self::$client = $this->getSession(‘symfony2') ->getDriver()->getClient();
 self::$container = self::$client->getContainer();
 self::$guzzle = self::$container->get(‘guzzle.api.client’);
 }
 }
  12. @michellesanver Tagged hooks /**
 * @BeforeScenario @database,@orm
 */
 public function

    cleanDatabase()
 {
 // clean database before
 // @database OR @orm scenarios
 }
  13. @michellesanver Tagged hooks: && /**
 * @BeforeScenario @database&&@fixtures
 */
 public

    function cleanDatabase()
 {
 // clean database before
 // @database OR @orm scenarios
 }
  14. @michellesanver Context /**
 * @When I do something with :argument


    */
 public function iDoSomethingWith($argument)
 {
 // do something with $argument
 }
  15. @michellesanver Context lifetime 2. Every step in a single scenario

    is executed inside a common context instance.
  16. @michellesanver Mink // wait for animation
 $session->wait(500); 
 // get

    loginlink
 $loginLinkSelector = '.hidden-phone a.login';
 $loginLink = $page->find('css', $loginLinkSelector); 
 // click loginlink
 $loginLink->click();
  17. @michellesanver PhantomJS @javascript
 Scenario: Searching for "tomaten"
 Given I am

    on "/sortiment/groceries"
 When I search for "tomatoes"
 Then I should see “Your search for tomatoes yielded"

  18. @michellesanver PhantomJS /**
 * @When I search for :searchterm
 */


    public function iSearchFor($searchterm)
 {
 
 }
  19. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  20. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  21. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  22. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  23. @michellesanver PhantomJS $session = $this->getSession();
 $page = $session->getPage();
 
 $searchField

    = $page->find('css', '.hidden-phone .search-input');
 $searchField->click();
 $searchField->setValue($searchterm);
 
 $searchButton = $page->find('css', '.hidden-phone .search-btn');
 $searchButton->click();
 
 // Wait until we have a search-title with a length longer than 0.
 $session->wait(
 0,
 "$('.search-title').length > 0"
 );
 
 $this->saveScreenshot('debugscreenshot.png', self::DEBUGPATH);

  24. @michellesanver Contexts and Steps Steps are your tests living in

    a context, which contains your logic for testing
  25. @michellesanver Testing JavaScript Use Mink with a driver, for instance

    PhantomJS to test dynamic parts of your website.
  26. @michellesanver Got a bit excited about one Testing Framework for

    ALL THE THINGS? As developers in an open source community the decision is ours.