SlideShare a Scribd company logo
1 of 105
Download to read offline
I put on my
mink and
wizard behat
Questing in the world of
front end testing
9:30 Setup
9:45 Introduction to Front End Testing
10:15 We write some tests
10:45 Coffee break
11:00 I talk about some more advanced stuff
11:30 We write some more tests
12:15 We attempt a grid
12:30 Q&A
12:45 End
Schedule
100% chance of incorrectness
$ 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 Practical
Vagrant
$ sudo vim /etc/hosts
10.41.6.62 opencfp.dev
# copy just T9AG1x/opencfp
$ cd opencfp
$ php vendor/behat/behat/bin/behat
No Vagrant
Booking.com
@thomas_shone
W
E
AR
E
H
IR
IN
G
Hoare Logic
{P} C {Q}
Hodor Logic
{P} C {Q}
Why?
What's the benefit?
Meet The Team
Don’t feed the druid after midnight
Task
Each test has a different approach
Barbarian
Quality Assurance
Ranger
Unit Test
Cleric
Continuous Integration
Wizard
Front End Test
Dreaded Bugbear
Teamwork
Wizards are squishy
$ 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 Practical
Vagrant
$ sudo vim /etc/hosts
10.41.6.62 opencfp.dev
# copy just T9AG1x/opencfp
$ cd opencfp
$ php vendor/behat/behat/bin/behat
No Vagrant
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)
Feature: Party harmony
As 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 Syntax
Readable testing language
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 into
FeatureContext.php
Feature: Party harmony
As 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 Syntax
What’s missing?
Scenario:
Given that the wizard has 10 cookies
And 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
Feature: Party harmony
As 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 charged
And 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
???
As a leader, I want to ensure harmony and
mutual trust, so that we work as a team
User stories
As a <role>, I want to <desire> so that
<benefit>
Front end testing is code coverage for your user stories
User stories
Coverage
Features are your contract with the stakeholders
Contract
Scenarios are the use cases that outline the user story
Scenarios
Legend has it...
… that someone once convinced their PO to
write all their front end tests.
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
OK...
Lets drop the metaphor and get to actual
code
$ 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"
PR
AC
TIC
AL
$ ./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
Initialize
Create a new test suite
PR
AC
TIC
AL
use BehatMinkExtensionContextMinkContext;
class FeatureContext extends MinkContext … {
…
}
Context
FeatureContext.php
PR
AC
TIC
AL
$ ./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>(?:
[^"]|")*)"$/
Context
What does Mink bring to the table?
PR
AC
TIC
AL
default:
suites:
default:
paths: [ %paths.base%/features/ ]
contexts: [ FeatureContext ]
extensions:
BehatMinkExtension:
base_url: "[your website]"
sessions:
default:
goutte: ~
Configuration
behat.yml
PR
AC
TIC
AL
Feature: Authentication and authorisation
As 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 "some@guy.com"
And I fill in "password" with "invalid"
And I press "Login"
Then I should see "Invalid Email or Password"
Our first feature
auth.feature
PR
AC
TIC
AL
$ ./vendor/bin/behat --config behat.yml features/auth.feature
Scenario: Attempt to login with an invalid account
Given I am on "/login"
When I fill in "email" with "some@guy.com"
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)
Victory
output
PR
AC
TIC
AL
Feature: Authentication and authorisation
As 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 "some@guy.com" with password "invalid"
Then I should see "Invalid Email or Password"
Simplify
auth.feature
PR
AC
TIC
AL
Scenario: Attempt to login with an invalid account
Given I login as "bob@smith.com" 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();
}
Simplify
output
PR
AC
TIC
AL
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");
}
}
Simplify
FeatureContext.php
PR
AC
TIC
AL
Scenario: Attempt to login with an invalid account
Given I login as "bob@smith.com" with password "invalid"
Then I should see "Invalid Email or Password"
1 scenarios (1 passed)
2 steps (2 passed)
Simplify
output
PR
AC
TIC
AL
// 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();
Session
BehatMinkSession
SID
E
N
O
TE
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);
BehatMinkElementDocumentElement
SID
E
N
O
TE
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);
BehatMinkDriverDriverInterface
SID
E
N
O
TE
Feature: Authentication and authorisation
As 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 "some@guy.com"
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 hurdle
This ones easy, you do…. oh….
PR
AC
TIC
AL
Migration and seeding
Doctrine, Propel, Laravel, Phinx
$ composer require robmorgan/phinx="~0.4"
Phinx to the rescue
Install
$ php vendor/bin/phinx init
Phinx by Rob Morgan - https://phinx.org. version 0.4.3
Created ./phinx.xml
Configuration
$ php vendor/bin/phinx create InitialMigration
Creating
SID
E
N
O
TE
#!/usr/bin/env bash
DATABASE="opencfp"
mysql -e "DROP DATABASE IF EXISTS $DATABASE" -uroot -p123
mysql -e "CREATE DATABASE $DATABASE" -uroot -p123
vendor/bin/phinx migrate
vendor/bin/behat
It’s a bit extreme
run-behat-test.sh
PR
AC
TIC
AL
SAVEPOINT identifier;
# Run tests
ROLLBACK TO SAVEPOINT identifier;
RELEASE SAVEPOINT identifier;
Transaction/Rollback
Roll your own solution
Activation emails?
smtp-sink, FakeSMTP, etc
# Stop the currently running service
sudo 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-sink
run-behat-test.sh
Or….
you could just read the activation code from
the database directly
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 context
DatabaseContext.php
SID
E
N
O
TE
default:
suites:
default:
paths: [ %paths.base%/features/ ]
contexts:
- FeatureContext
- DatabaseContext:
- mysql:host=localhost;dbname=opencfp
- root
- 123
Configuration
behat.yml
SID
E
N
O
TE
Or….
actually send the email and read it via SMTP
How far is too far?
What are your priorities?
Taking it too far
True story
// 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 way
Simple but safe bypass
Our first talk
Set 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 "some@guy.com" with password "secrets"
I login as "some@guy.com" with password "secrets"
Scenario: Add a new talk to our submissions
...
PR
AC
TIC
AL
Our first talk
Talk 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."
PR
AC
TIC
AL
Tyranny of JavaScript
Deleting a talk
Well that won’t work
talks.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 talk
Given 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. (BehatMinkExceptionResponseTextException)
// Guzzle using web scraper
behat/mink-goutte-driver
// Java-based distributed browser workers (support JavaScript)
behat/mink-selenium2-driver
behat/mink-sahi-driver
// node.js headless browser proxy (support JavaScript)
behat/mink-zombie-driver
Drivers
Some take the scenic route
default:
# …
extensions:
BehatMinkExtension:
base_url: "[your website]"
sessions:
# …
javascript:
selenium2:
browser: "firefox"
wd_host: http://[ip-address-of-host]:4444/wd/hub
Configuration
Setting up for Selenium
PR
AC
TIC
AL
$ java -jar selenium-server-standalone-2.*.jar
Selenium
@javascript # Or we could use @selenium2
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
Start Selenium Server
Specify javascript requirement
PR
AC
TIC
AL
$ ./vendor/bin/behat --tags speaker,talk
Tags
Run specific tags
@speaker
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
@talk
Scenario: Create a new talk
Given I am logged in as a speaker ...
SID
E
N
O
TE
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 alerts
And I should not see "Behat Talk"
Enable JavaScript
talks.feature
PR
AC
TIC
AL
Run as JavaScript
talks.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 talk
Given create a talk called "Behat Talk"
And I am on "/dashboard"
When I follow "Delete"
And I accept alerts
And I should not see "Behat Talk"
LIVE
D
EM
O
Scenario: Edit a talk
Given 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"
Transformations
Sometimes we need to remember
PR
AC
TIC
AL
CSS Selectors
Drives are just like browser, no one ever
supports everything properly...
SID
E
N
O
TE
/**
* @Transform /^memory:(.*)$/
*/
public function fromMemory($key) {
if (!isset($this->memory[$key])) {
throw new LogicException("Entry $key does not exist");
}
return $this->memory[$key];
}
Transformations
FeatureContext.php
PR
AC
TIC
AL
/**
* @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;
}
Transformations
FeatureContext.php
PR
AC
TIC
AL
class FeatureContext … {
public function takeAScreenshotCalled($filename) {
$driver = get_class($this->getSession()->getDriver());
if ($driver == 'BehatMinkDriverSelenium2Driver') {
$ss = $this->getSession()->getScreenshot();
file_put_contents($filename, $ss);
}
}
}
Screenshot
FeatureContext.php
PR
AC
TIC
AL
Advanced Usage
with extra bells and whistles
use BehatBehatHookScopeAfterFeatureScope; // @AfterFeature
AfterScenarioScope; // @AfterScenario
AfterStepScope; // @AfterStep
BeforeFeatureScope; // @BeforeFeature
BeforeScenarioScope; // @BeforeScenario
BeforeStepScope; // @BeforeStep
FeatureScope; // @Feature
ScenarioScope; // @Scenario
StepScope; // @Step
Hooks
Listen in close
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);
}
}
Hooks
FeatureContext.php
PR
AC
TIC
AL
class FeatureContext … {
/**
* @AfterStep
*/
public function afterStep(AfterStepScope $scope) {
$code = $event->getTestResult()->getResultCode();
if ($code == TestResult::FAILED) {
// Take a screenshot
}
}
}
Hooks
FeatureContext.php
PR
AC
TIC
AL
class FeatureContext … {
/**
* @Given /^(?:I )wait for AJAX to finish$/
*/
public function iWaitForAjaxToFinish() {
$this->getSession()->wait(5000, "(0 === jQuery.active)");
}
}
AJAX
The waiting game
PR
AC
TIC
AL
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 javascript
There might be a valid use case...
PR
AC
TIC
AL
default:
suites:
web:
paths: [ %paths.base%/features/web ]
contexts: [ BaseContext, WebContext ]
api:
paths: [ %paths.base%/features/api ]
contexts: [ BaseContext, ApiContext ]
Configuration
Multiple contexts
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
Configuration
Grouping and filtering
$ ./vendor/bin/behat --suite admin
Suites
Run 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
$ ./vendor/bin/behat --suite speaker
Suites
Run a specific suite
@speaker
Feature: Submitting to the CFP
In order to ensure that the conference has papers
As an speaker
I need to be able to submit papers
$ 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
D
EM
O
default:
extensions:
BehatMinkExtension:
sessions:
javascript:
selenium2:
wd_host: "http://127.0.0.1:4444/wb/hub"
capabilities:
version: ""
Configuration
Because magic...
LIVE
D
EM
O
Complex Users
Dealing with very complex user states
Feature: Show relevant promotions to non-paying activated customers
Scenario: Show promotion pricing to referred clients from the EU
Given I create a user with:
| email-activated |
| is-EU-member |
| is-referred-client |
| NOT has-made-purchase |
Then ...
Setup
What attributes does our user have?
interface AttributeInterface {
// Get the required attributes to have this attribute
public function getDependencies();
// Does this user have this attribute?
public function has(MinkContext $context);
// Allocate this attribute to the user
public function allocate(MinkContext $context);
// Attempt to remove this attribute from the user
public function remove(MinkContext $context);
}
Attributes
AttributeInterface.php
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);
}
}
Attributes
IsEUMember.php
// ...
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");
}
Attributes
IsEUMember.php
// This class must handle dependency conflicts by examining the
// dependencies of each with/without attribute
class Request {
protected $attributes = [];
public function with(AttributeInterface $feature);
public function without(AttributeInterface $feature);
// List of attributes (including dependents) required
public function getWith();
// List of attributes (including dependents) that must be removed
public function getWithout();
}
Request
Request.php
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);
}
User
User.php
use BehatGherkinNodeTableNode;
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);
}
}
TableNode
Handling a table
Some days everything is made of glass
Common Gotchas
Permutations
The reason why testing is painful
Expect breakages
And that’s a good thing
Speed vs Coverage
Find the right balance
Keep Selenium updated
Browsers change faster than fashion trends
Behat documents
http://docs.behat.org points to v2.5 docs but
doesn’t tell you.
Use http://docs.behat.org/en/v3.0/
Questions?
or ask me later via @thomas_shone
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 "admin@guy.com" with a
password "secrets"
And User "admin@guy.com" has admin rights
And I login as "admin@guy.com" with password "secrets"
Scenario: Approve a submitted paper
Scenario: Decline a submitted paper
Final Task
Apply what you know
Hint
Admin rights are granted by a CLI too
@javascript
Feature: 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 "admin@opencfp.org" with a password "secrets"
And User "admin@opencfp.org" has admin rights
And I login as "admin@opencfp.org" 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
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
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
Thank you
Photo from Flickr by John Morey, TrojanRat, Gerry Machen, USFS Region 5,
Peregrina Tyss and Thomas Hawk

