NEW Support for Behat tests, and initial set of tests

This commit is contained in:
Ingo Schommer 2012-11-09 19:16:16 +01:00 committed by Sam Minnee
parent 5edf86fe7a
commit 32f829d094
14 changed files with 567 additions and 0 deletions

View File

@ -18,5 +18,8 @@
"require": { "require": {
"php": ">=5.3.2", "php": ">=5.3.2",
"composer/installers": "*" "composer/installers": "*"
},
"autoload": {
"classmap": ["tests/behat/features/bootstrap"]
} }
} }

View File

@ -63,3 +63,6 @@ We can use string processing on the body of the response to then see if it fits
If you're testing for natural language responses like error messages, make sure to use [i18n](/topics/i18n) translations through If you're testing for natural language responses like error messages, make sure to use [i18n](/topics/i18n) translations through
the *_t()* method to avoid tests failing when i18n is enabled. the *_t()* method to avoid tests failing when i18n is enabled.
Note that for a more highlevel testing approach, SilverStripe also supports
[behaviour-driven testing through Behat](https://github.com/silverstripe-labs/silverstripe-behat-extension). It interacts directly with your website or CMS interface by remote controlling an actual browser, driven by natural language assertions.

View File

@ -146,6 +146,9 @@ For example, you could have a `phpunit-unit-tests.xml` and `phpunit-functional-t
**Assertion:** A predicate statement that must be true when a test runs. **Assertion:** A predicate statement that must be true when a test runs.
**Behat:** A behaviour-driven testing library used with SilverStripe as a higher-level
alternative to the `FunctionalTest` API, see [http://behat.org](http://behat.org).
**Test Case:** The atomic class type in most unit test frameworks. New unit tests are created by inheriting from the **Test Case:** The atomic class type in most unit test frameworks. New unit tests are created by inheriting from the
base test case. base test case.

1
tests/behat/README.md Normal file
View File

@ -0,0 +1 @@
See https://github.com/silverstripe-labs/silverstripe-behat-extension

View File

View File

@ -0,0 +1,38 @@
<?php
namespace SilverStripe\Framework\Test\Behaviour;
use SilverStripe\BehatExtension\Context\SilverStripeContext,
SilverStripe\BehatExtension\Context\BasicContext,
SilverStripe\BehatExtension\Context\LoginContext,
SilverStripe\Framework\Test\Behaviour\CmsFormsContext,
SilverStripe\Framework\Test\Behaviour\CmsUiContext;
// PHPUnit
require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';
/**
* Features context
*
* Context automatically loaded by Behat.
* Uses subcontexts to extend functionality.
*/
class FeatureContext extends SilverStripeContext
{
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
$this->useContext('BasicContext', new BasicContext($parameters));
$this->useContext('LoginContext', new LoginContext($parameters));
$this->useContext('CmsFormsContext', new CmsFormsContext($parameters));
$this->useContext('CmsUiContext', new CmsUiContext($parameters));
parent::__construct($parameters);
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace SilverStripe\Framework\Test\Behaviour;
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Context\Step,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
// PHPUnit
require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';
/**
* CmsFormsContext
*
* Context used to define steps related to forms inside CMS.
*/
class CmsFormsContext extends BehatContext
{
protected $context;
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
// Initialize your context here
$this->context = $parameters;
}
/**
* Get Mink session from MinkContext
*/
public function getSession($name = null)
{
return $this->getMainContext()->getSession($name);
}
/**
* @Then /^I should see an edit page form$/
*/
public function stepIShouldSeeAnEditPageForm()
{
$page = $this->getSession()->getPage();
$form = $page->find('css', '#Form_EditForm');
assertNotNull($form, 'I should see an edit page form');
}
/**
* @When /^I fill in the content form with "([^"]*)"$/
*/
public function stepIFillInTheContentFormWith($content)
{
$this->getSession()->evaluateScript("tinyMCE.get('Form_EditForm_Content').setContent('$content')");
}
/**
* @Then /^the content form should contain "([^"]*)"$/
*/
public function theContentFormShouldContain($content)
{
$this->getMainContext()->assertElementContains('#Form_EditForm_Content', $content);
}
}

View File

@ -0,0 +1,256 @@
<?php
namespace SilverStripe\Framework\Test\Behaviour;
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Context\Step,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
// PHPUnit
require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';
/**
* CmsUiContext
*
* Context used to define steps related to SilverStripe CMS UI like Tree or Panel.
*/
class CmsUiContext extends BehatContext
{
protected $context;
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters)
{
// Initialize your context here
$this->context = $parameters;
}
/**
* Get Mink session from MinkContext
*/
public function getSession($name = null)
{
return $this->getMainContext()->getSession($name);
}
/**
* @Then /^I should see the CMS$/
*/
public function iShouldSeeTheCms()
{
$page = $this->getSession()->getPage();
$cms_element = $page->find('css', '.cms');
assertNotNull($cms_element, 'CMS not found');
}
/**
* @Then /^I should see a "([^"]*)" notice$/
*/
public function iShouldSeeANotice($notice)
{
$this->getMainContext()->assertElementContains('.notice-wrap', $notice);
}
/**
* @Then /^I should see a "([^"]*)" message$/
*/
public function iShouldSeeAMessage($message)
{
$this->getMainContext()->assertElementContains('.message', $message);
}
protected function getCmsTabsElement()
{
$this->getSession()->wait(5000, "window.jQuery('.cms-content-header-tabs').size() > 0");
$page = $this->getSession()->getPage();
$cms_content_header_tabs = $page->find('css', '.cms-content-header-tabs');
assertNotNull($cms_content_header_tabs, 'CMS tabs not found');
return $cms_content_header_tabs;
}
protected function getCmsContentToolbarElement()
{
$this->getSession()->wait(
5000,
"window.jQuery('.cms-content-toolbar').size() > 0 "
. "&& window.jQuery('.cms-content-toolbar').children().size() > 0"
);
$page = $this->getSession()->getPage();
$cms_content_toolbar_element = $page->find('css', '.cms-content-toolbar');
assertNotNull($cms_content_toolbar_element, 'CMS content toolbar not found');
return $cms_content_toolbar_element;
}
protected function getCmsTreeElement()
{
$this->getSession()->wait(5000, "window.jQuery('.cms-tree').size() > 0");
$page = $this->getSession()->getPage();
$cms_tree_element = $page->find('css', '.cms-tree');
assertNotNull($cms_tree_element, 'CMS tree not found');
return $cms_tree_element;
}
protected function getGridfieldTable($title)
{
$page = $this->getSession()->getPage();
$table_elements = $page->findAll('css', '.ss-gridfield-table');
assertNotNull($table_elements, 'Table elements not found');
$table_element = null;
foreach ($table_elements as $table) {
$table_title_element = $table->find('css', '.title');
if ($table_title_element->getText() === $title) {
$table_element = $table;
break;
}
}
assertNotNull($table_element, sprintf('Table `%s` not found', $title));
return $table_element;
}
/**
* @Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/
*/
public function iShouldSeeAButtonInCmsContentToolbar($text)
{
$cms_content_toolbar_element = $this->getCmsContentToolbarElement();
$element = $cms_content_toolbar_element->find('named', array('link_or_button', "'$text'"));
assertNotNull($element, sprintf('%s button not found', $text));
}
/**
* @When /^I should see "([^"]*)" in CMS Tree$/
*/
public function stepIShouldSeeInCmsTree($text)
{
$cms_tree_element = $this->getCmsTreeElement();
$element = $cms_tree_element->find('named', array('content', "'$text'"));
assertNotNull($element, sprintf('%s not found', $text));
}
/**
* @When /^I should not see "([^"]*)" in CMS Tree$/
*/
public function stepIShouldNotSeeInCmsTree($text)
{
$cms_tree_element = $this->getCmsTreeElement();
$element = $cms_tree_element->find('named', array('content', "'$text'"));
assertNull($element, sprintf('%s found', $text));
}
/**
* @When /^I expand the "([^"]*)" CMS Panel$/
*/
public function iExpandTheCmsPanel()
{
// TODO Make dynamic, currently hardcoded to first panel
$page = $this->getSession()->getPage();
$panel_toggle_element = $page->find('css', '.cms-content > .cms-panel > .cms-panel-toggle > .toggle-expand');
assertNotNull($panel_toggle_element, 'Panel toggle not found');
if ($panel_toggle_element->isVisible()) {
$panel_toggle_element->click();
}
}
/**
* @When /^I click the "([^"]*)" CMS tab$/
*/
public function iClickTheCmsTab($tab)
{
$cms_tabs_element = $this->getCmsTabsElement();
$tab_element = $cms_tabs_element->find('named', array('link_or_button', "'$tab'"));
assertNotNull($tab_element, sprintf('%s tab not found', $tab));
$tab_element->click();
}
/**
* @Then /^the "([^"]*)" table should contain "([^"]*)"$/
*/
public function theTableShouldContain($table, $text)
{
$table_element = $this->getGridfieldTable($table);
var_dump($table_element);
$element = $table_element->find('named', array('content', "'$text'"));
assertNotNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table));
}
/**
* @Then /^the "([^"]*)" table should not contain "([^"]*)"$/
*/
public function theTableShouldNotContain($table, $text)
{
$table_element = $this->getGridfieldTable($table);
$element = $table_element->find('named', array('content', "'$text'"));
assertNull($element, sprintf('Element containing `%s` not found in `%s` table', $text, $table));
}
/**
* @Given /^I click on "([^"]*)" in the "([^"]*)" table$/
*/
public function iClickOnInTheTable($text, $table)
{
$table_element = $this->getGridfieldTable($table);
$element = $table_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
assertNotNull($element, sprintf('Element containing `%s` not found', $text));
$element->click();
}
/**
* @Then /^I can see the preview panel$/
*/
public function iCanSeeThePreviewPanel()
{
$this->getMainContext()->assertElementOnPage('.cms-preview');
}
/**
* @Given /^the preview contains "([^"]*)"$/
*/
public function thePreviewContains($content)
{
$driver = $this->getSession()->getDriver();
$driver->switchToIFrame('cms-preview-iframe');
$this->getMainContext()->assertPageContainsText($content);
$driver->switchToWindow();
}
/**
* @Given /^the preview does not contain "([^"]*)"$/
*/
public function thePreviewDoesNotContain($content)
{
$driver = $this->getSession()->getDriver();
$driver->switchToIFrame('cms-preview-iframe');
$this->getMainContext()->assertPageNotContainsText($content);
$driver->switchToWindow();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,20 @@
# features/login.feature
Feature: Log in
As an site owner
I want to access to the CMS to be secure
So that only my team can make content changes
Scenario: Bad login
Given I log in with "bad@example.com" and "badpassword"
Then I will see a bad log-in message
Scenario: Valid login
Given I am logged in with "ADMIN" permissions
When I go to "/admin/"
Then I should see the CMS
Scenario: /admin/ redirect for not logged in user
# disable automatic redirection so we can use the profiler
When I go to "/admin/" without redirection
Then I should be redirected to "/Security/login"
And I should see a log-in form

View File

@ -0,0 +1,84 @@
@javascript @assets
Feature: Manage files
As a cms author
I want to upload and manage files within the CMS
So that I can insert them into my content efficiently
Background:
# Idea: We could weave the database reset into this through
# saying 'Given there are ONLY the following...'.
Given there are the following Folder records
"""
folder1:
Filename: assets/folder1
folder1.1:
Filename: assets/folder1/folder1.1
Parent: =>Folder.folder1
folder2:
Filename: assets/folder2
Name: folder2
"""
And there are the following File records
"""
file1:
Filename: assets/folder1/file1.jpg
Name: file1.jpg
Parent: =>Folder.folder1
file2:
Filename: assets/folder1/folder1.1/file2.jpg
Name: file2.jpg
Parent: =>Folder.folder1.1
"""
And I am logged in with "ADMIN" permissions
# Alternative fixture shortcuts, with their titles
# as shown in admin/security rather than technical permission codes.
# Just an idea for now, could be handled by YAML fixtures as well
# And I am logged in with the following permissions
# - Access to 'Pages' section
# - Access to 'Files' section
And I go to "/admin/assets"
@modal
Scenario: I can add a new folder
Given I press the "Add folder" button
And I type "newfolder" into the dialog
And I confirm the dialog
Then the "Files" table should contain "newfolder"
Scenario: I can list files in a folder
Given I click on "folder1" in the "Files" table
Then the "folder1" table should contain "file1"
And the "folder1" table should not contain "file1.1"
Scenario: I can upload a file to a folder
Given I click on "folder1" in the "Files" table
And I press the "Upload" button
And I attach the file "testfile.jpg" to "AssetUploadField" with HTML5
And I wait for 5 seconds
And I press the "Back to folder" button
Then the "folder1" table should contain "testfile"
Scenario: I can edit a file
Given I click on "folder1" in the "Files" table
And I click on "file1" in the "folder1" table
And I fill in "renamedfile" for "Title"
And I press the "Save" button
And I press the "Back" button
Then the "folder1" table should not contain "testfile"
And the "folder1" table should contain "renamedfile"
Scenario: I can delete a file
Given I click on "folder1" in the "Files" table
And I click on "file1" in the "folder1" table
And I press the "Delete" button
Then the "folder1" table should not contain "file1"
Scenario: I can change the folder of a file
Given I click on "folder1" in the "Files" table
And I click on "file1" in the "folder1" table
And I fill in =>Folder.folder2 for "ParentID"
And I press the "Save" button
# /show/0 is to ensure that we are on top level folder
And I go to "/admin/assets/show/0"
And I click on "folder2" in the "Files" table
And the "folder2" table should contain "file1"

View File

@ -0,0 +1,87 @@
@database-defaults
Feature: Manage users
As a site administrator
I want to create and manage user accounts on my site
So that I can control access to the CMS
Background:
Given there are the following Permission records
"""
admin:
Code: ADMIN
security-admin:
Code: CMS_ACCESS_SecurityAdmin
"""
And there are the following Group records
"""
admingroup:
Title: Admin Group
Code: admin
Permissions: =>Permission.admin
staffgroup:
Title: Staff Group
Code: staffgroup
"""
And there are the following Member records
"""
admin:
FirstName: Admin
Email: admin@test.com
Groups: =>Group.admingroup
staffmember:
FirstName: Staff
Email: staffmember@test.com
Groups: =>Group.staffgroup
"""
And I am logged in with "ADMIN" permissions
And I go to "/admin/security"
@javascript
Scenario: I can list all users regardless of group
When I click the "Users" CMS tab
Then I should see "admin@test.com" in the "#Root_Users" element
And I should see "staffmember@test.com" in the "#Root_Users" element
@javascript
Scenario: I can list all users in a specific group
When I click the "Groups" CMS tab
# TODO Please check how performant this is
And I click "Admin Group" in the "#Root_Groups" element
Then I should see "admin@test.com" in the "#Root_Members" element
And I should not see "staffmember@test.com" in the "#Root_Members" element
@javascript
Scenario: I can add a user to the system
When I click the "Users" CMS tab
And I press the "Add Member" button
And I fill in the following:
| First Name | John |
| Surname | Doe |
| Email | john.doe@test.com |
And I press the "Create" button
Then I should see a "Saved member" message
When I go to "admin/security/"
Then I should see "john.doe@test.com" in the "#Root_Users" element
@javascript
Scenario: I can edit an existing user and add him to an existing group
When I click the "Users" CMS tab
And I click "staffmember@test.com" in the "#Root_Users" element
And I select "Admin Group" from "Groups"
And I additionally select "Administrators" from "Groups"
And I press the "Save" button
Then I should see a "Saved Member" message
When I go to "admin/security"
And I click the "Groups" CMS tab
And I click "Admin Group" in the "#Root_Groups" element
Then I should see "staffmember@test.com"
@javascript
Scenario: I can delete an existing user
When I click the "Users" CMS tab
And I click "staffmember@test.com" in the "#Root_Users" element
And I press the "Delete" button
Then I should see "admin@test.com"
And I should not see "staffmember@test.com"