$30 off During Our Annual Pro Sale. View Details »

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
    Behat+Mink+PhantomJS
    =
    TEST ALL THE THINGS*
    * Maybe not all the things

    View Slide

  2. @michellesanver

    View Slide

  3. @michellesanver
    WIIIIIIIE \o/
    “Learn the most by
    sharing your knowledge
    with others” - @coderabbi

    View Slide

  4. @michellesanver
    Audience
    Do you test your application?
    (With automatic testing not just clicking in a browser)

    View Slide

  5. @michellesanver
    I would have a picture of grumpy
    programmer here,
    but I didn’t want to pay the royalties.

    View Slide

  6. @michellesanver
    “My code is so awesome, I don’t need tests.”
    - Conference attendee

    View Slide

  7. @michellesanver
    Audience
    Do you use PHPUnit?

    View Slide

  8. @michellesanver
    Audience
    TDD? BDD?

    View Slide

  9. @michellesanver
    This talk is entry-level.

    View Slide

  10. @michellesanver

    View Slide

  11. @michellesanver

    View Slide

  12. @michellesanver
    Michelle Sanver

    View Slide

  13. @michellesanver
    Michelle Sanver
    Co-President of PHPWomen

    View Slide

  14. @michellesanver
    PHPWomen
    30 dollars

    View Slide

  15. @michellesanver
    Michelle Sanver
    Accent(s)!?

    View Slide

  16. @michellesanver
    Michelle Sanver

    View Slide

  17. @michellesanver
    I’m a backend developer.

    View Slide

  18. @michellesanver
    This talk is Open Source

    View Slide

  19. @michellesanver
    What is Behat anyway?

    View Slide

  20. @michellesanver
    Behat!?
    Behat is a Behaviour Driven
    Development framework

    View Slide

  21. @michellesanver
    Erm, what is BDD,
    anyway?

    View Slide

  22. @michellesanver
    BDD
    BDD is clearly defined behaviour
    and English spoken tests :D

    View Slide

  23. @michellesanver
    BDD
    BDD = TDD + Spices

    View Slide

  24. @michellesanver
    Why we chose Behat

    View Slide

  25. @michellesanver
    Gherkin
    Defining your behaviour

    View Slide

  26. @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"

    View Slide

  27. @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"

    View Slide

  28. @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"

    View Slide

  29. @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"

    View Slide

  30. @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"

    View Slide

  31. @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."

    View Slide

  32. @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

    View Slide

  33. @michellesanver
    Gherkin: Scenario Outlines
    Scenario Outline: Lonestar social

    Given there are beers

    When I drink beers

    Then I should have beers


    Examples:

    | start | drink | left |

    | 12 | 5 | 7 |

    | 20 | 5 | 15 |
    Keep it DRY

    View Slide

  34. @michellesanver
    Gherkin
    Tagged Hooks
    (@loggedin etc that we’ve seen)

    View Slide

  35. @michellesanver
    Reusable Actions: Steps

    View Slide

  36. @michellesanver
    Steps
    Each keyword (given, when etc.)
    is a step

    View Slide

  37. @michellesanver
    Steps
    Automatically add steps by
    accepting context!

    View Slide

  38. @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()

    {

    }

    }

    View Slide

  39. @michellesanver
    Steps: Results
    Pending

    View Slide

  40. @michellesanver
    Steps: Results
    Failed

    View Slide

  41. @michellesanver
    Steps: Results
    Skipped

    View Slide

  42. @michellesanver
    Steps: Results
    Ambigious

    View Slide

  43. @michellesanver
    Steps: Results
    Reduntant

    View Slide

  44. @michellesanver
    Steps: Results
    Undefined

    View Slide

  45. @michellesanver
    SUCCESSFUL \o/ :D

    View Slide

  46. @michellesanver
    Hooked on a feeling *bamdabadam*
    Hooks

    View Slide

  47. @michellesanver
    Hooks
    Suite Hooks

    View Slide

  48. @michellesanver
    Hooks
    Feature Hooks

    View Slide

  49. @michellesanver
    Hooks
    Scenario Hooks

    View Slide

  50. @michellesanver
    Hooks
    Step Hooks

    View Slide

  51. @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’);

    }

    }

    View Slide

  52. @michellesanver
    Tagged hooks
    /**

    * @BeforeScenario @database,@orm

    */

    public function cleanDatabase()

    {

    // clean database before

    // @database OR @orm scenarios

    }

    View Slide

  53. @michellesanver
    Tagged hooks: &&
    /**

    * @BeforeScenario @database&&@fixtures

    */

    public function cleanDatabase()

    {

    // clean database before

    // @database OR @orm scenarios

    }

    View Slide

  54. @michellesanver
    Context

    View Slide

  55. @michellesanver
    Context
    Feature
    How your application behaves
    Context
    How to test your application

    View Slide

  56. @michellesanver
    Context
    FeatureContext Class

    View Slide

  57. @michellesanver
    Context
    /**

    * @When I do something with :argument

    */

    public function iDoSomethingWith($argument)

    {

    // do something with $argument

    }

    View Slide

  58. @michellesanver
    Context
    Contact class requirements

    View Slide

  59. @michellesanver
    Context: Class requirements
    1. The context class should
    implement the Behat\Behat\Context
    \Context interface.

    View Slide

  60. @michellesanver
    Context: Class requirements
    2. The context class should be called
    FeatureContext.

    View Slide

  61. @michellesanver
    Context: Class requirements
    3. The context class should be
    discoverable and loadable by Behat.

    View Slide

  62. @michellesanver
    Context
    Contexts lifetime

    View Slide

  63. @michellesanver
    Context lifetime
    Your context class is initialized before
    each scenario is run

    View Slide

  64. @michellesanver
    Context lifetime
    1. Every scenario is isolated from
    each other scenario’s context.

    View Slide

  65. @michellesanver
    Context lifetime
    2. Every step in a single scenario is
    executed inside a common context
    instance.

    View Slide

  66. @michellesanver
    Context
    Multiple contexts

    View Slide

  67. @michellesanver
    Multiple contexts
    # behat.yml
    default:
    suites:
    default:
    contexts:
    - FeatureContext
    - SecondContext
    - ThirdContext

    View Slide

  68. @michellesanver
    Writing TESTS

    View Slide

  69. @michellesanver
    Writing Tests
    A test only fails if an exception is
    thrown.

    View Slide

  70. @michellesanver
    Writing Tests
    Write simple tests using PHPUnits
    assertions.

    View Slide

  71. @michellesanver
    Writing Tests
    But essentially test ANYTHING YOU’D
    LIKE, just throw exceptions.

    View Slide

  72. @michellesanver
    Mink

    View Slide

  73. @michellesanver
    Mink
    Simulate browser behaviour with Mink

    View Slide

  74. @michellesanver
    Mink
    Clicking links!

    View Slide

  75. @michellesanver
    Mink
    // wait for animation

    $session->wait(500);

    // get loginlink

    $loginLinkSelector = '.hidden-phone a.login';

    $loginLink = $page->find('css',
    $loginLinkSelector);

    // click loginlink

    $loginLink->click();

    View Slide

  76. @michellesanver
    Mink
    Drag and drop

    View Slide

  77. @michellesanver
    Fixtures and Mocking

    View Slide

  78. @michellesanver
    Fixtures and Mocking

    View Slide

  79. @michellesanver
    Fixtures and Mocking
    Any time API responses
    changed… We cried. :’(

    View Slide

  80. @michellesanver
    Fixtures and Mocking
    PHP VCR
    Record HTTP interactions!

    View Slide

  81. @michellesanver
    PhantomJS
    Testing JavaScript

    View Slide

  82. @michellesanver
    PhantomJS
    Headless browser

    View Slide

  83. @michellesanver
    Selenium Driver
    PhantomJS is a ghost!

    View Slide

  84. @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"


    View Slide

  85. @michellesanver
    PhantomJS
    /**

    * @When I search for :searchterm

    */

    public function iSearchFor($searchterm)

    {


    }

    View Slide

  86. @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);


    View Slide

  87. @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);


    View Slide

  88. @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);


    View Slide

  89. @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);


    View Slide

  90. @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);


    View Slide

  91. @michellesanver
    Screenshot comparison
    Compare image code

    View Slide

  92. @michellesanver
    Wrapup

    View Slide

  93. @michellesanver
    Behat and Gherkin
    A BDD Framework with clearly
    defined behaviours

    View Slide

  94. @michellesanver
    Contexts and Steps
    Steps are your tests living in a
    context, which contains your logic for
    testing

    View Slide

  95. @michellesanver
    Testing JavaScript
    Use Mink with a driver, for instance
    PhantomJS to test dynamic parts of
    your website.

    View Slide

  96. @michellesanver
    Still not mature for CSS
    and image comparison.

    View Slide

  97. @michellesanver
    Behat is Open Source

    View Slide

  98. @michellesanver
    Got a bit excited about one
    Testing Framework for ALL THE
    THINGS?
    As developers in an open source
    community the decision is ours.

    View Slide

  99. @michellesanver
    Contribute to my talk!

    View Slide

  100. @michellesanver
    Thank you!

    View Slide