WebDriverTestBase.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. namespace Drupal\FunctionalJavascriptTests;
  3. use Behat\Mink\Exception\DriverException;
  4. use Drupal\Tests\BrowserTestBase;
  5. use Symfony\Component\DependencyInjection\ContainerInterface;
  6. use Zumba\GastonJS\Exception\DeadClient;
  7. use Zumba\Mink\Driver\PhantomJSDriver;
  8. /**
  9. * Runs a browser test using a driver that supports Javascript.
  10. *
  11. * Base class for testing browser interaction implemented in JavaScript.
  12. *
  13. * @ingroup testing
  14. */
  15. abstract class WebDriverTestBase extends BrowserTestBase {
  16. /**
  17. * Disables CSS animations in tests for more reliable testing.
  18. *
  19. * CSS animations are disabled by installing the css_disable_transitions_test
  20. * module. Set to FALSE to test CSS animations.
  21. *
  22. * @var bool
  23. */
  24. protected $disableCssAnimations = TRUE;
  25. /**
  26. * {@inheritdoc}
  27. *
  28. * To use a legacy phantomjs based approach, please use PhantomJSDriver::class.
  29. */
  30. protected $minkDefaultDriverClass = DrupalSelenium2Driver::class;
  31. /**
  32. * {@inheritdoc}
  33. */
  34. protected function initMink() {
  35. if ($this->minkDefaultDriverClass === DrupalSelenium2Driver::class) {
  36. $this->minkDefaultDriverArgs = ['chrome', NULL, 'http://localhost:4444'];
  37. }
  38. elseif ($this->minkDefaultDriverClass === PhantomJSDriver::class) {
  39. // Set up the template cache used by the PhantomJS mink driver.
  40. $path = $this->tempFilesDirectory . DIRECTORY_SEPARATOR . 'browsertestbase-templatecache';
  41. $this->minkDefaultDriverArgs = [
  42. 'http://127.0.0.1:8510',
  43. $path,
  44. ];
  45. if (!file_exists($path)) {
  46. mkdir($path);
  47. }
  48. }
  49. try {
  50. return parent::initMink();
  51. }
  52. catch (DeadClient $e) {
  53. $this->markTestSkipped('PhantomJS is either not installed or not running. Start it via phantomjs --ssl-protocol=any --ignore-ssl-errors=true vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768&');
  54. }
  55. catch (DriverException $e) {
  56. if ($this->minkDefaultDriverClass === DrupalSelenium2Driver::class) {
  57. $this->markTestSkipped("The test wasn't able to connect to your webdriver instance. For more information read core/tests/README.md.\n\nThe original message while starting Mink: {$e->getMessage()}");
  58. }
  59. else {
  60. throw $e;
  61. }
  62. }
  63. catch (\Exception $e) {
  64. $this->markTestSkipped('An unexpected error occurred while starting Mink: ' . $e->getMessage());
  65. }
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. protected function installModulesFromClassProperty(ContainerInterface $container) {
  71. self::$modules = ['js_deprecation_log_test'];
  72. if ($this->disableCssAnimations) {
  73. self::$modules[] = 'css_disable_transitions_test';
  74. }
  75. parent::installModulesFromClassProperty($container);
  76. }
  77. /**
  78. * {@inheritdoc}
  79. */
  80. protected function initFrontPage() {
  81. parent::initFrontPage();
  82. // Set a standard window size so that all javascript tests start with the
  83. // same viewport.
  84. $this->getSession()->resizeWindow(1024, 768);
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. protected function tearDown() {
  90. if ($this->mink) {
  91. // Wait for all requests to finish. It is possible that an AJAX request is
  92. // still on-going.
  93. $result = $this->getSession()->wait(5000, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))');
  94. if (!$result) {
  95. // If the wait is unsuccessful, there may still be an AJAX request in
  96. // progress. If we tear down now, then this AJAX request may fail with
  97. // missing database tables, because tear down will have removed them.
  98. // Rather than allow it to fail, throw an explicit exception now
  99. // explaining what the problem is.
  100. throw new \RuntimeException('Unfinished AJAX requests while tearing down a test');
  101. }
  102. $warnings = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_deprecation_log_test.warnings') || JSON.stringify([]))");
  103. foreach ($warnings as $warning) {
  104. if (strpos($warning, '[Deprecation]') === 0) {
  105. @trigger_error('Javascript Deprecation:' . substr($warning, 13), E_USER_DEPRECATED);
  106. }
  107. }
  108. }
  109. parent::tearDown();
  110. }
  111. /**
  112. * {@inheritdoc}
  113. */
  114. protected function getMinkDriverArgs() {
  115. if ($this->minkDefaultDriverClass === DrupalSelenium2Driver::class) {
  116. return getenv('MINK_DRIVER_ARGS_WEBDRIVER') ?: getenv('MINK_DRIVER_ARGS_PHANTOMJS') ?: parent::getMinkDriverArgs();
  117. }
  118. elseif ($this->minkDefaultDriverClass === PhantomJSDriver::class) {
  119. return getenv('MINK_DRIVER_ARGS_PHANTOMJS') ?: parent::getMinkDriverArgs();
  120. }
  121. return parent::getMinkDriverArgs();
  122. }
  123. /**
  124. * Asserts that the element with the given CSS selector is visible.
  125. *
  126. * @param string $css_selector
  127. * The CSS selector identifying the element to check.
  128. * @param string $message
  129. * Optional message to show alongside the assertion.
  130. *
  131. * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use
  132. * \Behat\Mink\Element\NodeElement::isVisible() instead.
  133. */
  134. protected function assertElementVisible($css_selector, $message = '') {
  135. $this->assertTrue($this->getSession()->getDriver()->isVisible($this->cssSelectToXpath($css_selector)), $message);
  136. @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.1.0 and will be removed in 9.0.0. Use \Behat\Mink\Element\NodeElement::isVisible() instead.', E_USER_DEPRECATED);
  137. }
  138. /**
  139. * Asserts that the element with the given CSS selector is not visible.
  140. *
  141. * @param string $css_selector
  142. * The CSS selector identifying the element to check.
  143. * @param string $message
  144. * Optional message to show alongside the assertion.
  145. *
  146. * @deprecated in drupal:8.1.0 and is removed from drupal:9.0.0. Use
  147. * \Behat\Mink\Element\NodeElement::isVisible() instead.
  148. */
  149. protected function assertElementNotVisible($css_selector, $message = '') {
  150. $this->assertFalse($this->getSession()->getDriver()->isVisible($this->cssSelectToXpath($css_selector)), $message);
  151. @trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.1.0 and will be removed in 9.0.0. Use \Behat\Mink\Element\NodeElement::isVisible() instead.', E_USER_DEPRECATED);
  152. }
  153. /**
  154. * Waits for the given time or until the given JS condition becomes TRUE.
  155. *
  156. * @param string $condition
  157. * JS condition to wait until it becomes TRUE.
  158. * @param int $timeout
  159. * (Optional) Timeout in milliseconds, defaults to 10000.
  160. * @param string $message
  161. * (optional) A message to display with the assertion. If left blank, a
  162. * default message will be displayed.
  163. *
  164. * @throws \PHPUnit\Framework\AssertionFailedError
  165. *
  166. * @see \Behat\Mink\Driver\DriverInterface::evaluateScript()
  167. */
  168. protected function assertJsCondition($condition, $timeout = 10000, $message = '') {
  169. $message = $message ?: "Javascript condition met:\n" . $condition;
  170. $result = $this->getSession()->getDriver()->wait($timeout, $condition);
  171. $this->assertTrue($result, $message);
  172. }
  173. /**
  174. * Creates a screenshot.
  175. *
  176. * @param string $filename
  177. * The file name of the resulting screenshot. If using the default phantomjs
  178. * driver then this should be a JPG filename.
  179. * @param bool $set_background_color
  180. * (optional) By default this method will set the background color to white.
  181. * Set to FALSE to override this behavior.
  182. *
  183. * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
  184. * When operation not supported by the driver.
  185. * @throws \Behat\Mink\Exception\DriverException
  186. * When the operation cannot be done.
  187. */
  188. protected function createScreenshot($filename, $set_background_color = TRUE) {
  189. $session = $this->getSession();
  190. if ($set_background_color) {
  191. $session->executeScript("document.body.style.backgroundColor = 'white';");
  192. }
  193. $image = $session->getScreenshot();
  194. file_put_contents($filename, $image);
  195. }
  196. /**
  197. * {@inheritdoc}
  198. */
  199. public function assertSession($name = NULL) {
  200. return new WebDriverWebAssert($this->getSession($name), $this->baseUrl);
  201. }
  202. /**
  203. * Gets the current Drupal javascript settings and parses into an array.
  204. *
  205. * Unlike BrowserTestBase::getDrupalSettings(), this implementation reads the
  206. * current values of drupalSettings, capturing all changes made via javascript
  207. * after the page was loaded.
  208. *
  209. * @return array
  210. * The Drupal javascript settings array.
  211. *
  212. * @see \Drupal\Tests\BrowserTestBase::getDrupalSettings()
  213. */
  214. protected function getDrupalSettings() {
  215. $script = <<<EndOfScript
  216. (function () {
  217. if (typeof drupalSettings !== 'undefined') {
  218. return drupalSettings;
  219. }
  220. })();
  221. EndOfScript;
  222. return $this->getSession()->evaluateScript($script) ?: [];
  223. }
  224. /**
  225. * {@inheritdoc}
  226. */
  227. protected function getHtmlOutputHeaders() {
  228. // The webdriver API does not support fetching headers.
  229. return '';
  230. }
  231. }