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

Behat + Mink + PhantomJS = TEST ALL THE THINGS!

Behat + Mink + PhantomJS = TEST ALL THE THINGS!

Michelle Sanver

September 23, 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
    Let’s talk
    Come forward

    View Slide

  5. @michellesanver
    This talk is entry-level.

    View Slide

  6. @michellesanver

    View Slide

  7. @michellesanver

    View Slide

  8. @michellesanver
    Michelle Sanver

    View Slide

  9. @michellesanver
    Michelle Sanver
    Co-President of PHPWomen

    View Slide

  10. @michellesanver
    PHPWomen
    30 dollars

    View Slide

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

    View Slide

  12. @michellesanver
    Michelle Sanver

    View Slide

  13. @michellesanver
    I’m a backend developer.

    View Slide

  14. @michellesanver

    View Slide

  15. @michellesanver
    This talk is Open
    Source

    View Slide

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

    View Slide

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

    View Slide

  18. @michellesanver
    “My code is so awesome, I don’t need tests.”
    - LoneStar PHP conference attendee

    View Slide

  19. @michellesanver
    Audience
    Do you use PHPUnit?

    View Slide

  20. @michellesanver
    Audience
    TDD? BDD?

    View Slide

  21. @michellesanver
    First I will explain how
    the individual parts work

    View Slide

  22. @michellesanver
    Then how it fits together

    View Slide

  23. @michellesanver
    Behat + Mink + PhantomJS
    Gherkin
    PhantomJS OR webdriver
    MinkContext
    FeatureContext
    Behat

    View Slide

  24. @michellesanver
    What is Behat anyway?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. @michellesanver
    BDD
    BDD = TDD + Spices

    View Slide

  29. @michellesanver
    Why we chose Behat

    View Slide

  30. @michellesanver
    Gherkin
    Defining your behaviour

    View Slide

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

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

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

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

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

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

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

  38. @michellesanver
    Gherkin: Scenario Outlines
    Scenario Outline: PHPDay 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

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

    View Slide

  40. @michellesanver
    Reusable Actions: Steps

    View Slide

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

    View Slide

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

    View Slide

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

  44. @michellesanver
    Steps: Results
    Pending

    View Slide

  45. @michellesanver
    Steps: Results
    Failed

    View Slide

  46. @michellesanver
    Steps: Results
    Skipped

    View Slide

  47. @michellesanver
    Steps: Results
    Ambigious

    View Slide

  48. @michellesanver
    Steps: Results
    Reduntant

    View Slide

  49. @michellesanver
    Steps: Results
    Undefined

    View Slide

  50. @michellesanver
    SUCCESSFUL \o/ :D

    View Slide

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

    View Slide

  52. @michellesanver
    Hooks
    Suite Hooks

    View Slide

  53. @michellesanver
    Hooks
    Feature Hooks

    View Slide

  54. @michellesanver
    Hooks
    Scenario Hooks

    View Slide

  55. @michellesanver
    Hooks
    Step Hooks

    View Slide

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

  57. @michellesanver
    Tagged hooks
    /**

    * @BeforeScenario @database,@orm

    */

    public function cleanDatabase()

    {

    // clean database before

    // @database OR @orm scenarios

    }

    View Slide

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

    * @BeforeScenario @database&&@fixtures

    */

    public function cleanDatabase()

    {

    // clean database before

    // @database OR @orm scenarios

    }

    View Slide

  59. @michellesanver
    Context

    View Slide

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

    View Slide

  61. @michellesanver
    Context
    FeatureContext Class

    View Slide

  62. @michellesanver
    Context
    /**

    * @When I do something with :argument

    */

    public function iDoSomethingWith($argument)

    {

    // do something with $argument

    }

    View Slide

  63. @michellesanver
    Context
    Contact class requirements

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  67. @michellesanver
    Context
    Contexts lifetime

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  71. @michellesanver
    Context
    Multiple contexts

    View Slide

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

    View Slide

  73. @michellesanver
    Writing TESTS

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  77. @michellesanver
    Mink

    View Slide

  78. @michellesanver
    Mink
    Simulate browser behaviour with Mink

    View Slide

  79. @michellesanver
    Mink
    Clicking links!

    View Slide

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

  81. @michellesanver
    Mink
    Drag and drop

    View Slide

  82. @michellesanver
    Fixtures and Mocking

    View Slide

  83. @michellesanver
    Fixtures and Mocking

    View Slide

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

    View Slide

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

    View Slide

  86. @michellesanver
    Fixtures and Mocking
    PHP VCR
    Record HTTP interactions and replay them
    during test runs.

    View Slide

  87. @michellesanver
    PhantomJS
    Testing JavaScript

    View Slide

  88. @michellesanver
    PhantomJS
    Headless browser

    View Slide

  89. @michellesanver
    Selenium Driver
    PhantomJS is a ghost!

    View Slide

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

  91. @michellesanver
    PhantomJS
    /**

    * @When I search for :searchterm

    */

    public function iSearchFor($searchterm)

    {


    }

    View Slide

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

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

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

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

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

  97. @michellesanver
    Screenshot comparison
    Compare image code

    View Slide

  98. @michellesanver
    Fitting the pieces
    together

    View Slide

  99. @michellesanver
    Behat + Mink + PhantomJS
    Gherkin
    PhantomJS OR webdriver
    MinkContext
    FeatureContext
    Behat

    View Slide

  100. @michellesanver
    Wrapup

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. @michellesanver
    Behat is Open Source

    View Slide

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

  107. @michellesanver
    Contribute
    How would you use Behat in Drupal?
    Any caveats you can think of?

    View Slide

  108. @michellesanver
    Open Source
    Open Discussion

    View Slide

  109. @michellesanver
    Thank you!

    View Slide