More Related Content

What's hot

Phone calls and sms from php
Phone calls and sms from phpPhone calls and sms from php
Phone calls and sms from phpDavid Stockton
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I thinkWim Godden
 
The promise of asynchronous php
The promise of asynchronous phpThe promise of asynchronous php
The promise of asynchronous phpWim Godden
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreRyan Weaver
 
When dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWhen dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWim Godden
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I thinkWim Godden
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityRyan Weaver
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsIgnacio Martín
 
Joe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand DwrJoe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand Dwrdeimos
 
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)Dotan Dimet
 
Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuerydeimos
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I thinkWim Godden
 
Let's play a game with blackfire player
Let's play a game with blackfire playerLet's play a game with blackfire player
Let's play a game with blackfire playerMarcin Czarnecki
 
Testing Web Applications with GEB
Testing Web Applications with GEBTesting Web Applications with GEB
Testing Web Applications with GEBHoward Lewis Ship
 
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…D
 
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...D
 
Mojolicious - A new hope
Mojolicious - A new hopeMojolicious - A new hope
Mojolicious - A new hopeMarcus Ramberg
 
Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!Anatoly Sharifulin
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowKacper Gunia
 

What's hot (20)

Phone calls and sms from php
Phone calls and sms from phpPhone calls and sms from php
Phone calls and sms from php
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
 
