105
I put on my mink and wizard behat Questing in the world of front end testing

I put on my mink and wizard behat (tutorial)

  • Upload
    xsist10

  • View
    474

  • Download
    5

Embed Size (px)

Citation preview

Page 1: I put on my mink and wizard behat (tutorial)

I put on my mink and

wizard behatQuesting in the world of

front end testing

Page 2: I put on my mink and wizard behat (tutorial)

9:30 Setup9:45 Introduction to Front End Testing10:15 We write some tests10:45 Coffee break11:00 I talk about some more advanced stuff11:30 We write some more tests12:15 We attempt a grid12:30 Q&A12:45 End

Schedule100% chance of incorrectness

Page 3: I put on my mink and wizard behat (tutorial)

$ sudo vim /etc/hosts

192.168.56.101 opencfp.dev

# copy T9AG1x and T9AG1x_*$ cd T9AG1x$ vagrant up$ vagrant ssh$ cd /var/www/opencfp$ sh run.sh

Setup for PracticalVagrant

$ sudo vim /etc/hosts

10.41.6.62 opencfp.dev

# copy just T9AG1x/opencfp$ cd opencfp$ php vendor/behat/behat/bin/behat

No Vagrant

Page 4: I put on my mink and wizard behat (tutorial)

Booking.com@thomas_shone

WE ARE HIRING

Page 5: I put on my mink and wizard behat (tutorial)

Hoare Logic{P} C {Q}

Page 6: I put on my mink and wizard behat (tutorial)

Hodor Logic{P} C {Q}

Page 7: I put on my mink and wizard behat (tutorial)

Why?What's the benefit?

Page 8: I put on my mink and wizard behat (tutorial)

Meet The TeamDon’t feed the druid after midnight

Page 9: I put on my mink and wizard behat (tutorial)

TaskEach test has a different approach

Page 10: I put on my mink and wizard behat (tutorial)

BarbarianQuality Assurance

Page 11: I put on my mink and wizard behat (tutorial)

RangerUnit Test

Page 12: I put on my mink and wizard behat (tutorial)

ClericContinuous Integration

Page 13: I put on my mink and wizard behat (tutorial)

WizardFront End Test

Page 14: I put on my mink and wizard behat (tutorial)
Page 15: I put on my mink and wizard behat (tutorial)

Dreaded Bugbear

Page 16: I put on my mink and wizard behat (tutorial)

Teamwork Wizards are squishy

Page 17: I put on my mink and wizard behat (tutorial)

$ sudo vim /etc/hosts

192.168.56.101 opencfp.dev

# copy T9AG1x and T9AG1x_*$ cd T9AG1x$ vagrant up$ vagrant ssh$ cd /var/www/opencfp$ sh run.sh

Setup for PracticalVagrant

$ sudo vim /etc/hosts

10.41.6.62 opencfp.dev

# copy just T9AG1x/opencfp$ cd opencfp$ php vendor/behat/behat/bin/behat

No Vagrant

Page 18: I put on my mink and wizard behat (tutorial)

The glue

Behat(cucumber syntax)

Mink(browser emulation)

Goutte(web driver)

Selenium(web driver)

Zombie(web driver)

Guzzle(curl)

Selenium RC(java)

Zombie.js(node.js)

Page 19: I put on my mink and wizard behat (tutorial)

Feature: Party harmonyAs a leader, I want to ensure harmony and mutual trust, so that we work as a team

Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard mysteriously catches fire

Cucumber SyntaxReadable testing language

Page 20: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/** * @Given that the wizard has :num cookies */public function wizardHasCookies($num) {

// $this->wizard is a pre-existing condition... like syphilis$this->wizard->setNumberOfCookies($num);

}}

and converts it intoFeatureContext.php

Page 21: I put on my mink and wizard behat (tutorial)

Feature: Party harmonyAs a leader, I want to ensure harmony and mutual trust, so that we work as a team

Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard mysteriously catches fire

Cucumber SyntaxWhat’s missing?

Page 22: I put on my mink and wizard behat (tutorial)

