mirror of
https://github.com/silverstripe/silverstripe-behat-extension
synced 2024-10-22 17:05:32 +02:00
Merge branch '4'
This commit is contained in:
commit
2f8de05cd3
87
README.md
87
README.md
@ -1,6 +1,8 @@
|
||||
# SilverStripe Integration for Behat
|
||||
|
||||
[![Build Status](https://travis-ci.org/silverstripe-labs/silverstripe-behat-extension.svg?branch=master)](https://travis-ci.org/silverstripe-labs/silverstripe-behat-extension)
|
||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/silverstripe/silverstripe-behat-extension/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/silverstripe/silverstripe-behat-extension/?branch=master)
|
||||
[![SilverStripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
|
||||
|
||||
## Overview
|
||||
|
||||
@ -8,7 +10,7 @@
|
||||
Because it primarily interacts with your website through a browser,
|
||||
you don't need any specific integration tools to get it going with
|
||||
a basic SilverStripe website, simply follow the
|
||||
[standard Behat usage instructions](http://docs.behat.org/).
|
||||
[standard Behat usage instructions](http://docs.behat.org/en/latest/user_guide.html).
|
||||
|
||||
This extension comes in handy if you want to go beyond
|
||||
interacting with an existing website and database,
|
||||
@ -45,48 +47,28 @@ Skip this step if adding the module to an existing project.
|
||||
Switch to the newly created webroot, and add the SilverStripe Behat extension.
|
||||
|
||||
cd my-test-project
|
||||
composer require --dev silverstripe/behat-extension:"@stable"
|
||||
composer require --dev silverstripe/behat-extension
|
||||
|
||||
Now get the latest Selenium2 server (requires [Java](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)):
|
||||
Download the standalone [Google Chrome WebDriver](http://chromedriver.storage.googleapis.com/index.html?path=2.8/)
|
||||
|
||||
composer require --dev se/selenium-server-standalone:"2.x@stable"
|
||||
|
||||
Download [Firefox 31.0 ESR](https://ftp.mozilla.org/pub/firefox/releases/31.8.0esr/) (Extended Support Release).
|
||||
This version is older than your currently installed Firefox.
|
||||
It's important to have a browser that's [supported by Selenium-Webdriver](http://docs.seleniumhq.org/docs/01_introducing_selenium.jsp#selenium-webdriver). Even newer Firefox ESR versions are likely to break with the Selenium version we're running.
|
||||
|
||||
Now install the SilverStripe project as usual by opening it in a browser and following the instructions.
|
||||
Protip: You can skip this step by using `[SS_DATABASE_CHOOSE_NAME]` in a global
|
||||
[`_ss_environment.php`](http://doc.silverstripe.org/framework/en/topics/environment-management)
|
||||
file one level above the webroot.
|
||||
[`.env`](https://docs.silverstripe.org/en/getting_started/environment_management/) file one level above the webroot.
|
||||
|
||||
Unless you have [`$_FILE_TO_URL_MAPPING`](http://doc.silverstripe.org/framework/en/topics/commandline#configuration)
|
||||
set up, you also need to specify the URL for your webroot. Either add it to the existing `behat.yml` configuration file
|
||||
Unless you have [`SS_BASE_URL`](http://doc.silverstripe.org/framework/en/topics/commandline#configuration) set up,
|
||||
you also need to specify the URL for your webroot. Either add it to the existing `behat.yml` configuration file
|
||||
in your project root, or set is as an environment variable in your terminal session:
|
||||
|
||||
export BEHAT_PARAMS="extensions[SilverStripe\BehatExtension\MinkExtension][base_url]=http://localhost/"
|
||||
|
||||
## Usage
|
||||
|
||||
### Prevent Firefox from Automatically Updating
|
||||
|
||||
The moment you open Firefox, it's going to try and update itself out of the stone age. To prevent this, open a new tab and go to `about:config`. There, change the following settings to `false`:
|
||||
|
||||
- `app.update.auto`
|
||||
- `app.update.enabled`
|
||||
- `app.update.silent`
|
||||
|
||||
Firefox will already have started the update, so close and delete it. The settings you changed should be stored as preferences, apart from the application files you've just deleted. Reinstall that ancient version. The next time you open it, and go to "About Firefox", you should see a button desperately pleading with you to "check for updates". Don't click that if you know what's good for you...
|
||||
|
||||
### Starting the Selenium Server
|
||||
### Starting ChromeDriver
|
||||
|
||||
You can run the server locally in a separate Terminal session:
|
||||
|
||||
vendor/bin/selenium-server-standalone
|
||||
|
||||
In some cases it may be necessary to start a specific version of Firefox
|
||||
|
||||
vendor/bin/selenium-server-standalone -Dwebdriver.firefox.bin="/Applications/Firefox31.app/Contents/MacOS/firefox-bin"
|
||||
chromedriver
|
||||
|
||||
### Running the Tests
|
||||
|
||||
@ -98,9 +80,7 @@ Or even run a single scenario by it's name (supports regular expressions):
|
||||
|
||||
vendor/bin/behat --name 'My scenario title' @framework
|
||||
|
||||
This will start a Firefox browser by default. Other browsers and profiles can be configured in `behat.yml`.
|
||||
|
||||
For example, if you want to start a Chrome Browser you can following the instructions provided [here](docs/chrome-behat.md).
|
||||
This will start a Chrome browser by default. Other browsers and profiles can be configured in `behat.yml`.
|
||||
|
||||
### Running with stand-alone command
|
||||
|
||||
@ -127,10 +107,10 @@ Make sure you set `SS_BASE_URL` to `http://localhost:8080` in `.env`
|
||||
## Configuration
|
||||
|
||||
The SilverStripe installer already comes with a YML configuration
|
||||
which is ready to run tests on a locally hosted Selenium server,
|
||||
which is ready to run tests on the standalone ChromeDriver server,
|
||||
located in the project root as `behat.yml`.
|
||||
|
||||
You should ensure that you have configured SS_BASE_URL in your `.env`.
|
||||
You should ensure that you have configured `SS_BASE_URL` in your `.env` file.
|
||||
|
||||
Generic Mink configuration settings are placed in `SilverStripe\BehatExtension\MinkExtension`,
|
||||
which is a subclass of `Behat\MinkExtension\Extension`.
|
||||
@ -167,14 +147,15 @@ Example: behat.yml
|
||||
- %paths.modules.framework%/tests/behat/features/files/
|
||||
extensions:
|
||||
SilverStripe\BehatExtension\MinkExtension:
|
||||
default_session: selenium2
|
||||
javascript_session: selenium2
|
||||
selenium2:
|
||||
browser: firefox
|
||||
default_session: facebook_web_driver
|
||||
javascript_session: facebook_web_driver
|
||||
facebook_web_driver:
|
||||
browser: chrome
|
||||
wd_host: "http://127.0.0.1:9515" #chromedriver port
|
||||
SilverStripe\BehatExtension\Extension:
|
||||
screenshot_path: %paths.base%/artifacts/screenshots
|
||||
|
||||
## Module Initialization
|
||||
## Module Initialisation
|
||||
|
||||
You're all set to start writing features now! Simply create `*.feature` files
|
||||
anywhere in your codebase, and run them as shown above. We recommend the folder
|
||||
@ -259,7 +240,8 @@ use the inline definition syntax. The following example shows some syntax variat
|
||||
Then I should see "Page 1" in CMS Tree
|
||||
|
||||
* Fixtures are created where you defined them. If you want the fixtures to be created
|
||||
before every scenario, define them in [Background](http://docs.behat.org/guides/1.gherkin.html#backgrounds).
|
||||
before every scenario, define them in
|
||||
[Background](http://docs.behat.org/en/latest/user_guide/writing_scenarios.html#backgrounds).
|
||||
If you want them to be created only when a particular scenario runs, define them there.
|
||||
* Fixtures are cleared between scenarios.
|
||||
* The basic syntax works for all `DataObject` subclasses, but some specific
|
||||
@ -284,7 +266,7 @@ of your module. You can create it with the following commands:
|
||||
|
||||
### FeatureContext
|
||||
|
||||
The generic [Behat usage instructions](http://docs.behat.org/) apply
|
||||
The generic [Behat usage instructions](http://docs.behat.org/en/latest/user_guide.html) apply
|
||||
here as well. The only major difference is the base class from which
|
||||
to extend your own `FeatureContext`: It should be `SilverStripeContext`
|
||||
rather than `BehatContext`.
|
||||
@ -351,8 +333,6 @@ Sometimes SilverStripe needs to know the URL of your site. When you're visiting
|
||||
your site in a web browser this is easy to work out, but if you're executing
|
||||
scripts on the command-line, it has no way of knowing.
|
||||
|
||||
To work this out, this module is using [file to URL mapping](http://doc.silverstripe.org/framework/en/topics/commandline#configuration).
|
||||
|
||||
### How does the module interact with the SS database?
|
||||
|
||||
The module creates temporary database on init and is switching to the alternative
|
||||
@ -421,13 +401,8 @@ The `macgdbp` IDE key needs to match your `xdebug.idekey` php.ini setting.
|
||||
### How do I set up continuous integration through Travis?
|
||||
|
||||
Check out the [travis.yml](https://github.com/silverstripe/silverstripe-framework/blob/master/.travis.yml)
|
||||
in `silverstripe/framework` for a good example on how to set up Behat tests through [travis-ci.org](http://travis-ci.org).
|
||||
Note that the [Travis CI Environment](https://docs.travis-ci.com/user/ci-environment#Firefox)
|
||||
does not default to the latest [Firefox ESR](https://www.mozilla.org/en-US/firefox/organizations/all/) release, but an older version.
|
||||
(31.0 in January 2017). You should try to run your tests locally with the same Firefox version,
|
||||
and [download the correct Firefox release](https://ftp.mozilla.org/pub/firefox/releases/)).
|
||||
Alternatively, [configure Travis](https://docs.travis-ci.com/user/firefox/) to use a newer version.
|
||||
Don't forget to disable auto-updates in your Firefox settings.
|
||||
in `silverstripe/framework` for a good example on how to set up Behat tests through
|
||||
[travis-ci.org](http://travis-ci.org).
|
||||
|
||||
## Cheatsheet
|
||||
|
||||
@ -732,13 +707,19 @@ It's based on the `vendor/bin/behat -di @cms` output.
|
||||
|
||||
### Transformations
|
||||
|
||||
Behat [transformations](http://docs.behat.org/guides/2.definitions.html#step-argument-transformations)
|
||||
Behat [transformations](http://docs.behat.org/en/v2.5/guides/2.definitions.html#step-argument-transformations)
|
||||
have the ability to change step arguments based on their original value,
|
||||
for example to cast any argument matching the `\d` regex into an actual PHP integer.
|
||||
|
||||
* `/^(?:(the|a)) time of (?<val>.*)$/`: Transforms relative time statements compatible with [strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the time of 1 hour ago" might return "22:00:00" if its currently "23:00:00".
|
||||
* `/^(?:(the|a)) date of (?<val>.*)$/`: Transforms relative date statements compatible with [strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the date of 2 days ago" might return "2013-10-10" if its currently the 12th of October 2013.
|
||||
* `/^(?:(the|a)) datetime of (?<val>.*)$/`: Transforms relative date and time statements compatible with [strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the datetime of 2 days ago" might return "2013-10-10 23:00:00" if its currently the 12th of October 2013.
|
||||
* `/^(?:(the|a)) time of (?<val>.*)$/`: Transforms relative time statements compatible with
|
||||
[strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the time of 1 hour ago" might
|
||||
return "22:00:00" if its currently "23:00:00".
|
||||
* `/^(?:(the|a)) date of (?<val>.*)$/`: Transforms relative date statements compatible with
|
||||
[strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the date of 2 days ago" might
|
||||
return "2013-10-10" if its currently the 12th of October 2013.
|
||||
* `/^(?:(the|a)) datetime of (?<val>.*)$/`: Transforms relative date and time statements compatible with
|
||||
[strtotime()](http://www.php.net/manual/en/datetime.formats.relative.php). Example: "the datetime of 2 days ago" might
|
||||
return "2013-10-10 23:00:00" if its currently the 12th of October 2013.
|
||||
|
||||
## Useful resources
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
#Running Behat Tests using Chrome
|
||||
If you would like to run Behat Tests using Google Chrome here are a few steps I went through to get that setup.
|
||||
|
||||
1) [Download the Google Chrome Webdriver](http://chromedriver.storage.googleapis.com/index.html?path=2.8/)
|
||||
|
||||
2) Unzip the file, and place the chromedriver file in a known location.
|
||||
|
||||
3) Now edit the `behat.yml` file and create a new profile for using Chrome by adding the following below the default profile.
|
||||
|
||||
```
|
||||
default_session: selenium2
|
||||
javascript_session: selenium2
|
||||
selenium2:
|
||||
browser: chrome
|
||||
SilverStripe\BehatExtension\Extension:
|
||||
screenshot_path: %behat.paths.base%/_artifacts/screenshots
|
||||
```
|
||||
|
||||
4) Now we need to use the new webdriver with Selenium.
|
||||
|
||||
```
|
||||
java -jar selenium-server.jar -Dwebdriver.chrome.driver="/path/to/chromedriver"
|
||||
```
|
||||
|
||||
5) Now run your behat steps with the chrome profile.
|
||||
|
||||
```
|
||||
behat @mysite --profile=chrome
|
||||
```
|
@ -21,7 +21,7 @@ First of all, check out a default SilverStripe project
|
||||
and ensure it runs on your environment. Detailed installation instructions
|
||||
can be found in the [README](../README.md) of this module.
|
||||
Once you've got the SilverStripe project running, make sure you've
|
||||
started Selenium. With all configuration in place, initialize Behat
|
||||
started ChromeDriver. With all configuration in place, initialise Behat
|
||||
for
|
||||
|
||||
vendor/bin/behat --init @mysite
|
||||
|
@ -277,9 +277,11 @@ JS;
|
||||
*/
|
||||
public function closeModalDialog(AfterScenarioScope $event)
|
||||
{
|
||||
$expectsUnsavedChangesModal = $this->stepHasTag($event, 'unsavedChanges');
|
||||
|
||||
try {
|
||||
// Only for failed tests on CMS page
|
||||
if ($event->getTestResult()->getResultCode() === TestResult::FAILED) {
|
||||
if ($expectsUnsavedChangesModal || $event->getTestResult()->getResultCode() === TestResult::FAILED) {
|
||||
$cmsElement = $this->getSession()->getPage()->find('css', '.cms');
|
||||
if ($cmsElement) {
|
||||
try {
|
||||
|
@ -12,19 +12,23 @@ use InvalidArgumentException;
|
||||
use SilverStripe\Assets\Folder;
|
||||
use SilverStripe\Assets\Storage\AssetStore;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Core\Extension;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\Dev\BehatFixtureFactory;
|
||||
use SilverStripe\Dev\FixtureBlueprint;
|
||||
use SilverStripe\Dev\FixtureFactory;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Dev\YamlFixture;
|
||||
use SilverStripe\ORM\Connect\TempDatabase;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\Security\Group;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
* Context used to create fixtures in the SilverStripe ORM.
|
||||
@ -55,10 +59,15 @@ class FixtureContext implements Context
|
||||
protected $tempDatabase;
|
||||
|
||||
/**
|
||||
* @var String Tracks all files and folders created from fixtures, for later cleanup.
|
||||
* @var string[] Tracks all files and folders created from fixtures, for later cleanup.
|
||||
*/
|
||||
protected $createdFilesPaths = array();
|
||||
|
||||
/**
|
||||
* @var string[] Tracks any config files that have been activated as part of a scenario
|
||||
*/
|
||||
protected $activatedConfigFiles = array();
|
||||
|
||||
/**
|
||||
* @var array Stores the asset tuples.
|
||||
*/
|
||||
@ -632,6 +641,66 @@ class FixtureContext implements Context
|
||||
$this->getMainContext()->getSession()->visit($this->getMainContext()->locatePath($link));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $extension
|
||||
* @param $class
|
||||
*
|
||||
* @Given I add an extension :extension to the :class class
|
||||
*/
|
||||
public function iAddAnExtensionToTheClass($extension, $class)
|
||||
{
|
||||
// Validate the extension
|
||||
assertTrue(
|
||||
class_exists($extension) && is_subclass_of($extension, Extension::class),
|
||||
'Given extension does not extend Extension'
|
||||
);
|
||||
|
||||
// Add the extension to the CLI context
|
||||
/** @var Extensible $targetClass */
|
||||
$targetClass = $this->convertTypeToClass($class);
|
||||
$targetClass::add_extension($extension);
|
||||
|
||||
// Write config for this extension too...
|
||||
$snakedExtension = strtolower(str_replace('\\', '-', $extension));
|
||||
$config = <<<YAML
|
||||
---
|
||||
Name: testonly-enable-extension-$snakedExtension
|
||||
---
|
||||
$class:
|
||||
extensions:
|
||||
- $extension
|
||||
YAML;
|
||||
|
||||
$filename = 'enable-' . $snakedExtension . '.yml';
|
||||
$destPath = $this->getDestinationConfigFolder($filename);
|
||||
file_put_contents($destPath, $config);
|
||||
|
||||
// Remember to cleanup...
|
||||
$this->activatedConfigFiles[] = $destPath;
|
||||
|
||||
// Flush website. We'll need to dev/build too if it's a DataExtension
|
||||
if (is_subclass_of($extension, DataExtension::class)) {
|
||||
$this->getMainContext()->visit('/dev/build?flush');
|
||||
} else {
|
||||
$this->getMainContext()->visit('/?flush');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination folder for config and assert the given file name doesn't exist within in.
|
||||
*
|
||||
* @param $filename
|
||||
* @return string
|
||||
*/
|
||||
protected function getDestinationConfigFolder($filename)
|
||||
{
|
||||
$project = ModuleManifest::config()->get('project') ?: 'mysite';
|
||||
$mysite = ModuleLoader::getModule($project);
|
||||
assertNotNull($mysite, 'Project exists');
|
||||
$destPath = $mysite->getResource("_config/{$filename}")->getPath();
|
||||
assertFileNotExists($destPath, "Config file {$filename} hasn't aleady been loaded");
|
||||
return $destPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a file or folder exists in the webroot.
|
||||
@ -714,6 +783,19 @@ class FixtureContext implements Context
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all config files after scenario
|
||||
*
|
||||
* @AfterScenario
|
||||
* @param AfterScenarioScope $event
|
||||
*/
|
||||
public function afterResetConfig(AfterScenarioScope $event)
|
||||
{
|
||||
$this->clearConfigFiles();
|
||||
// Flush
|
||||
$this->getMainContext()->visit('/?flush');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a fixture for use
|
||||
*
|
||||
@ -867,4 +949,27 @@ class FixtureContext implements Context
|
||||
}
|
||||
return join('/', $paths);
|
||||
}
|
||||
|
||||
protected function clearConfigFiles()
|
||||
{
|
||||
// No files to cleanup
|
||||
if (empty($this->activatedConfigFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->activatedConfigFiles as $configFile) {
|
||||
if (file_exists($configFile)) {
|
||||
unlink($configFile);
|
||||
}
|
||||
}
|
||||
$this->activatedConfigFiles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch situations where failed scenarios and early exiting would prevent cleanup.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->clearConfigFiles();
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,14 @@
|
||||
|
||||
namespace SilverStripe\BehatExtension\Utility;
|
||||
|
||||
use Behat\Behat\Hook\Scope\ScenarioScope;
|
||||
use Behat\Behat\Hook\Scope\StepScope;
|
||||
use Behat\Gherkin\Node\FeatureNode;
|
||||
use Behat\Gherkin\Node\NodeInterface;
|
||||
use Behat\Gherkin\Node\ScenarioInterface;
|
||||
use Behat\Gherkin\Node\TaggedNodeInterface;
|
||||
use \Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Helpers for working with steps
|
||||
@ -65,22 +68,35 @@ trait StepHelper
|
||||
/**
|
||||
* Check if a step has a given tag
|
||||
*
|
||||
* @param StepScope $event
|
||||
* @param StepScope|ScenarioScope $event
|
||||
* @param string $tag
|
||||
* @return bool
|
||||
*/
|
||||
protected function stepHasTag(StepScope $event, $tag)
|
||||
protected function stepHasTag($event, $tag)
|
||||
{
|
||||
// Check feature
|
||||
$feature = $event->getFeature();
|
||||
if ($feature && $feature->hasTag($tag)) {
|
||||
return true;
|
||||
$checks = [];
|
||||
if ($event instanceof StepScope) {
|
||||
$checks[] = $feature = $event->getFeature();
|
||||
$checks[] = $this->getStepScenario($feature, $event->getStep());
|
||||
} elseif ($event instanceof ScenarioScope) {
|
||||
$checks[] = $event->getFeature();
|
||||
$checks[] = $event->getScenario();
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'%s expected an instance of either %s or %s. Got %s instead',
|
||||
__METHOD__,
|
||||
StepScope::class,
|
||||
ScenarioScope::class,
|
||||
is_object($event) ? sprintf('an instance of %s', get_class($event)) : gettype($event)
|
||||
));
|
||||
}
|
||||
// Check scenario
|
||||
$scenario = $this->getStepScenario($feature, $event->getStep());
|
||||
if ($scenario && $scenario->hasTag($tag)) {
|
||||
return true;
|
||||
|
||||
foreach ($checks as $check) {
|
||||
if ($check instanceof TaggedNodeInterface && $check->hasTag($tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user