The promise of asynchronous php
The promise of asynchronous phpThe promise of asynchronous php
The promise of asynchronous php
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
When dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniquesWhen dynamic becomes static: the next step in web caching techniques
When dynamic becomes static: the next step in web caching techniques
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Joe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand DwrJoe Walker Interactivewebsites Cometand Dwr
Joe Walker Interactivewebsites Cometand Dwr
 
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
Mojolicious - Perl Framework for the Real-Time Web (Lightning Talk)
 
PhpBB meets Symfony2
PhpBB meets Symfony2PhpBB meets Symfony2
PhpBB meets Symfony2
 
Remy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQueryRemy Sharp The DOM scripting toolkit jQuery
Remy Sharp The DOM scripting toolkit jQuery
 
My app is secure... I think
My app is secure... I thinkMy app is secure... I think
My app is secure... I think
 
Let's play a game with blackfire player
Let's play a game with blackfire playerLet's play a game with blackfire player
Let's play a game with blackfire player
 
Testing Web Applications with GEB
Testing Web Applications with GEBTesting Web Applications with GEB
Testing Web Applications with GEB
 
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
Things Your Mother Didnt Tell You About Bundle Configurations - Symfony Live…
 
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
Things Your Mother Didn't Tell You About Bundle Configurations - Symfony Live...
 