Scenario:Given that the wizard has 10 cookiesAnd the Bard eats 1 cookie

Fire spell fizzled (OutOfManaException)

1 scenario (1 failed)2 steps (1 passed, 1 failed)0m0.03s (14.19Mb)

Remember {P} C {Q}Set your starting states

Page 23: I put on my mink and wizard behat (tutorial)

Feature: Party harmonyAs a leader, I want to ensure harmony and mutual trust, so that we work as a team

Background:The Wizard’s fire spell is fully chargedAnd the Bard is currently not on fire

Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard mysteriously catches fire

Remember {P} C {Q}Set your starting states

Page 24: I put on my mink and wizard behat (tutorial)

???As a leader, I want to ensure harmony and

mutual trust, so that we work as a team

Page 25: I put on my mink and wizard behat (tutorial)

User storiesAs a <role>, I want to <desire> so that

<benefit>

Page 26: I put on my mink and wizard behat (tutorial)

Front end testing is code coverage for your user stories

User storiesCoverage

Features are your contract with the stakeholders

Contract

Scenarios are the use cases that outline the user story

Scenarios

Page 27: I put on my mink and wizard behat (tutorial)

Legend has it...… that someone once convinced their PO to

write all their front end tests.

Page 28: I put on my mink and wizard behat (tutorial)

class MinkContext … {/** * Clicks link with specified id|title|alt|text. *

* @When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ */public function clickLink($link) {

$link = $this->fixStepArgument($link); $this->getSession()->getPage()->clickLink($link); }}

Mink provides...MinkContext.php

Page 29: I put on my mink and wizard behat (tutorial)

OK...Lets drop the metaphor and get to actual

code

Page 30: I put on my mink and wizard behat (tutorial)

$ composer require behat/behat="~3.0,>=3.0.5"

Getting started

$ composer require behat/mink-extension="~2.0"

Behat (cucumber syntax)

Mink (browser emulator)

Web drivers$ composer require behat/mink-goutte-driver="~1.0"$ composer require behat/mink-selenium2-driver="~1.2"

PRACTICAL

Page 31: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat --init

+d features - place your *.feature files here+d features/bootstrap - place your context classes here+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here

InitializeCreate a new test suite

PRACTICAL

Page 32: I put on my mink and wizard behat (tutorial)

use Behat\MinkExtension\Context\MinkContext;

class FeatureContext extends MinkContext … {…

}

ContextFeatureContext.php

PRACTICAL

Page 33: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat -dl

Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/

ContextWhat does Mink bring to the table?

PRACTICAL

Page 34: I put on my mink and wizard behat (tutorial)

default: suites: default: paths: [ %paths.base%/features/ ] contexts: [ FeatureContext ] extensions: Behat\MinkExtension: base_url: "[your website]" sessions: default: goutte: ~

Configurationbehat.yml

PRACTICAL

Page 35: I put on my mink and wizard behat (tutorial)

Feature: Authentication and authorisationAs a security conscious developer I wish to ensure that only valid users can access our website.

Scenario: Attempt to login with invalid details Given I am on "/login"

When I fill in "email" with "[email protected]" And I fill in "password" with "invalid" And I press "Login" Then I should see "Invalid Email or Password"

Our first featureauth.feature

PRACTICAL

Page 36: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat --config behat.yml features/auth.feature

Scenario: Attempt to login with an invalid accountGiven I am on "/login"When I fill in "email" with "[email protected]"And I fill in "password" with "invalid"And I press "Login"Then I should see "Invalid Email or Password"

1 scenarios (1 passed)5 steps (5 passed)

Victoryoutput

PRACTICAL

Page 37: I put on my mink and wizard behat (tutorial)

Feature: Authentication and authorisationAs a security conscious developer I wish to ensure that only valid users can access our website.

Scenario: Attempt to login with invalid details Given I login as "[email protected]" with password "invalid" Then I should see "Invalid Email or Password"

Simplifyauth.feature

PRACTICAL

Page 38: I put on my mink and wizard behat (tutorial)

