ActorContext.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. /**
  3. *
  4. * @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
  5. *
  6. * @license GNU AGPL version 3 or any later version
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as
  10. * published by the Free Software Foundation, either version 3 of the
  11. * License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. use Behat\Behat\Hook\Scope\BeforeStepScope;
  23. use Behat\MinkExtension\Context\RawMinkContext;
  24. /**
  25. * Behat context to set the actor used in sibling contexts.
  26. *
  27. * This helper context provides a step definition ("I act as XXX") to change the
  28. * current actor of the scenario, which makes possible to use different browser
  29. * sessions in the same scenario.
  30. *
  31. * Sibling contexts that want to have access to the current actor of the
  32. * scenario must implement the ActorAwareInterface; this can be done just by
  33. * using the ActorAware trait.
  34. *
  35. * Besides updating the current actor in sibling contexts the ActorContext also
  36. * propagates its inherited "base_url" Mink parameter to the Actors as needed.
  37. *
  38. * By default no multiplier for the find timeout is set in the Actors. However,
  39. * it can be customized using the "actorTimeoutMultiplier" parameter of the
  40. * ActorContext in "behat.yml". This parameter also affects the overall timeout
  41. * to start a session for an Actor before giving up.
  42. *
  43. * Every actor used in the scenarios must have a corresponding Mink session
  44. * declared in "behat.yml" with the same name as the actor. All used sessions
  45. * are stopped after each scenario is run.
  46. */
  47. class ActorContext extends RawMinkContext {
  48. /**
  49. * @var array
  50. */
  51. private $actors;
  52. /**
  53. * @var array
  54. */
  55. private $sharedNotebook;
  56. /**
  57. * @var Actor
  58. */
  59. private $currentActor;
  60. /**
  61. * @var float
  62. */
  63. private $actorTimeoutMultiplier;
  64. /**
  65. * Creates a new ActorContext.
  66. *
  67. * @param float $actorTimeoutMultiplier the timeout multiplier for Actor
  68. * related timeouts.
  69. */
  70. public function __construct($actorTimeoutMultiplier = 1) {
  71. $this->actorTimeoutMultiplier = $actorTimeoutMultiplier;
  72. }
  73. /**
  74. * Sets a Mink parameter.
  75. *
  76. * When the "base_url" parameter is set its value is propagated to all the
  77. * Actors.
  78. *
  79. * @param string $name the name of the parameter.
  80. * @param string $value the value of the parameter.
  81. */
  82. public function setMinkParameter($name, $value) {
  83. parent::setMinkParameter($name, $value);
  84. if ($name === "base_url") {
  85. foreach ($this->actors as $actor) {
  86. $actor->setBaseUrl($value);
  87. }
  88. }
  89. }
  90. /**
  91. * Returns the session with the given name.
  92. *
  93. * If the session is not started it is started before returning it; if the
  94. * session fails to start (typically due to a timeout connecting with the
  95. * web browser) it will be tried again up to $actorTimeoutMultiplier times
  96. * in total (rounded up to the next integer) before giving up.
  97. *
  98. * @param string|null $sname the name of the session to get, or null for the
  99. * default session.
  100. * @return \Behat\Mink\Session the session.
  101. */
  102. public function getSession($name = null) {
  103. for ($i = 0; $i < ($this->actorTimeoutMultiplier - 1); $i++) {
  104. try {
  105. return parent::getSession($name);
  106. } catch (\Behat\Mink\Exception\DriverException $exception) {
  107. echo "Exception when getting " . ($name == null? "default session": "session '$name'") . ": " . $exception->getMessage() . "\n";
  108. echo "Trying again\n";
  109. }
  110. }
  111. return parent::getSession($name);
  112. }
  113. /**
  114. * @BeforeScenario
  115. *
  116. * Initializes the Actors for the new Scenario with the default Actor.
  117. *
  118. * Other Actors are added (and their Mink Sessions started) only when they
  119. * are used in an "I act as XXX" step.
  120. */
  121. public function initializeActors() {
  122. $this->actors = [];
  123. $this->sharedNotebook = [];
  124. $this->getSession()->start();
  125. $this->getSession()->maximizeWindow();
  126. $this->actors["default"] = new Actor("default", $this->getSession(), $this->getMinkParameter("base_url"), $this->sharedNotebook);
  127. $this->actors["default"]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier);
  128. $this->currentActor = $this->actors["default"];
  129. }
  130. /**
  131. * @BeforeStep
  132. */
  133. public function setCurrentActorInSiblingActorAwareContexts(BeforeStepScope $scope) {
  134. $environment = $scope->getEnvironment();
  135. foreach ($environment->getContexts() as $context) {
  136. if ($context instanceof ActorAwareInterface) {
  137. $context->setCurrentActor($this->currentActor);
  138. }
  139. }
  140. }
  141. /**
  142. * @Given I act as :actorName
  143. */
  144. public function iActAs($actorName) {
  145. if (!array_key_exists($actorName, $this->actors)) {
  146. $this->getSession($actorName)->start();
  147. $this->getSession($actorName)->maximizeWindow();
  148. $this->actors[$actorName] = new Actor($actorName, $this->getSession($actorName), $this->getMinkParameter("base_url"), $this->sharedNotebook);
  149. $this->actors[$actorName]->setFindTimeoutMultiplier($this->actorTimeoutMultiplier);
  150. }
  151. $this->currentActor = $this->actors[$actorName];
  152. // Ensure that the browser window of the actor is the one in the
  153. // foreground; this works around a bug in the Firefox driver of Selenium
  154. // and/or maybe in Firefox itself when interacting with a window in the
  155. // background, but also reflects better how the user would interact with
  156. // the browser in real life.
  157. $session = $this->actors[$actorName]->getSession();
  158. $session->switchToWindow($session->getWindowName());
  159. }
  160. /**
  161. * @AfterScenario
  162. *
  163. * Stops all the Mink Sessions used in the last Scenario.
  164. */
  165. public function cleanUpSessions() {
  166. foreach ($this->actors as $actor) {
  167. $actor->getSession()->stop();
  168. }
  169. }
  170. }