Mojolicious - A new hope
Mojolicious - A new hopeMojolicious - A new hope
Mojolicious - A new hope
 
Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!Mojolicious. Веб в коробке!
Mojolicious. Веб в коробке!
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
 

Viewers also liked

BDD Language in PHPUnit Tests
BDD Language in PHPUnit TestsBDD Language in PHPUnit Tests
BDD Language in PHPUnit Testsmfrost503
 
Amazon Web Services
Amazon Web ServicesAmazon Web Services
Amazon Web ServicesDuy Lâm
 
[BDD] Introduction to Behat (PL)
[BDD] Introduction to Behat (PL)[BDD] Introduction to Behat (PL)
[BDD] Introduction to Behat (PL)Piotr Pelczar
 
Acceptance & Integration Testing With Behat (PBC11)
Acceptance & Integration Testing With Behat (PBC11)Acceptance & Integration Testing With Behat (PBC11)
Acceptance & Integration Testing With Behat (PBC11)benwaine
 
Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)Jessica Mauerhan
 
Web Acceptance Testing with Behat
Web Acceptance Testing with BehatWeb Acceptance Testing with Behat
Web Acceptance Testing with BehatFabian Kiss
 
Behat bdd training (php) course slides pdf
Behat bdd training (php) course slides pdfBehat bdd training (php) course slides pdf
Behat bdd training (php) course slides pdfseleniumbootcamp
 

Viewers also liked (9)

BDD Language in PHPUnit Tests
BDD Language in PHPUnit TestsBDD Language in PHPUnit Tests
BDD Language in PHPUnit Tests
 
Amazon Web Services
Amazon Web ServicesAmazon Web Services
Amazon Web Services
 
[BDD] Introduction to Behat (PL)
[BDD] Introduction to Behat (PL)[BDD] Introduction to Behat (PL)
[BDD] Introduction to Behat (PL)
 
BDD in Symfony2
BDD in Symfony2BDD in Symfony2
BDD in Symfony2
 
Acceptance & Integration Testing With Behat (PBC11)
Acceptance & Integration Testing With Behat (PBC11)Acceptance & Integration Testing With Behat (PBC11)
Acceptance & Integration Testing With Behat (PBC11)
 
Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)Behat - Beyond the Basics (2016 - SunshinePHP)
Behat - Beyond the Basics (2016 - SunshinePHP)
 
Web Acceptance Testing with Behat
Web Acceptance Testing with BehatWeb Acceptance Testing with Behat
Web Acceptance Testing with Behat
 
Behat bdd training (php) course slides pdf
Behat bdd training (php) course slides pdfBehat bdd training (php) course slides pdf
Behat bdd training (php) course slides pdf
 
Behat 3.0 meetup (March)
Behat 3.0 meetup (March)Behat 3.0 meetup (March)
Behat 3.0 meetup (March)
 

Similar to I put on my mink and wizard behat (tutorial)

4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebookguoqing75
 
Facebook的缓存系统
Facebook的缓存系统Facebook的缓存系统
Facebook的缓存系统yiditushe
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetAchieve Internet
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasminePaulo Ragonha
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207patter
 
fog or: How I Learned to Stop Worrying and Love the Cloud
fog or: How I Learned to Stop Worrying and Love the Cloudfog or: How I Learned to Stop Worrying and Love the Cloud
fog or: How I Learned to Stop Worrying and Love the CloudWesley Beary
 
The promise of asynchronous php
The promise of asynchronous phpThe promise of asynchronous php
The promise of asynchronous phpWim Godden
 
A reviravolta do desenvolvimento web
A reviravolta do desenvolvimento webA reviravolta do desenvolvimento web
A reviravolta do desenvolvimento webWallace Reis
 
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)Brian Sam-Bodden
 
Caching Up and Down the Stack
Caching Up and Down the StackCaching Up and Down the Stack
Caching Up and Down the StackDan Kuebrich
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesLindsay Holmwood
 