Scenario: Attempt to login with an invalid accountGiven I login as "[email protected]" with password "invalid"Then I should see "Invalid Email or Password"

1 scenario (1 undefined) /** * @Given I login as :arg1 with password :arg2 */

public function iLoginAsWithPassword($arg1, $arg2) { throw new PendingException();

}

Simplifyoutput

PRACTICAL

Page 39: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/**

* @Given I login as :username with password :password */

public function iLoginAsWithPassword($username, $password) {$this->visit("/login");$this->fillField("email", $username);$this->fillField("password", $password);$this->pressButton("Login");

}}

SimplifyFeatureContext.php

PRACTICAL

Page 40: I put on my mink and wizard behat (tutorial)

Scenario: Attempt to login with an invalid accountGiven I login as "[email protected]" with password "invalid"Then I should see "Invalid Email or Password"

1 scenarios (1 passed)2 steps (2 passed)

Simplifyoutput

PRACTICAL

Page 41: I put on my mink and wizard behat (tutorial)

// Manipulate the current web session$session = $this->getSession();

$session->visit($url);$session->setBasicAuth($user, $password = '');$session->setRequestHeader($name, $value);$session->setCookie($name, $value = null);$session->getCookie($name);$session->getCurrentUrl();$session->reload();$session->back();

SessionBehat\Mink\Session

SIDE NOTE

Page 42: I put on my mink and wizard behat (tutorial)

Page

// Navigate and manipulate the current page in a selector style$page = $this->getSession()->getPage();

$page->find($selectorType, $selector); // 'css', '.class-name'$page->findById($id);$page->hasLink($locator);$page->clickLink($locator);$page->fillField($locator, $value);$page->hasSelect($locator);$page->selectFieldOption($locator, $value, $multiple = false);$page->hasTable($locator);

Behat\Mink\Element\DocumentElementSIDE NOTE

Page 43: I put on my mink and wizard behat (tutorial)

Driver

// Access the web driver directly via xpaths$driver = $this->getSession()->getDriver();

$xpath = '//html/body/table/thead/tr/th[first()]'$driver->blur($xpath);$driver->focus($xpath);$driver->mouseOver($xpath);$driver->isVisible($xpath);$driver->dragTo($sourceXpath, $destinationXpath);// Modifier could be 'ctrl', 'alt', 'shift' or 'meta'$driver->keyPress($xpath, $char, $modifier = null);

Behat\Mink\Driver\DriverInterfaceSIDE NOTE

Page 44: I put on my mink and wizard behat (tutorial)

Feature: Authentication and authorisationAs a security conscious developer I wish to ensure that only valid users can access our website.

Scenario: Attempt to register a new user Given I am on "/signup"

When I fill in "email" with "[email protected]" And I fill in "password" with "valid" And I fill in "password2" with "valid" And I fill in "first_name" with "some"

And I fill in "last_name" with "guy" And I press "Create my speaker profile" Then I should see "You’ve successfully created your account"

Our first hurdleThis ones easy, you do…. oh….

PRACTICAL

Page 45: I put on my mink and wizard behat (tutorial)

Migration and seedingDoctrine, Propel, Laravel, Phinx

Page 46: I put on my mink and wizard behat (tutorial)

$ composer require robmorgan/phinx="~0.4"

Phinx to the rescueInstall

$ php vendor/bin/phinx initPhinx by Rob Morgan - https://phinx.org. version 0.4.3Created ./phinx.xml

Configuration

$ php vendor/bin/phinx create InitialMigration

Creating

SIDE NOTE

Page 47: I put on my mink and wizard behat (tutorial)

#!/usr/bin/env bashDATABASE="opencfp"mysql -e "DROP DATABASE IF EXISTS $DATABASE" -uroot -p123mysql -e "CREATE DATABASE $DATABASE" -uroot -p123vendor/bin/phinx migratevendor/bin/behat

It’s a bit extremerun-behat-test.sh

PRACTICAL

Page 48: I put on my mink and wizard behat (tutorial)

SAVEPOINT identifier;

# Run tests

