Add actors for test scenarios

An actor plays the role of an end-user in the test scenario. As such,
each actor has its own web browser session used to perform the actions
specified by the steps of the scenario. Only one actor is active at a
time in a test scenario, and the current actor can be set through the "I
act as XXX" step; from then on, all the steps are performed by that
actor, until a different actor is set by calling "I act as XXX" again.
If no actor was explicitly set in a scenario then the default actor,
unsurprisingly named "default", is the one used.

The ActorContext class is added to provide automatic support for all
that. To use the ActorContext, besides adding it to the context list in
"behat.yml", a Mink session for each actor used in the features must be
specified in "behat.yml". Once done other Contexts just need to
implement the ActorAwareInterface (which can be done simply by using the
ActorAware trait) to have access to the current Actor object of the test
scenario; as the Actor object provides its own session other Contexts do
not need to extend from RawMinkContext. The ActorContext is itself a
RawMinkContext, so it automatically receives the base URL of the
Nextcloud test server run by NextcloudTestServerContext and propagates
that base URL to all the actors.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2017-04-04 16:18:05 +02:00
parent 4c620f1fcb
commit 7c07f01d59
6 changed files with 292 additions and 1 deletions

View File

@ -6,6 +6,7 @@ default:
paths:
- %paths.base%/../features
contexts:
- ActorContext
- NextcloudTestServerContext
- FeatureContext

View File

@ -23,6 +23,8 @@
use Behat\Behat\Context\Context;
class FeatureContext implements Context {
class FeatureContext implements Context, ActorAwareInterface {
use ActorAware;
}

View File

@ -0,0 +1,92 @@
<?php
/**
*
* @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* An actor in a test scenario.
*
* Every Actor object is intended to be used only in a single test scenario.
* An Actor can control its web browser thanks to the Mink Session received when
* it was created, so in each scenario each Actor must have its own Mink
* Session; the same Mink Session can be used by different Actors in different
* scenarios, but never by different Actors in the same scenario.
*
* The test servers used in an scenario can change between different test runs,
* so an Actor stores the base URL for the current test server being used; in
* most cases the tests are specified using relative paths that can be converted
* to the appropriate absolute URL using locatePath() in the step
* implementation.
*/
class Actor {
/**
* @var \Behat\Mink\Session
*/
private $session;
/**
* @var string
*/
private $baseUrl;
/**
* Creates a new Actor.
*
* @param \Behat\Mink\Session $session the Mink Session used to control its
* web browser.
* @param string $baseUrl the base URL used when solving relative URLs.
*/
public function __construct(\Behat\Mink\Session $session, $baseUrl) {
$this->session = $session;
$this->baseUrl = $baseUrl;
}
/**
* Sets the base URL.
*
* @param string $baseUrl the base URL used when solving relative URLs.
*/
public function setBaseUrl($baseUrl) {
$this->baseUrl = $baseUrl;
}
/**
* Returns the Mink Session used to control its web browser.
*
* @return \Behat\Mink\Session the Mink Session used to control its web
* browser.
*/
public function getSession() {
return $this->session;
}
/**
* Returns the full path for the given relative path based on the base URL.
*
* @param string relativePath the relative path.
* @return string the full path.
*/
public function locatePath($relativePath) {
return $this->baseUrl . $relativePath;
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
*
* @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
trait ActorAware {
/**
* @var Actor
*/
private $actor;
/**
* @param Actor $actor
*/
public function setCurrentActor(Actor $actor) {
$this->actor = $actor;
}
}

View File

@ -0,0 +1,31 @@
<?php
/**
*
* @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
interface ActorAwareInterface {
/**
* @param Actor $actor
*/
public function setCurrentActor(Actor $actor);
}

View File

@ -0,0 +1,127 @@
<?php
/**
*
* @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
use Behat\Behat\Hook\Scope\BeforeStepScope;
use Behat\MinkExtension\Context\RawMinkContext;
/**
* Behat context to set the actor used in sibling contexts.
*
* This helper context provides a step definition ("I act as XXX") to change the
* current actor of the scenario, which makes possible to use different browser
* sessions in the same scenario.
*
* Sibling contexts that want to have access to the current actor of the
* scenario must implement the ActorAwareInterface; this can be done just by
* using the ActorAware trait.
*
* Besides updating the current actor in sibling contexts the ActorContext also
* propagates its inherited "base_url" Mink parameter to the Actors as needed.
*
* Every actor used in the scenarios must have a corresponding Mink session
* declared in "behat.yml" with the same name as the actor. All used sessions
* are stopped after each scenario is run.
*/
class ActorContext extends RawMinkContext {
/**
* @var array
*/
private $actors;
/**
* @var Actor
*/
private $currentActor;
/**
* Sets a Mink parameter.
*
* When the "base_url" parameter is set its value is propagated to all the
* Actors.
*
* @param string $name the name of the parameter.
* @param string $value the value of the parameter.
*/
public function setMinkParameter($name, $value) {
parent::setMinkParameter($name, $value);
if ($name === "base_url") {
foreach ($this->actors as $actor) {
$actor->setBaseUrl($value);
}
}
}
/**
* @BeforeScenario
*
* Initializes the Actors for the new Scenario with the default Actor.
*
* Other Actors are added (and their Mink Sessions started) only when they
* are used in an "I act as XXX" step.
*/
public function initializeActors() {
$this->actors = array();
$this->actors["default"] = new Actor($this->getSession(), $this->getMinkParameter("base_url"));
$this->currentActor = $this->actors["default"];
}
/**
* @BeforeStep
*/
public function setCurrentActorInSiblingActorAwareContexts(BeforeStepScope $scope) {
$environment = $scope->getEnvironment();
foreach ($environment->getContexts() as $context) {
if ($context instanceof ActorAwareInterface) {
$context->setCurrentActor($this->currentActor);
}
}
}
/**
* @Given I act as :actorName
*/
public function iActAs($actorName) {
if (!array_key_exists($actorName, $this->actors)) {
$this->actors[$actorName] = new Actor($this->getSession($actorName), $this->getMinkParameter("base_url"));
}
$this->currentActor = $this->actors[$actorName];
}
/**
* @AfterScenario
*
* Stops all the Mink Sessions used in the last Scenario.
*/
public function cleanUpSessions() {
foreach ($this->actors as $actor) {
$actor->getSession()->stop();
}
}
}