Express Presentation
Express PresentationExpress Presentation
Express Presentationaaronheckmann
 
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)Wesley Beary
 
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...Gosuke Miyashita
 
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceBeijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceJesse Vincent
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐいHisateru Tanaka
 
Puppet for dummies - PHPBenelux UG edition
Puppet for dummies - PHPBenelux UG editionPuppet for dummies - PHPBenelux UG edition
Puppet for dummies - PHPBenelux UG editionJoshua Thijssen
 
Asynchronous PHP and Real-time Messaging
Asynchronous PHP and Real-time MessagingAsynchronous PHP and Real-time Messaging
Asynchronous PHP and Real-time MessagingSteve Rhoades
 

Similar to I put on my mink and wizard behat (tutorial) (20)

4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook4069180 Caching Performance Lessons From Facebook
4069180 Caching Performance Lessons From Facebook
 
Facebook的缓存系统
Facebook的缓存系统Facebook的缓存系统
Facebook的缓存系统
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and Puppet
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
symfony on action - WebTech 207
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
 
YouDrup_in_Drupal
YouDrup_in_DrupalYouDrup_in_Drupal
YouDrup_in_Drupal
 
YouDrup_in_Drupal
YouDrup_in_DrupalYouDrup_in_Drupal
YouDrup_in_Drupal
 
fog or: How I Learned to Stop Worrying and Love the Cloud
fog or: How I Learned to Stop Worrying and Love the Cloudfog or: How I Learned to Stop Worrying and Love the Cloud
fog or: How I Learned to Stop Worrying and Love the Cloud
 
The promise of asynchronous php
The promise of asynchronous phpThe promise of asynchronous php
The promise of asynchronous php
 
A reviravolta do desenvolvimento web
A reviravolta do desenvolvimento webA reviravolta do desenvolvimento web
A reviravolta do desenvolvimento web
 
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
 
Caching Up and Down the Stack
Caching Up and Down the StackCaching Up and Down the Stack
Caching Up and Down the Stack
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websites
 
Express Presentation
Express PresentationExpress Presentation
Express Presentation
 
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)
fog or: How I Learned to Stop Worrying and Love the Cloud (OpenStack Edition)
 
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
Yapc::Asia 2008 Tokyo - Easy system administration programming with a framewo...
 
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret SauceBeijing Perl Workshop 2008 Hiveminder Secret Sauce
Beijing Perl Workshop 2008 Hiveminder Secret Sauce
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
Puppet for dummies - PHPBenelux UG edition
Puppet for dummies - PHPBenelux UG editionPuppet for dummies - PHPBenelux UG edition
Puppet for dummies - PHPBenelux UG edition
 
Asynchronous PHP and Real-time Messaging
Asynchronous PHP and Real-time MessagingAsynchronous PHP and Real-time Messaging
Asynchronous PHP and Real-time Messaging
 

More from xsist10

Security theatre (Scotland php)
Security theatre (Scotland php)Security theatre (Scotland php)
Security theatre (Scotland php)xsist10
 
Security Theatre (PHP Leuven)
Security Theatre (PHP Leuven)Security Theatre (PHP Leuven)
Security Theatre (PHP Leuven)xsist10
 
Security Theatre - Confoo
Security Theatre - ConfooSecurity Theatre - Confoo
Security Theatre - Confooxsist10
 
Security Theatre - PHP UK Conference
Security Theatre - PHP UK ConferenceSecurity Theatre - PHP UK Conference
Security Theatre - PHP UK Conferencexsist10
 
Security Theatre - Benelux
Security Theatre - BeneluxSecurity Theatre - Benelux
Security Theatre - Beneluxxsist10
 
Security Theatre - AmsterdamPHP
Security Theatre - AmsterdamPHPSecurity Theatre - AmsterdamPHP
Security Theatre - AmsterdamPHPxsist10
 
PHP SA 2014 - Releasing Your Open Source Project
PHP SA 2014 - Releasing Your Open Source ProjectPHP SA 2014 - Releasing Your Open Source Project
PHP SA 2014 - Releasing Your Open Source Projectxsist10
 
PHP SA 2013 - The weak points in our PHP projects
PHP SA 2013 - The weak points in our PHP projectsPHP SA 2013 - The weak points in our PHP projects
PHP SA 2013 - The weak points in our PHP projectsxsist10
 

More from xsist10 (8)

Security theatre (Scotland php)
Security theatre (Scotland php)Security theatre (Scotland php)
Security theatre (Scotland php)
 
Security Theatre (PHP Leuven)
Security Theatre (PHP Leuven)Security Theatre (PHP Leuven)
Security Theatre (PHP Leuven)
 
Security Theatre - Confoo
Security Theatre - ConfooSecurity Theatre - Confoo
Security Theatre - Confoo
 
Security Theatre - PHP UK Conference
Security Theatre - PHP UK ConferenceSecurity Theatre - PHP UK Conference
Security Theatre - PHP UK Conference
 
Security Theatre - Benelux
Security Theatre - BeneluxSecurity Theatre - Benelux
Security Theatre - Benelux
 