ROLLBACK TO SAVEPOINT identifier;RELEASE SAVEPOINT identifier;

Transaction/RollbackRoll your own solution

Page 49: I put on my mink and wizard behat (tutorial)

Activation emails?smtp-sink, FakeSMTP, etc

Page 50: I put on my mink and wizard behat (tutorial)

# Stop the currently running servicesudo service postfix stop# Dumps outgoing emails to file as "day.hour.minute.second"smtp-sink -d "%d.%H.%M.%S" localhost:2500 1000 &

vendor/bin/behat

smtp-sinkrun-behat-test.sh

Page 51: I put on my mink and wizard behat (tutorial)

Or….you could just read the activation code from

the database directly

Page 52: I put on my mink and wizard behat (tutorial)

class DatabaseContext {public function __construct($dsn, $user, $pass) {

$this->dbh = new PDO($dsn, $user, $pass);}/** * @When /^there is no user called :user$/ */public function removeUser($user) {

$this->dbh->prepare("DELETE FROM `users` WHERE username=?")->query([$user]);

}}

A new contextDatabaseContext.php

SIDE NOTE

Page 53: I put on my mink and wizard behat (tutorial)

default: suites: default: paths: [ %paths.base%/features/ ] contexts:

- FeatureContext- DatabaseContext:

- mysql:host=localhost;dbname=opencfp- root- 123

Configurationbehat.yml

SIDE NOTE

Page 54: I put on my mink and wizard behat (tutorial)

Or….actually send the email and read it via SMTP

Page 55: I put on my mink and wizard behat (tutorial)

How far is too far?What are your priorities?

Page 56: I put on my mink and wizard behat (tutorial)

Taking it too farTrue story

Page 57: I put on my mink and wizard behat (tutorial)

// Make sure your server and your behat client have the same time set// Share the secret key between the two. The code should be valid for// 30 second periods$code = sha1($secret_key . floor(time() / 30));if ($request->get("code") === $code) {

// Bypass captcha}

Easier waySimple but safe bypass

Page 58: I put on my mink and wizard behat (tutorial)

Our first talkSet the stage

Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions

Background:There is a user called "[email protected]" with password "secrets"I login as "[email protected]" with password "secrets"

Scenario: Add a new talk to our submissions...

PRACTICAL

Page 59: I put on my mink and wizard behat (tutorial)

Our first talkTalk submission in 3, 2, 1...

Scenario: Add a new talk to our submissions Given I am on "talk/create" And I fill in the following: | title | Behat Talk | | description | Awesome | | type | regular | | category | testing |

| level | mid | And I check "desired" And I press "Submit my talk!" Then I should see "Success: Successfully added talk."

PRACTICAL

Page 60: I put on my mink and wizard behat (tutorial)

Tyranny of JavaScriptDeleting a talk

Page 61: I put on my mink and wizard behat (tutorial)

Well that won’t worktalks.feature

Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions

Scenario: Delete a talkGiven create a talk called "Behat Talk"And I am on "/dashboard"When I follow "Delete"And I should not see "Behat Talk Changed"

The text "Behat Talk Changed" appears in the text of this page, but it should not. (Behat\Mink\Exception\ResponseTextException)

Page 62: I put on my mink and wizard behat (tutorial)

// Guzzle using web scraperbehat/mink-goutte-driver

// Java-based distributed browser workers (support JavaScript)behat/mink-selenium2-driverbehat/mink-sahi-driver

// node.js headless browser proxy (support JavaScript)behat/mink-zombie-driver

DriversSome take the scenic route

Page 63: I put on my mink and wizard behat (tutorial)

default: # … extensions: Behat\MinkExtension: base_url: "[your website]" sessions: # … javascript: selenium2:

browser: "firefox"wd_host: http://[ip-address-of-host]:4444/wd/hub

ConfigurationSetting up for Selenium

PRACTICAL

Page 64: I put on my mink and wizard behat (tutorial)

$ java -jar selenium-server-standalone-2.*.jar

Selenium

@javascript # Or we could use @selenium2Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions

