An automation framework built around Behat for testing Drupal sites
Brendan MacDonald
https://github.com/cameronandwilding/CWTest_Behat
{
"name": "cw/behat_test",
"authors": [
{
"name": "Cameron and Wilding",
"email": "info@cameronandwilding.com"
}
],
"require": {
"behat/behat": "3.0.6",
"drupal/drupal-extension": "3.1.5",
"emuse/behat-html-formatter": "0.1.0",
"phpunit/phpunit": "*"
},
"autoload": {
"psr-4": {
"CWTest\\": "src/"
}
},
"bin": [
"bin/start_phantomjs_webdriver.sh",
"bin/start_selenium_server.sh",
"bin/stop_phantomjs_webdriver.sh",
"bin/stop_selenium_server.sh",
"bin/cwtest-bootstrap.sh"
]
}
Feature Context
Feature file with a 'Create Article' Scenario
@article
Scenario: Create an Article
Given I am on '/node/add/article'
And I fill in 'edit-title-0-value' with 'Article Title'
When I press 'edit-submit'
Then I should see 'Article Article Title has been created.' in the '.messages--status'
/**
* Fills in form field with specified id|name|label|value
*/
public function fillField($field, $value)
{
$field = $this->fixStepArgument($field);
$value = $this->fixStepArgument($value);
$this->getSession()->getPage()->fillField($field, $value);
}
'Create Article' scenario
@article
Scenario: Create an Article
Given I am on '/node/add/article'
And I fill in 'edit-title-0-value' with 'Article Title'
When I press 'edit-submit'
Then I should see 'Article Article Title has been created.' in the '.messages--status'
@article
Scenario: Create an Article
Given I am logged in as a user with the administrator role
And I visit the Create Article page
When I enter the following values on the Create Article page
| FIELD | VALUE |
| TITLE | Article Title <alpha_number> |
| BODY | This is the body text of the Article. |
| IMAGE | 150x350.jpg |
| ALT | ALT - 150x350.jpg |
And I press save and publish
Then I verify that the article was created successfully
'Create Article' scenario
@article
Scenario: Create an Article
Given I am logged in as a user with the administrator role
And I visit the Create Article page
...
<?php
use CWTest\Util\ArticlePage;
class ArticleContext extends PageContext {
...
<?php
class ArticlePage extends Page {
...
Article Context
Article Page
'Create Article' Scenario
class ArticlePage {
/**
* The path to the Article Content Type.
*
* @var string
*/
private $path = '/node/add/article';
/**
* Fields visble in Create and Edit mode.
*
* @var array
*/
private $fields = array(
'TITLE' => 'edit-title-0-value',
'IMAGE' => 'edit-field-image-0-upload',
);
/**
* Frames available in Create and Edit mode.
*
* @var array
*/
private $frames = array(
'BODY' => 'cke_edit-body-0-value'
);
/**
* Buttons visible in Create mode.
*
* @var array
*/
private $create_buttons = array(
'SAVE_AND_PUBLISH' => 'Save and publish',
'SAVE_AS_UNPUBLISHED' => 'Save as unpublished',
'PREVIEW' => 'Preview',
);
/**
* Buttons visible in Edit mode.
*
* @var array
*/
private $edit_buttons = array(
'REMOVE' => 'Remove',
'SAVE_AND_KEEP_PUBLISHED' => 'Save and keep published',
'SAVE_AND_UNPUBLISH' => 'Save and unpublish',
'PREVIEW' => 'Preview',
);
class ArticleContext extends PageContext {
/**
* ArticlePage instance.
*
* @var ArticlePage
*/
private $articlePage;
/**
* ArticleContext constructor.
*/
public function __construct() {
parent::__construct();
$this->articlePage = new ArticlePage();
}
/**
* @Given I visit the Create Article page
*/
public function visitCreateArticlePage() {
$this->helperContext->visitPath($this->articlePage->getPath());
}
/**
* @Given I visit the Edit Article page
*/
public function visitEditArticlePage() {
$this->helperContext->visitPath(self::getEditPath());
}
/**
* Fills in the title field.
*
* @param string $title
*/
private function fillTitleField($title) {
$this->helperContext->iFillInFieldByIDWith(
$this->articlePage->getField(self::FIELD_TITLE), $title);
}
/**
* @Given I enter the following values on the Create Article page
*/
public function iEnterTheFollowingValuesOnTheCreateArticlePage(TableNode $table) {
foreach ($table->getHash() as $key => $value) {
$field = trim($value['FIELD']);
$value = trim($value['VALUE']);
switch ($field) {
case self::FIELD_TITLE:
self::fillTitleField($value);
break;
...
Feature files for content type tests:
Page object class:
Context class:
<?php
/**
* @file
*/
namespace CWTest\Context;
use Drupal\DrupalExtension\Context\RawDrupalContext;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Behat\Hook\Scope\AfterStepScope;
use Drupal\DrupalExtension\Context\MinkContext;
use Behat\Gherkin\Node\TableNode;
use Behat\Behat\Context\SnippetAcceptingContext;
use CWTest\Exception\CWContextException;
use CWTest\Util\RandomItems;
/**
* Class HelperContext
*
* HelperContext contains supporting functions for all Behat projects.
*/
class HelperContext extends RawDrupalContext implements SnippetAcceptingContext {
...
./run-behat.sh [tag] [profile]
./run-behat.sh article phantomjs
Script runner syntax
To run all your article tests on firefox:
To run all your article tests using phantomsjs:
./run-behat.sh article firefox
To run a specific test within the article tests on firefox:
./run-behat.sh article firefox "Verify the structure of the Create Article page"
Installation script:
Tests can be run using selenium or phantomjs.
HTML reports:
Results folder contains:
Configuration split between 2 YML files:
behat.yml
default:
suites:
login:
paths: [ %paths.base%/features ]
filters:
tags: @login
contexts:
- CWTest\Context\LoginContext
- CWTest\Context\MyAccountContext
- CWTest\Context\PageContext
- CWTest\Context\HelperContext:
parameters:
screenshot_path: %paths.base%/../Results/Behat/screenshots
- Drupal\DrupalExtension\Context\MinkContext
- Drupal\DrupalExtension\Context\DrupalContext
...
Configuration split between 2 YML files:
behat.local.yml
default:
extensions:
Behat\MinkExtension:
base_url:
Drupal\DrupalExtension:
drupal:
drupal_root:
Multiple versions/attempts at the framework.
Behat knowledge is a pre-requisite.
Composer set-up was not ideal.
Reporting was inadequate.
Future ideas!
Try it out and let us know what you think.
https://github.com/cameronandwilding/CWTest_Behat