Security Theatre - AmsterdamPHP
Security Theatre - AmsterdamPHPSecurity Theatre - AmsterdamPHP
Security Theatre - AmsterdamPHP
 
PHP SA 2014 - Releasing Your Open Source Project
PHP SA 2014 - Releasing Your Open Source ProjectPHP SA 2014 - Releasing Your Open Source Project
PHP SA 2014 - Releasing Your Open Source Project
 
PHP SA 2013 - The weak points in our PHP projects
PHP SA 2013 - The weak points in our PHP projectsPHP SA 2013 - The weak points in our PHP projects
PHP SA 2013 - The weak points in our PHP projects
 

Recently uploaded

Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...itnewsafrica
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxLoriGlavin3
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentPim van der Noll
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterMydbops
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integrationmarketing932765
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkPixlogix Infotech
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observabilityitnewsafrica
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPathCommunity
 
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...itnewsafrica
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructureitnewsafrica
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Mark Goldstein
 

Recently uploaded (20)

Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...Zeshan Sattar- Assessing the skill requirements and industry expectations for...
Zeshan Sattar- Assessing the skill requirements and industry expectations for...
 
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptxA Deep Dive on Passkeys: FIDO Paris Seminar.pptx
A Deep Dive on Passkeys: FIDO Paris Seminar.pptx
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
Scale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL RouterScale your database traffic with Read & Write split using MySQL Router
Scale your database traffic with Read & Write split using MySQL Router
 
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS:  6 Ways to Automate Your Data IntegrationBridging Between CAD & GIS:  6 Ways to Automate Your Data Integration
Bridging Between CAD & GIS: 6 Ways to Automate Your Data Integration
 
React Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App FrameworkReact Native vs Ionic - The Best Mobile App Framework
React Native vs Ionic - The Best Mobile App Framework
 
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security ObservabilityGlenn Lazarus- Why Your Observability Strategy Needs Security Observability
Glenn Lazarus- Why Your Observability Strategy Needs Security Observability
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
UiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to HeroUiPath Community: Communication Mining from Zero to Hero
UiPath Community: Communication Mining from Zero to Hero
 
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...Abdul Kader Baba- Managing Cybersecurity Risks  and Compliance Requirements i...
Abdul Kader Baba- Managing Cybersecurity Risks and Compliance Requirements i...
 
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical InfrastructureVarsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
Varsha Sewlal- Cyber Attacks on Critical Critical Infrastructure
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
Arizona Broadband Policy Past, Present, and Future Presentation 3/25/24
 