Start Selenium Server

Specify javascript requirement

PRACTICAL

Page 65: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat --tags speaker,talk

TagsRun specific tags

@speakerFeature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions

@talk Scenario: Create a new talk Given I am logged in as a speaker ...

SIDE NOTE

Page 66: I put on my mink and wizard behat (tutorial)

Feature: Submitting and managing talks As a speaker I wish be able to submit talks so I can get a chance to talk at a conference.

@javascript Scenario: Delete a talk

Given create a talk called "Behat Talk"And I am on "/dashboard"When I fill "Delete"And I accept alertsAnd I should not see "Behat Talk"

Enable JavaScripttalks.feature

PRACTICAL

Page 67: I put on my mink and wizard behat (tutorial)

Run as JavaScripttalks.feature

Feature: Submitting and managing talks As a speaker I wish be able to submit talks so I can get a chance to talk at a conference.

Scenario: Delete a talkGiven create a talk called "Behat Talk"And I am on "/dashboard"When I follow "Delete"And I accept alertsAnd I should not see "Behat Talk"

LIVE DEMO

Page 68: I put on my mink and wizard behat (tutorial)

Scenario: Edit a talkGiven I am on "/dashboard"And I remember "tr[id^='talk']" content as "Title"When I follow "Edit"And I fill in "title" with "New Title"And I press "Update my talk!"Then I should see "New Title"And I should not see "memory:Title"

TransformationsSometimes we need to remember

PRACTICAL

Page 69: I put on my mink and wizard behat (tutorial)

CSS SelectorsDrives are just like browser, no one ever

supports everything properly...

SIDE NOTE

Page 70: I put on my mink and wizard behat (tutorial)

/** * @Transform /^memory:(.*)$/ */public function fromMemory($key) {

if (!isset($this->memory[$key])) {throw new LogicException("Entry $key does not exist");

}return $this->memory[$key];

}

TransformationsFeatureContext.php

PRACTICAL

Page 71: I put on my mink and wizard behat (tutorial)

/** * @Given /^I remember "(.*)" content as "(.*)"$/ */public function rememberContentOf($selector, $key) {

$e = $this->getSession()->getPage()->find("css", $selector);if (!is_object($e)) {

throw new LogicException("Element $selector not found"); }

$value = $e->getValue() ? $e->getValue() : $e->getText();$this->memory[$key] = $value;

}

TransformationsFeatureContext.php

PRACTICAL

Page 72: I put on my mink and wizard behat (tutorial)

class FeatureContext … {public function takeAScreenshotCalled($filename) { $driver = get_class($this->getSession()->getDriver());

if ($driver == 'Behat\Mink\Driver\Selenium2Driver') {$ss = $this->getSession()->getScreenshot();file_put_contents($filename, $ss);

}}

}

ScreenshotFeatureContext.php

PRACTICAL

Page 73: I put on my mink and wizard behat (tutorial)

Advanced Usagewith extra bells and whistles

Page 74: I put on my mink and wizard behat (tutorial)

use Behat\Behat\Hook\Scope\AfterFeatureScope; // @AfterFeature \AfterScenarioScope; // @AfterScenario \AfterStepScope; // @AfterStep \BeforeFeatureScope; // @BeforeFeature \BeforeScenarioScope; // @BeforeScenario \BeforeStepScope; // @BeforeStep \FeatureScope; // @Feature \ScenarioScope; // @Scenario \StepScope; // @Step

HooksListen in close

Page 75: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/** * @AfterScenarioScope */public function afterScenario(AfterScenarioScope $scope) {

$scenario = $scope->getScenario()->getTitle();$filename = make_safe_filename($scenario);// Take a screenshot and put it on a dashboard somewhere$this->takeAScreenshotCalled($filename);

}}

HooksFeatureContext.php

PRACTICAL

Page 76: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/** * @AfterStep */public function afterStep(AfterStepScope $scope) {

$code = $event->getTestResult()->getResultCode();if ($code == TestResult::FAILED) {

// Take a screenshot}

}}

HooksFeatureContext.php

