Drupal investigation

WebAssert.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <?php
  2. namespace Drupal\Tests;
  3. use Behat\Mink\Exception\ExpectationException;
  4. use Behat\Mink\WebAssert as MinkWebAssert;
  5. use Behat\Mink\Element\TraversableElement;
  6. use Behat\Mink\Exception\ElementNotFoundException;
  7. use Behat\Mink\Session;
  8. use Drupal\Component\Utility\Html;
  9. use Drupal\Core\Url;
  10. /**
  11. * Defines a class with methods for asserting presence of elements during tests.
  12. */
  13. class WebAssert extends MinkWebAssert {
  14. /**
  15. * The absolute URL of the site under test.
  16. *
  17. * @var string
  18. */
  19. protected $baseUrl = '';
  20. /**
  21. * Constructor.
  22. *
  23. * @param \Behat\Mink\Session $session
  24. * The Behat session object;
  25. * @param string $base_url
  26. * The base URL of the site under test.
  27. */
  28. public function __construct(Session $session, $base_url = '') {
  29. parent::__construct($session);
  30. $this->baseUrl = $base_url;
  31. }
  32. /**
  33. * {@inheritdoc}
  34. */
  35. protected function cleanUrl($url) {
  36. if ($url instanceof Url) {
  37. $url = $url->setAbsolute()->toString();
  38. }
  39. // Strip the base URL from the beginning for absolute URLs.
  40. if ($this->baseUrl !== '' && strpos($url, $this->baseUrl) === 0) {
  41. $url = substr($url, strlen($this->baseUrl));
  42. }
  43. // Make sure there is a forward slash at the beginning of relative URLs for
  44. // consistency.
  45. if (parse_url($url, PHP_URL_HOST) === NULL && strpos($url, '/') !== 0) {
  46. $url = "/$url";
  47. }
  48. return parent::cleanUrl($url);
  49. }
  50. /**
  51. * Checks that specific button exists on the current page.
  52. *
  53. * @param string $button
  54. * One of id|name|label|value for the button.
  55. * @param \Behat\Mink\Element\TraversableElement $container
  56. * (optional) The document to check against. Defaults to the current page.
  57. *
  58. * @return \Behat\Mink\Element\NodeElement
  59. * The matching element.
  60. *
  61. * @throws \Behat\Mink\Exception\ElementNotFoundException
  62. * When the element doesn't exist.
  63. */
  64. public function buttonExists($button, TraversableElement $container = NULL) {
  65. $container = $container ?: $this->session->getPage();
  66. $node = $container->findButton($button);
  67. if ($node === NULL) {
  68. throw new ElementNotFoundException($this->session, 'button', 'id|name|label|value', $button);
  69. }
  70. return $node;
  71. }
  72. /**
  73. * Checks that the specific button does NOT exist on the current page.
  74. *
  75. * @param string $button
  76. * One of id|name|label|value for the button.
  77. * @param \Behat\Mink\Element\TraversableElement $container
  78. * (optional) The document to check against. Defaults to the current page.
  79. *
  80. * @throws \Behat\Mink\Exception\ExpectationException
  81. * When the button exists.
  82. */
  83. public function buttonNotExists($button, TraversableElement $container = NULL) {
  84. $container = $container ?: $this->session->getPage();
  85. $node = $container->findButton($button);
  86. $this->assert(NULL === $node, sprintf('A button "%s" appears on this page, but it should not.', $button));
  87. }
  88. /**
  89. * Checks that specific select field exists on the current page.
  90. *
  91. * @param string $select
  92. * One of id|name|label|value for the select field.
  93. * @param \Behat\Mink\Element\TraversableElement $container
  94. * (optional) The document to check against. Defaults to the current page.
  95. *
  96. * @return \Behat\Mink\Element\NodeElement
  97. * The matching element
  98. *
  99. * @throws \Behat\Mink\Exception\ElementNotFoundException
  100. * When the element doesn't exist.
  101. */
  102. public function selectExists($select, TraversableElement $container = NULL) {
  103. $container = $container ?: $this->session->getPage();
  104. $node = $container->find('named', [
  105. 'select',
  106. $this->session->getSelectorsHandler()->xpathLiteral($select),
  107. ]);
  108. if ($node === NULL) {
  109. throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
  110. }
  111. return $node;
  112. }
  113. /**
  114. * Checks that specific option in a select field exists on the current page.
  115. *
  116. * @param string $select
  117. * One of id|name|label|value for the select field.
  118. * @param string $option
  119. * The option value.
  120. * @param \Behat\Mink\Element\TraversableElement $container
  121. * (optional) The document to check against. Defaults to the current page.
  122. *
  123. * @return \Behat\Mink\Element\NodeElement
  124. * The matching option element
  125. *
  126. * @throws \Behat\Mink\Exception\ElementNotFoundException
  127. * When the element doesn't exist.
  128. */
  129. public function optionExists($select, $option, TraversableElement $container = NULL) {
  130. $container = $container ?: $this->session->getPage();
  131. $select_field = $container->find('named', [
  132. 'select',
  133. $this->session->getSelectorsHandler()->xpathLiteral($select),
  134. ]);
  135. if ($select_field === NULL) {
  136. throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
  137. }
  138. $option_field = $select_field->find('named', ['option', $option]);
  139. if ($option_field === NULL) {
  140. throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $option);
  141. }
  142. return $option_field;
  143. }
  144. /**
  145. * Checks that an option in a select field does NOT exist on the current page.
  146. *
  147. * @param string $select
  148. * One of id|name|label|value for the select field.
  149. * @param string $option
  150. * The option value that shoulkd not exist.
  151. * @param \Behat\Mink\Element\TraversableElement $container
  152. * (optional) The document to check against. Defaults to the current page.
  153. *
  154. * @throws \Behat\Mink\Exception\ElementNotFoundException
  155. * When the select element doesn't exist.
  156. */
  157. public function optionNotExists($select, $option, TraversableElement $container = NULL) {
  158. $container = $container ?: $this->session->getPage();
  159. $select_field = $container->find('named', [
  160. 'select',
  161. $this->session->getSelectorsHandler()->xpathLiteral($select),
  162. ]);
  163. if ($select_field === NULL) {
  164. throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
  165. }
  166. $option_field = $select_field->find('named', ['option', $option]);
  167. $this->assert($option_field === NULL, sprintf('An option "%s" exists in select "%s", but it should not.', $option, $select));
  168. }
  169. /**
  170. * Pass if the page title is the given string.
  171. *
  172. * @param string $expected_title
  173. * The string the page title should be.
  174. *
  175. * @throws \Behat\Mink\Exception\ExpectationException
  176. * Thrown when element doesn't exist, or the title is a different one.
  177. */
  178. public function titleEquals($expected_title) {
  179. $title_element = $this->session->getPage()->find('css', 'title');
  180. if (!$title_element) {
  181. throw new ExpectationException('No title element found on the page', $this->session);
  182. }
  183. $actual_title = $title_element->getText();
  184. $this->assert($expected_title === $actual_title, 'Title found');
  185. }
  186. /**
  187. * Passes if a link with the specified label is found.
  188. *
  189. * An optional link index may be passed.
  190. *
  191. * @param string $label
  192. * Text between the anchor tags.
  193. * @param int $index
  194. * Link position counting from zero.
  195. * @param string $message
  196. * (optional) A message to display with the assertion. Do not translate
  197. * messages: use strtr() to embed variables in the message text, not
  198. * t(). If left blank, a default message will be displayed.
  199. *
  200. * @throws \Behat\Mink\Exception\ExpectationException
  201. * Thrown when element doesn't exist, or the link label is a different one.
  202. */
  203. public function linkExists($label, $index = 0, $message = '') {
  204. $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label]));
  205. $links = $this->session->getPage()->findAll('named', ['link', $label]);
  206. $this->assert(!empty($links[$index]), $message);
  207. }
  208. /**
  209. * Passes if a link with the specified label is not found.
  210. *
  211. * An optional link index may be passed.
  212. *
  213. * @param string $label
  214. * Text between the anchor tags.
  215. * @param string $message
  216. * (optional) A message to display with the assertion. Do not translate
  217. * messages: use strtr() to embed variables in the message text, not
  218. * t(). If left blank, a default message will be displayed.
  219. *
  220. * @throws \Behat\Mink\Exception\ExpectationException
  221. * Thrown when element doesn't exist, or the link label is a different one.
  222. */
  223. public function linkNotExists($label, $message = '') {
  224. $message = ($message ? $message : strtr('Link with label %label not found.', ['%label' => $label]));
  225. $links = $this->session->getPage()->findAll('named', ['link', $label]);
  226. $this->assert(empty($links), $message);
  227. }
  228. /**
  229. * Passes if a link containing a given href (part) is found.
  230. *
  231. * @param string $href
  232. * The full or partial value of the 'href' attribute of the anchor tag.
  233. * @param int $index
  234. * Link position counting from zero.
  235. * @param string $message
  236. * (optional) A message to display with the assertion. Do not translate
  237. * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
  238. * variables in the message text, not t(). If left blank, a default message
  239. * will be displayed.
  240. *
  241. * @throws \Behat\Mink\Exception\ExpectationException
  242. * Thrown when element doesn't exist, or the link label is a different one.
  243. */
  244. public function linkByHrefExists($href, $index = 0, $message = '') {
  245. $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
  246. $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href]));
  247. $links = $this->session->getPage()->findAll('xpath', $xpath);
  248. $this->assert(!empty($links[$index]), $message);
  249. }
  250. /**
  251. * Passes if a link containing a given href (part) is not found.
  252. *
  253. * @param string $href
  254. * The full or partial value of the 'href' attribute of the anchor tag.
  255. * @param string $message
  256. * (optional) A message to display with the assertion. Do not translate
  257. * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
  258. * variables in the message text, not t(). If left blank, a default message
  259. * will be displayed.
  260. *
  261. * @throws \Behat\Mink\Exception\ExpectationException
  262. * Thrown when element doesn't exist, or the link label is a different one.
  263. */
  264. public function linkByHrefNotExists($href, $message = '') {
  265. $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
  266. $message = ($message ? $message : strtr('No link containing href %href found.', ['%href' => $href]));
  267. $links = $this->session->getPage()->findAll('xpath', $xpath);
  268. $this->assert(empty($links), $message);
  269. }
  270. /**
  271. * Builds an XPath query.
  272. *
  273. * Builds an XPath query by replacing placeholders in the query by the value
  274. * of the arguments.
  275. *
  276. * XPath 1.0 (the version supported by libxml2, the underlying XML library
  277. * used by PHP) doesn't support any form of quotation. This function
  278. * simplifies the building of XPath expression.
  279. *
  280. * @param string $xpath
  281. * An XPath query, possibly with placeholders in the form ':name'.
  282. * @param array $args
  283. * An array of arguments with keys in the form ':name' matching the
  284. * placeholders in the query. The values may be either strings or numeric
  285. * values.
  286. *
  287. * @return string
  288. * An XPath query with arguments replaced.
  289. */
  290. public function buildXPathQuery($xpath, array $args = []) {
  291. // Replace placeholders.
  292. foreach ($args as $placeholder => $value) {
  293. if (is_object($value)) {
  294. throw new \InvalidArgumentException('Just pass in scalar values for $args and remove all t() calls from your test.');
  295. }
  296. // XPath 1.0 doesn't support a way to escape single or double quotes in a
  297. // string literal. We split double quotes out of the string, and encode
  298. // them separately.
  299. if (is_string($value)) {
  300. // Explode the text at the quote characters.
  301. $parts = explode('"', $value);
  302. // Quote the parts.
  303. foreach ($parts as &$part) {
  304. $part = '"' . $part . '"';
  305. }
  306. // Return the string.
  307. $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0];
  308. }
  309. // Use preg_replace_callback() instead of preg_replace() to prevent the
  310. // regular expression engine from trying to substitute backreferences.
  311. $replacement = function ($matches) use ($value) {
  312. return $value;
  313. };
  314. $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath);
  315. }
  316. return $xpath;
  317. }
  318. /**
  319. * Passes if the raw text IS NOT found escaped on the loaded page.
  320. *
  321. * Raw text refers to the raw HTML that the page generated.
  322. *
  323. * @param string $raw
  324. * Raw (HTML) string to look for.
  325. */
  326. public function assertNoEscaped($raw) {
  327. $this->responseNotContains(Html::escape($raw));
  328. }
  329. /**
  330. * Passes if the raw text IS found escaped on the loaded page.
  331. *
  332. * Raw text refers to the raw HTML that the page generated.
  333. *
  334. * @param string $raw
  335. * Raw (HTML) string to look for.
  336. */
  337. public function assertEscaped($raw) {
  338. $this->responseContains(Html::escape($raw));
  339. }
  340. /**
  341. * Asserts a condition.
  342. *
  343. * The parent method is overridden because it is a private method.
  344. *
  345. * @param bool $condition
  346. * The condition.
  347. * @param string $message
  348. * The success message.
  349. *
  350. * @throws \Behat\Mink\Exception\ExpectationException
  351. * When the condition is not fulfilled.
  352. */
  353. public function assert($condition, $message) {
  354. if ($condition) {
  355. return;
  356. }
  357. throw new ExpectationException($message, $this->session->getDriver());
  358. }
  359. /**
  360. * Checks that a given form field element is disabled.
  361. *
  362. * @param string $field
  363. * One of id|name|label|value for the field.
  364. * @param \Behat\Mink\Element\TraversableElement $container
  365. * (optional) The document to check against. Defaults to the current page.
  366. *
  367. * @return \Behat\Mink\Element\NodeElement
  368. * The matching element.
  369. *
  370. * @throws \Behat\Mink\Exception\ElementNotFoundException
  371. * @throws \Behat\Mink\Exception\ExpectationException
  372. */
  373. public function fieldDisabled($field, TraversableElement $container = NULL) {
  374. $container = $container ?: $this->session->getPage();
  375. $node = $container->findField($field);
  376. if ($node === NULL) {
  377. throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field);
  378. }
  379. if (!$node->hasAttribute('disabled')) {
  380. throw new ExpectationException("Field $field is disabled", $this->session->getDriver());
  381. }
  382. return $node;
  383. }
  384. /**
  385. * Checks that specific hidden field exists.
  386. *
  387. * @param string $field
  388. * One of id|name|value for the hidden field.
  389. * @param \Behat\Mink\Element\TraversableElement $container
  390. * (optional) The document to check against. Defaults to the current page.
  391. *
  392. * @return \Behat\Mink\Element\NodeElement
  393. * The matching element.
  394. *
  395. * @throws \Behat\Mink\Exception\ElementNotFoundException
  396. */
  397. public function hiddenFieldExists($field, TraversableElement $container = NULL) {
  398. $container = $container ?: $this->session->getPage();
  399. if ($node = $container->find('hidden_field_selector', ['hidden_field', $field])) {
  400. return $node;
  401. }
  402. throw new ElementNotFoundException($this->session->getDriver(), 'form hidden field', 'id|name|value', $field);
  403. }
  404. /**
  405. * Checks that specific hidden field does not exists.
  406. *
  407. * @param string $field
  408. * One of id|name|value for the hidden field.
  409. * @param \Behat\Mink\Element\TraversableElement $container
  410. * (optional) The document to check against. Defaults to the current page.
  411. *
  412. * @throws \Behat\Mink\Exception\ExpectationException
  413. */
  414. public function hiddenFieldNotExists($field, TraversableElement $container = NULL) {
  415. $container = $container ?: $this->session->getPage();
  416. $node = $container->find('hidden_field_selector', ['hidden_field', $field]);
  417. $this->assert($node === NULL, "A hidden field '$field' exists on this page, but it should not.");
  418. }
  419. /**
  420. * Checks that specific hidden field have provided value.
  421. *
  422. * @param string $field
  423. * One of id|name|value for the hidden field.
  424. * @param string $value
  425. * The hidden field value that needs to be checked.
  426. * @param \Behat\Mink\Element\TraversableElement $container
  427. * (optional) The document to check against. Defaults to the current page.
  428. *
  429. * @throws \Behat\Mink\Exception\ElementNotFoundException
  430. * @throws \Behat\Mink\Exception\ExpectationException
  431. */
  432. public function hiddenFieldValueEquals($field, $value, TraversableElement $container = NULL) {
  433. $node = $this->hiddenFieldExists($field, $container);
  434. $actual = $node->getValue();
  435. $regex = '/^' . preg_quote($value, '/') . '$/ui';
  436. $message = "The hidden field '$field' value is '$actual', but '$value' expected.";
  437. $this->assert((bool) preg_match($regex, $actual), $message);
  438. }
  439. /**
  440. * Checks that specific hidden field doesn't have the provided value.
  441. *
  442. * @param string $field
  443. * One of id|name|value for the hidden field.
  444. * @param string $value
  445. * The hidden field value that needs to be checked.
  446. * @param \Behat\Mink\Element\TraversableElement $container
  447. * (optional) The document to check against. Defaults to the current page.
  448. *
  449. * @throws \Behat\Mink\Exception\ElementNotFoundException
  450. * @throws \Behat\Mink\Exception\ExpectationException
  451. */
  452. public function hiddenFieldValueNotEquals($field, $value, TraversableElement $container = NULL) {
  453. $node = $this->hiddenFieldExists($field, $container);
  454. $actual = $node->getValue();
  455. $regex = '/^' . preg_quote($value, '/') . '$/ui';
  456. $message = "The hidden field '$field' value is '$actual', but it should not be.";
  457. $this->assert(!preg_match($regex, $actual), $message);
  458. }
  459. }