I put on my mink and wizard behat (tutorial)

  • 1. I put on my mink and wizard behat Questing in the world of front end testing
  • 2. 9:30 Setup 9:45 Introduction to Front End Testing 10:15 We write some tests 10:45 Coffee break 11:00 I talk about some more advanced stuff 11:30 We write some more tests 12:15 We attempt a grid 12:30 Q&A 12:45 End Schedule 100% chance of incorrectness
  • 3. $ 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 Practical Vagrant $ sudo vim /etc/hosts 10.41.6.62 opencfp.dev # copy just T9AG1x/opencfp $ cd opencfp $ php vendor/behat/behat/bin/behat No Vagrant
  • 8. Meet The Team Don’t feed the druid after midnight
  • 9. Task Each test has a different approach
  • 14.
  • 17. $ 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 Practical Vagrant $ sudo vim /etc/hosts 10.41.6.62 opencfp.dev # copy just T9AG1x/opencfp $ cd opencfp $ php vendor/behat/behat/bin/behat No Vagrant
  • 18. 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)
  • 19. Feature: Party harmony As 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 Syntax Readable testing language
  • 20. 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 into FeatureContext.php
  • 21. Feature: Party harmony As 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 Syntax What’s missing?
  • 22. Scenario: Given that the wizard has 10 cookies And 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
  • 23. Feature: Party harmony As 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 charged And 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
  • 24. ??? As a leader, I want to ensure harmony and mutual trust, so that we work as a team
  • 25. User stories As a <role>, I want to <desire> so that <benefit>
  • 26. Front end testing is code coverage for your user stories User stories Coverage Features are your contract with the stakeholders Contract Scenarios are the use cases that outline the user story Scenarios
  • 27. Legend has it... … that someone once convinced their PO to write all their front end tests.
  • 28. 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
  • 29. OK... Lets drop the metaphor and get to actual code
  • 30. $ 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" PR AC TIC AL
  • 31. $ ./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 Initialize Create a new test suite PR AC TIC AL
  • 32. use BehatMinkExtensionContextMinkContext; class FeatureContext extends MinkContext … { … } Context FeatureContext.php PR AC TIC AL
  • 33. $ ./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>(?: [^"]|")*)"$/ Context What does Mink bring to the table? PR AC TIC AL
  • 34. default: suites: default: paths: [ %paths.base%/features/ ] contexts: [ FeatureContext ] extensions: BehatMinkExtension: base_url: "[your website]" sessions: default: goutte: ~ Configuration behat.yml PR AC TIC AL
  • 35. Feature: Authentication and authorisation As 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 "some@guy.com" And I fill in "password" with "invalid" And I press "Login" Then I should see "Invalid Email or Password" Our first feature auth.feature PR AC TIC AL
  • 36. $ ./vendor/bin/behat --config behat.yml features/auth.feature Scenario: Attempt to login with an invalid account Given I am on "/login" When I fill in "email" with "some@guy.com" 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) Victory output PR AC TIC AL
  • 37. Feature: Authentication and authorisation As 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 "some@guy.com" with password "invalid" Then I should see "Invalid Email or Password" Simplify auth.feature PR AC TIC AL
  • 38. Scenario: Attempt to login with an invalid account Given I login as "bob@smith.com" 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(); } Simplify output PR AC TIC AL
  • 39. 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"); } } Simplify FeatureContext.php PR AC TIC AL
  • 40. Scenario: Attempt to login with an invalid account Given I login as "bob@smith.com" with password "invalid" Then I should see "Invalid Email or Password" 1 scenarios (1 passed) 2 steps (2 passed) Simplify output PR AC TIC AL
  • 41. // 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(); Session BehatMinkSession SID E N O TE
  • 42. 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); BehatMinkElementDocumentElement SID E N O TE
  • 43. 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); BehatMinkDriverDriverInterface SID E N O TE
  • 44. Feature: Authentication and authorisation As 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 "some@guy.com" 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 hurdle This ones easy, you do…. oh…. PR AC TIC AL
  • 45. Migration and seeding Doctrine, Propel, Laravel, Phinx
  • 46. $ composer require robmorgan/phinx="~0.4" Phinx to the rescue Install $ php vendor/bin/phinx init Phinx by Rob Morgan - https://phinx.org. version 0.4.3 Created ./phinx.xml Configuration $ php vendor/bin/phinx create InitialMigration Creating SID E N O TE
  • 47. #!/usr/bin/env bash DATABASE="opencfp" mysql -e "DROP DATABASE IF EXISTS $DATABASE" -uroot -p123 mysql -e "CREATE DATABASE $DATABASE" -uroot -p123 vendor/bin/phinx migrate vendor/bin/behat It’s a bit extreme run-behat-test.sh PR AC TIC AL
  • 48. SAVEPOINT identifier; # Run tests ROLLBACK TO SAVEPOINT identifier; RELEASE SAVEPOINT identifier; Transaction/Rollback Roll your own solution
  • 50. # Stop the currently running service sudo 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-sink run-behat-test.sh
  • 51. Or…. you could just read the activation code from the database directly
  • 52. 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 context DatabaseContext.php SID E N O TE
  • 53. default: suites: default: paths: [ %paths.base%/features/ ] contexts: - FeatureContext - DatabaseContext: - mysql:host=localhost;dbname=opencfp - root - 123 Configuration behat.yml SID E N O TE
  • 54. Or…. actually send the email and read it via SMTP
  • 55. How far is too far? What are your priorities?
  • 56. Taking it too far True story
  • 57. // 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 way Simple but safe bypass
  • 58. Our first talk Set 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 "some@guy.com" with password "secrets" I login as "some@guy.com" with password "secrets" Scenario: Add a new talk to our submissions ... PR AC TIC AL
  • 59. Our first talk Talk 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." PR AC TIC AL
  • 61. Well that won’t work talks.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 talk Given 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. (BehatMinkExceptionResponseTextException)
  • 62. // Guzzle using web scraper behat/mink-goutte-driver // Java-based distributed browser workers (support JavaScript) behat/mink-selenium2-driver behat/mink-sahi-driver // node.js headless browser proxy (support JavaScript) behat/mink-zombie-driver Drivers Some take the scenic route
  • 63. default: # … extensions: BehatMinkExtension: base_url: "[your website]" sessions: # … javascript: selenium2: browser: "firefox" wd_host: http://[ip-address-of-host]:4444/wd/hub Configuration Setting up for Selenium PR AC TIC AL
  • 64. $ java -jar selenium-server-standalone-2.*.jar Selenium @javascript # Or we could use @selenium2 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 Start Selenium Server Specify javascript requirement PR AC TIC AL
  • 65. $ ./vendor/bin/behat --tags speaker,talk Tags Run specific tags @speaker 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 @talk Scenario: Create a new talk Given I am logged in as a speaker ... SID E N O TE
  • 66. 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 alerts And I should not see "Behat Talk" Enable JavaScript talks.feature PR AC TIC AL
  • 67. Run as JavaScript talks.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 talk Given create a talk called "Behat Talk" And I am on "/dashboard" When I follow "Delete" And I accept alerts And I should not see "Behat Talk" LIVE D EM O
  • 68. Scenario: Edit a talk Given 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" Transformations Sometimes we need to remember PR AC TIC AL
  • 69. CSS Selectors Drives are just like browser, no one ever supports everything properly... SID E N O TE
  • 70. /** * @Transform /^memory:(.*)$/ */ public function fromMemory($key) { if (!isset($this->memory[$key])) { throw new LogicException("Entry $key does not exist"); } return $this->memory[$key]; } Transformations FeatureContext.php PR AC TIC AL
  • 71. /** * @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; } Transformations FeatureContext.php PR AC TIC AL
  • 72. class FeatureContext … { public function takeAScreenshotCalled($filename) { $driver = get_class($this->getSession()->getDriver()); if ($driver == 'BehatMinkDriverSelenium2Driver') { $ss = $this->getSession()->getScreenshot(); file_put_contents($filename, $ss); } } } Screenshot FeatureContext.php PR AC TIC AL
  • 73. Advanced Usage with extra bells and whistles
  • 74. use BehatBehatHookScopeAfterFeatureScope; // @AfterFeature AfterScenarioScope; // @AfterScenario AfterStepScope; // @AfterStep BeforeFeatureScope; // @BeforeFeature BeforeScenarioScope; // @BeforeScenario BeforeStepScope; // @BeforeStep FeatureScope; // @Feature ScenarioScope; // @Scenario StepScope; // @Step Hooks Listen in close
  • 75. 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); } } Hooks FeatureContext.php PR AC TIC AL
  • 76. class FeatureContext … { /** * @AfterStep */ public function afterStep(AfterStepScope $scope) { $code = $event->getTestResult()->getResultCode(); if ($code == TestResult::FAILED) { // Take a screenshot } } } Hooks FeatureContext.php PR AC TIC AL
  • 77. class FeatureContext … { /** * @Given /^(?:I )wait for AJAX to finish$/ */ public function iWaitForAjaxToFinish() { $this->getSession()->wait(5000, "(0 === jQuery.active)"); } } AJAX The waiting game PR AC TIC AL
  • 78. 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 javascript There might be a valid use case... PR AC TIC AL
  • 79. default: suites: web: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, WebContext ] api: paths: [ %paths.base%/features/api ] contexts: [ BaseContext, ApiContext ] Configuration Multiple contexts
  • 80. 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 Configuration Grouping and filtering
  • 81. $ ./vendor/bin/behat --suite admin Suites Run 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
  • 82. $ ./vendor/bin/behat --suite speaker Suites Run a specific suite @speaker Feature: Submitting to the CFP In order to ensure that the conference has papers As an speaker I need to be able to submit papers
  • 83. $ 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 D EM O
  • 85. Complex Users Dealing with very complex user states
  • 86. Feature: Show relevant promotions to non-paying activated customers Scenario: Show promotion pricing to referred clients from the EU Given I create a user with: | email-activated | | is-EU-member | | is-referred-client | | NOT has-made-purchase | Then ... Setup What attributes does our user have?
  • 87. interface AttributeInterface { // Get the required attributes to have this attribute public function getDependencies(); // Does this user have this attribute? public function has(MinkContext $context); // Allocate this attribute to the user public function allocate(MinkContext $context); // Attempt to remove this attribute from the user public function remove(MinkContext $context); } Attributes AttributeInterface.php
  • 88. 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); } } Attributes IsEUMember.php
  • 89. // ... 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"); } Attributes IsEUMember.php
  • 90. // This class must handle dependency conflicts by examining the // dependencies of each with/without attribute class Request { protected $attributes = []; public function with(AttributeInterface $feature); public function without(AttributeInterface $feature); // List of attributes (including dependents) required public function getWith(); // List of attributes (including dependents) that must be removed public function getWithout(); } Request Request.php
  • 91. 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); } User User.php
  • 92. use BehatGherkinNodeTableNode; 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); } } TableNode Handling a table
  • 93. Some days everything is made of glass Common Gotchas
  • 94. Permutations The reason why testing is painful
  • 96. Speed vs Coverage Find the right balance
  • 97. Keep Selenium updated Browsers change faster than fashion trends
  • 98. Behat documents http://docs.behat.org points to v2.5 docs but doesn’t tell you. Use http://docs.behat.org/en/v3.0/
  • 99. Questions? or ask me later via @thomas_shone
  • 100. 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 "admin@guy.com" with a password "secrets" And User "admin@guy.com" has admin rights And I login as "admin@guy.com" with password "secrets" Scenario: Approve a submitted paper Scenario: Decline a submitted paper Final Task Apply what you know
  • 101. Hint Admin rights are granted by a CLI too
  • 102. @javascript Feature: 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 "admin@opencfp.org" with a password "secrets" And User "admin@opencfp.org" has admin rights And I login as "admin@opencfp.org" 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
  • 103. 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
  • 104. 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
  • 105. Thank you Photo from Flickr by John Morey, TrojanRat, Gerry Machen, USFS Region 5, Peregrina Tyss and Thomas Hawk