PRACTICAL

Page 77: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/** * @Given /^(?:I )wait for AJAX to finish$/ */public function iWaitForAjaxToFinish() {

$this->getSession()->wait(5000, "(0 === jQuery.active)");}

}

AJAXThe waiting game

PRACTICAL

Page 78: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/** * @Given /^(?:I )press the letter :l$/ */public function iPressTheLetter($l) {

$s = "jQuery.event.trigger({type:'keypress', which:'$l'});"; $this->getSession()->evaluateScript($s);}

}

Raw javascriptThere might be a valid use case...

PRACTICAL

Page 79: I put on my mink and wizard behat (tutorial)

default: suites: web: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, WebContext ] api: paths: [ %paths.base%/features/api ] contexts: [ BaseContext, ApiContext ]

ConfigurationMultiple contexts

Page 80: I put on my mink and wizard behat (tutorial)

default: suites: admin: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, AdminContext ] filters: role: admin speaker: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, SpeakerContext ] filters: tags: @speaker

ConfigurationGrouping and filtering

Page 81: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat --suite admin

SuitesRun a specific suite

Feature: Managing the CFP In order to ensure that speakers can submit their papers As an admin I need to be able to open the call for papers

Page 82: I put on my mink and wizard behat (tutorial)

$ ./vendor/bin/behat --suite speaker

SuitesRun a specific suite

@speakerFeature: Submitting to the CFP In order to ensure that the conference has papers As an speaker I need to be able to submit papers

Page 83: I put on my mink and wizard behat (tutorial)

$ java -jar selenium-server-standalone-2.*.jar -role hub

Selenium Grid

$ java -jar selenium-server-standalone-2.*.jar -role node -hub http://10.41.6.62:4444/grid/register

Start the grid

Add a node

LIVE DEMO

Page 84: I put on my mink and wizard behat (tutorial)

default: extensions: Behat\MinkExtension: sessions: javascript: selenium2: wd_host: "http://127.0.0.1:4444/wb/hub" capabilities: version: ""

ConfigurationBecause magic...

LIVE DEMO

Page 85: I put on my mink and wizard behat (tutorial)

Complex UsersDealing with very complex user states

Page 86: I put on my mink and wizard behat (tutorial)

Feature: Show relevant promotions to non-paying activated customers

Scenario: Show promotion pricing to referred clients from the EUGiven I create a user with: | email-activated | | is-EU-member | | is-referred-client | | NOT has-made-purchase |Then ...

SetupWhat attributes does our user have?

Page 87: I put on my mink and wizard behat (tutorial)

interface AttributeInterface {// Get the required attributes to have this attributepublic function getDependencies();// Does this user have this attribute?public function has(MinkContext $context);// Allocate this attribute to the userpublic function allocate(MinkContext $context);// Attempt to remove this attribute from the userpublic function remove(MinkContext $context);

}

AttributesAttributeInterface.php

Page 88: I put on my mink and wizard behat (tutorial)

class IsEUMember extends AttributeInterface {public function getDependencies() { return ["email-activated"]; }

public function has(MinkContext $context) {$context->visit("/profile");$field = $context->getSession()

->getPage()->findField("country");

return in_array($field->getValue(), $this->eu_countries);}

}

AttributesIsEUMember.php

Page 89: I put on my mink and wizard behat (tutorial)

// ...public function allocate(MinkContext $context) {

$context->visit("/profile");$context->selectOption("country", "Netherlands");$context->pressButton("Update");

}

public function remove(MinkContext $context) {$context->visit("/profile");$context->selectOption("country", "UK"); // Future-proofing$context->pressButton("Update");

}

AttributesIsEUMember.php

Page 90: I put on my mink and wizard behat (tutorial)

// This class must handle dependency conflicts by examining the// dependencies of each with/without attributeclass Request {

protected $attributes = [];public function with(AttributeInterface $feature);public function without(AttributeInterface $feature);// List of attributes (including dependents) requiredpublic function getWith();// List of attributes (including dependents) that must be removedpublic function getWithout();

}

RequestRequest.php

Page 91: I put on my mink and wizard behat (tutorial)

class User {public function __construct(MinkContext $context, $request) {

$this->createNewUser($context);foreach ($request->getWith() as $attr) {

$attr->allocate($content);}foreach ($request->getWithout() as $attr) {

$attr->remove($content);}

}public function canSupport($request);

}

UserUser.php

Page 92: I put on my mink and wizard behat (tutorial)

use Behat\Gherkin\Node\TableNode;class FeatureContext … {

/** * @Given /^(?:I )create a user with:$/ */public function iCreateAUser($type, TableNode $table) {

$attributes = $table->getRowsHash();$request = new Request();// Build using $request->with(...) & $request->without(...);$user = new User($context, $request);

}}

TableNodeHandling a table

Page 93: I put on my mink and wizard behat (tutorial)

Some days everything is made of glass

Common Gotchas

Page 94: I put on my mink and wizard behat (tutorial)

PermutationsThe reason why testing is painful

Page 95: I put on my mink and wizard behat (tutorial)

Expect breakagesAnd that’s a good thing

Page 96: I put on my mink and wizard behat (tutorial)

Speed vs CoverageFind the right balance

Page 97: I put on my mink and wizard behat (tutorial)

Keep Selenium updatedBrowsers change faster than fashion trends

Page 98: I put on my mink and wizard behat (tutorial)

Behat documentshttp://docs.behat.org points to v2.5 docs but

doesn’t tell you.Use http://docs.behat.org/en/v3.0/

Page 99: I put on my mink and wizard behat (tutorial)

Questions?or ask me later via @thomas_shone

Page 100: I put on my mink and wizard behat (tutorial)

Feature: Administration of talk submissions In order to be able to manage a conference, as an admin, I should be able to manage talks

Background:Given there is a speaker registered as "[email protected]" with a password "secrets"And User "[email protected]" has admin rightsAnd I login as "[email protected]" with password "secrets"

Scenario: Approve a submitted paper Scenario: Decline a submitted paper

Final TaskApply what you know

Page 101: I put on my mink and wizard behat (tutorial)

HintAdmin rights are granted by a CLI too

Page 102: I put on my mink and wizard behat (tutorial)

@javascriptFeature: Admin In order to be able to manage a conference, as an admin, I should be able to manage talks

Background: Given there is a speaker registered as "[email protected]" with a password "secrets" And User "[email protected]" has admin rights And I login as "[email protected]" with password "secrets"

Scenario: Approve a submitted paper Given I create a talk called "New Talk" And I am on "/admin/talks" And I click on element ".js-talk-select" Then I should see an ".check-select--selected" element

Scenario: Reject a submitted paper Given I am on "/admin/talks" And I click on element ".check-select--selected" Then I should not see an ".check-select--selected" element

Model? Answer

Page 103: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/**

* @Given User :email has admin rights */

public function userHasAdminRights($email){

exec("php -f bin/opencfp admin:promote " . escapeshellarg($email));}

/** * @Given I click on element :selector */

public function iClickOnElement($selector) { $element = $this->getSession()->getPage()->find("css", $selector); if (!is_object($element)) { throw new LogicException("Element $selector not found"); } $this->getSession()->evaluateScript('$("' . $selector . '").click();');

}}

Model? Answer

Page 104: I put on my mink and wizard behat (tutorial)

class FeatureContext … {/**

* @Given I create a talk called :title */

public function iCreateATalkCalled($title) { $this->visit("/dashboard"); $this->clickLink("Submit a talk"); $this->fillField("title", $title); $this->fillField("description", "Awesome"); $this->fillField("type", "regular"); $this->fillField("category", "testing"); $this->fillField("level", "mid"); $this->checkOption("desired"); $this->pressButton("Submit my talk!");}

}

Model? Answer

Page 105: I put on my mink and wizard behat (tutorial)

Thank youPhoto from Flickr by John Morey, TrojanRat, Gerry Machen, USFS Region 5,

Peregrina Tyss and Thomas Hawk