annotate core/tests/Drupal/Tests/UiHelperTrait.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@17 1 <?php
Chris@17 2
Chris@17 3 namespace Drupal\Tests;
Chris@17 4
Chris@17 5 use Behat\Mink\Driver\GoutteDriver;
Chris@17 6 use Drupal\Component\Render\FormattableMarkup;
Chris@17 7 use Drupal\Component\Utility\Html;
Chris@17 8 use Drupal\Component\Utility\UrlHelper;
Chris@17 9 use Drupal\Core\Session\AccountInterface;
Chris@17 10 use Drupal\Core\Session\AnonymousUserSession;
Chris@17 11 use Drupal\Core\Test\RefreshVariablesTrait;
Chris@17 12 use Drupal\Core\Url;
Chris@17 13
Chris@17 14 /**
Chris@17 15 * Provides UI helper methods.
Chris@17 16 */
Chris@17 17 trait UiHelperTrait {
Chris@17 18
Chris@17 19 use BrowserHtmlDebugTrait;
Chris@17 20 use AssertHelperTrait;
Chris@17 21 use RefreshVariablesTrait;
Chris@17 22
Chris@17 23 /**
Chris@17 24 * The current user logged in using the Mink controlled browser.
Chris@17 25 *
Chris@17 26 * @var \Drupal\user\UserInterface
Chris@17 27 */
Chris@17 28 protected $loggedInUser = FALSE;
Chris@17 29
Chris@17 30 /**
Chris@17 31 * The number of meta refresh redirects to follow, or NULL if unlimited.
Chris@17 32 *
Chris@17 33 * @var null|int
Chris@17 34 */
Chris@17 35 protected $maximumMetaRefreshCount = NULL;
Chris@17 36
Chris@17 37 /**
Chris@17 38 * The number of meta refresh redirects followed during ::drupalGet().
Chris@17 39 *
Chris@17 40 * @var int
Chris@17 41 */
Chris@17 42 protected $metaRefreshCount = 0;
Chris@17 43
Chris@17 44 /**
Chris@17 45 * Fills and submits a form.
Chris@17 46 *
Chris@17 47 * @param array $edit
Chris@17 48 * Field data in an associative array. Changes the current input fields
Chris@17 49 * (where possible) to the values indicated.
Chris@17 50 *
Chris@17 51 * A checkbox can be set to TRUE to be checked and should be set to FALSE to
Chris@17 52 * be unchecked.
Chris@17 53 * @param string $submit
Chris@17 54 * Value of the submit button whose click is to be emulated. For example,
Chris@17 55 * 'Save'. The processing of the request depends on this value. For example,
Chris@17 56 * a form may have one button with the value 'Save' and another button with
Chris@17 57 * the value 'Delete', and execute different code depending on which one is
Chris@17 58 * clicked.
Chris@17 59 * @param string $form_html_id
Chris@17 60 * (optional) HTML ID of the form to be submitted. On some pages
Chris@17 61 * there are many identical forms, so just using the value of the submit
Chris@17 62 * button is not enough. For example: 'trigger-node-presave-assign-form'.
Chris@17 63 * Note that this is not the Drupal $form_id, but rather the HTML ID of the
Chris@17 64 * form, which is typically the same thing but with hyphens replacing the
Chris@17 65 * underscores.
Chris@17 66 */
Chris@17 67 protected function submitForm(array $edit, $submit, $form_html_id = NULL) {
Chris@17 68 $assert_session = $this->assertSession();
Chris@17 69
Chris@17 70 // Get the form.
Chris@17 71 if (isset($form_html_id)) {
Chris@17 72 $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']");
Chris@17 73 $submit_button = $assert_session->buttonExists($submit, $form);
Chris@17 74 $action = $form->getAttribute('action');
Chris@17 75 }
Chris@17 76 else {
Chris@17 77 $submit_button = $assert_session->buttonExists($submit);
Chris@17 78 $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button);
Chris@17 79 $action = $form->getAttribute('action');
Chris@17 80 }
Chris@17 81
Chris@17 82 // Edit the form values.
Chris@17 83 foreach ($edit as $name => $value) {
Chris@17 84 $field = $assert_session->fieldExists($name, $form);
Chris@17 85
Chris@17 86 // Provide support for the values '1' and '0' for checkboxes instead of
Chris@17 87 // TRUE and FALSE.
Chris@17 88 // @todo Get rid of supporting 1/0 by converting all tests cases using
Chris@17 89 // this to boolean values.
Chris@17 90 $field_type = $field->getAttribute('type');
Chris@17 91 if ($field_type === 'checkbox') {
Chris@17 92 $value = (bool) $value;
Chris@17 93 }
Chris@17 94
Chris@17 95 $field->setValue($value);
Chris@17 96 }
Chris@17 97
Chris@17 98 // Submit form.
Chris@17 99 $this->prepareRequest();
Chris@17 100 $submit_button->press();
Chris@17 101
Chris@17 102 // Ensure that any changes to variables in the other thread are picked up.
Chris@17 103 $this->refreshVariables();
Chris@17 104
Chris@17 105 // Check if there are any meta refresh redirects (like Batch API pages).
Chris@17 106 if ($this->checkForMetaRefresh()) {
Chris@17 107 // We are finished with all meta refresh redirects, so reset the counter.
Chris@17 108 $this->metaRefreshCount = 0;
Chris@17 109 }
Chris@17 110
Chris@17 111 // Log only for JavascriptTestBase tests because for Goutte we log with
Chris@17 112 // ::getResponseLogHandler.
Chris@17 113 if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
Chris@17 114 $out = $this->getSession()->getPage()->getContent();
Chris@17 115 $html_output = 'POST request to: ' . $action .
Chris@17 116 '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
Chris@17 117 $html_output .= '<hr />' . $out;
Chris@17 118 $html_output .= $this->getHtmlOutputHeaders();
Chris@17 119 $this->htmlOutput($html_output);
Chris@17 120 }
Chris@17 121
Chris@17 122 }
Chris@17 123
Chris@17 124 /**
Chris@17 125 * Executes a form submission.
Chris@17 126 *
Chris@17 127 * It will be done as usual submit form with Mink.
Chris@17 128 *
Chris@17 129 * @param \Drupal\Core\Url|string $path
Chris@17 130 * Location of the post form. Either a Drupal path or an absolute path or
Chris@17 131 * NULL to post to the current page. For multi-stage forms you can set the
Chris@17 132 * path to NULL and have it post to the last received page. Example:
Chris@17 133 *
Chris@17 134 * @code
Chris@17 135 * // First step in form.
Chris@17 136 * $edit = array(...);
Chris@17 137 * $this->drupalPostForm('some_url', $edit, 'Save');
Chris@17 138 *
Chris@17 139 * // Second step in form.
Chris@17 140 * $edit = array(...);
Chris@17 141 * $this->drupalPostForm(NULL, $edit, 'Save');
Chris@17 142 * @endcode
Chris@17 143 * @param array $edit
Chris@17 144 * Field data in an associative array. Changes the current input fields
Chris@17 145 * (where possible) to the values indicated.
Chris@17 146 *
Chris@17 147 * When working with form tests, the keys for an $edit element should match
Chris@17 148 * the 'name' parameter of the HTML of the form. For example, the 'body'
Chris@17 149 * field for a node has the following HTML:
Chris@17 150 * @code
Chris@17 151 * <textarea id="edit-body-und-0-value" class="text-full form-textarea
Chris@17 152 * resize-vertical" placeholder="" cols="60" rows="9"
Chris@17 153 * name="body[0][value]"></textarea>
Chris@17 154 * @endcode
Chris@17 155 * When testing this field using an $edit parameter, the code becomes:
Chris@17 156 * @code
Chris@17 157 * $edit["body[0][value]"] = 'My test value';
Chris@17 158 * @endcode
Chris@17 159 *
Chris@17 160 * A checkbox can be set to TRUE to be checked and should be set to FALSE to
Chris@17 161 * be unchecked. Multiple select fields can be tested using 'name[]' and
Chris@17 162 * setting each of the desired values in an array:
Chris@17 163 * @code
Chris@17 164 * $edit = array();
Chris@17 165 * $edit['name[]'] = array('value1', 'value2');
Chris@17 166 * @endcode
Chris@17 167 * @todo change $edit to disallow NULL as a value for Drupal 9.
Chris@17 168 * https://www.drupal.org/node/2802401
Chris@17 169 * @param string $submit
Chris@17 170 * The id, name, label or value of the submit button which is to be clicked.
Chris@17 171 * For example, 'Save'. The first element matched by
Chris@17 172 * \Drupal\Tests\WebAssert::buttonExists() will be used. The processing of
Chris@17 173 * the request depends on this value. For example, a form may have one
Chris@17 174 * button with the value 'Save' and another button with the value 'Delete',
Chris@17 175 * and execute different code depending on which one is clicked.
Chris@17 176 * @param array $options
Chris@17 177 * Options to be forwarded to the url generator.
Chris@17 178 * @param string|null $form_html_id
Chris@17 179 * (optional) HTML ID of the form to be submitted. On some pages
Chris@17 180 * there are many identical forms, so just using the value of the submit
Chris@17 181 * button is not enough. For example: 'trigger-node-presave-assign-form'.
Chris@17 182 * Note that this is not the Drupal $form_id, but rather the HTML ID of the
Chris@17 183 * form, which is typically the same thing but with hyphens replacing the
Chris@17 184 * underscores.
Chris@17 185 *
Chris@17 186 * @return string
Chris@17 187 * (deprecated) The response content after submit form. It is necessary for
Chris@17 188 * backwards compatibility and will be removed before Drupal 9.0. You should
Chris@17 189 * just use the webAssert object for your assertions.
Chris@17 190 *
Chris@17 191 * @see \Drupal\Tests\WebAssert::buttonExists()
Chris@17 192 */
Chris@17 193 protected function drupalPostForm($path, $edit, $submit, array $options = [], $form_html_id = NULL) {
Chris@17 194 if (is_object($submit)) {
Chris@17 195 // Cast MarkupInterface objects to string.
Chris@17 196 $submit = (string) $submit;
Chris@17 197 }
Chris@17 198 if ($edit === NULL) {
Chris@17 199 $edit = [];
Chris@17 200 }
Chris@17 201 if (is_array($edit)) {
Chris@17 202 $edit = $this->castSafeStrings($edit);
Chris@17 203 }
Chris@17 204
Chris@17 205 if (isset($path)) {
Chris@17 206 $this->drupalGet($path, $options);
Chris@17 207 }
Chris@17 208
Chris@17 209 $this->submitForm($edit, $submit, $form_html_id);
Chris@17 210
Chris@17 211 return $this->getSession()->getPage()->getContent();
Chris@17 212 }
Chris@17 213
Chris@17 214 /**
Chris@17 215 * Logs in a user using the Mink controlled browser.
Chris@17 216 *
Chris@17 217 * If a user is already logged in, then the current user is logged out before
Chris@17 218 * logging in the specified user.
Chris@17 219 *
Chris@17 220 * Please note that neither the current user nor the passed-in user object is
Chris@17 221 * populated with data of the logged in user. If you need full access to the
Chris@17 222 * user object after logging in, it must be updated manually. If you also need
Chris@17 223 * access to the plain-text password of the user (set by drupalCreateUser()),
Chris@17 224 * e.g. to log in the same user again, then it must be re-assigned manually.
Chris@17 225 * For example:
Chris@17 226 * @code
Chris@17 227 * // Create a user.
Chris@17 228 * $account = $this->drupalCreateUser(array());
Chris@17 229 * $this->drupalLogin($account);
Chris@17 230 * // Load real user object.
Chris@17 231 * $pass_raw = $account->passRaw;
Chris@17 232 * $account = User::load($account->id());
Chris@17 233 * $account->passRaw = $pass_raw;
Chris@17 234 * @endcode
Chris@17 235 *
Chris@17 236 * @param \Drupal\Core\Session\AccountInterface $account
Chris@17 237 * User object representing the user to log in.
Chris@17 238 *
Chris@17 239 * @see drupalCreateUser()
Chris@17 240 */
Chris@17 241 protected function drupalLogin(AccountInterface $account) {
Chris@17 242 if ($this->loggedInUser) {
Chris@17 243 $this->drupalLogout();
Chris@17 244 }
Chris@17 245
Chris@17 246 $this->drupalGet('user/login');
Chris@17 247 $this->submitForm([
Chris@18 248 'name' => $account->getAccountName(),
Chris@17 249 'pass' => $account->passRaw,
Chris@17 250 ], t('Log in'));
Chris@17 251
Chris@17 252 // @see ::drupalUserIsLoggedIn()
Chris@17 253 $account->sessionId = $this->getSession()->getCookie(\Drupal::service('session_configuration')->getOptions(\Drupal::request())['name']);
Chris@17 254 $this->assertTrue($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()]));
Chris@17 255
Chris@17 256 $this->loggedInUser = $account;
Chris@17 257 $this->container->get('current_user')->setAccount($account);
Chris@17 258 }
Chris@17 259
Chris@17 260 /**
Chris@17 261 * Logs a user out of the Mink controlled browser and confirms.
Chris@17 262 *
Chris@17 263 * Confirms logout by checking the login page.
Chris@17 264 */
Chris@17 265 protected function drupalLogout() {
Chris@17 266 // Make a request to the logout page, and redirect to the user page, the
Chris@17 267 // idea being if you were properly logged out you should be seeing a login
Chris@17 268 // screen.
Chris@17 269 $assert_session = $this->assertSession();
Chris@17 270 $this->drupalGet('user/logout', ['query' => ['destination' => 'user']]);
Chris@17 271 $assert_session->fieldExists('name');
Chris@17 272 $assert_session->fieldExists('pass');
Chris@17 273
Chris@17 274 // @see BrowserTestBase::drupalUserIsLoggedIn()
Chris@17 275 unset($this->loggedInUser->sessionId);
Chris@17 276 $this->loggedInUser = FALSE;
Chris@17 277 \Drupal::currentUser()->setAccount(new AnonymousUserSession());
Chris@17 278 }
Chris@17 279
Chris@17 280 /**
Chris@17 281 * Returns WebAssert object.
Chris@17 282 *
Chris@17 283 * @param string $name
Chris@17 284 * (optional) Name of the session. Defaults to the active session.
Chris@17 285 *
Chris@17 286 * @return \Drupal\Tests\WebAssert
Chris@17 287 * A new web-assert option for asserting the presence of elements with.
Chris@17 288 */
Chris@17 289 public function assertSession($name = NULL) {
Chris@17 290 $this->addToAssertionCount(1);
Chris@17 291 return new WebAssert($this->getSession($name), $this->baseUrl);
Chris@17 292 }
Chris@17 293
Chris@17 294 /**
Chris@17 295 * Retrieves a Drupal path or an absolute path.
Chris@17 296 *
Chris@17 297 * @param string|\Drupal\Core\Url $path
Chris@17 298 * Drupal path or URL to load into Mink controlled browser.
Chris@17 299 * @param array $options
Chris@17 300 * (optional) Options to be forwarded to the url generator.
Chris@17 301 * @param string[] $headers
Chris@17 302 * An array containing additional HTTP request headers, the array keys are
Chris@17 303 * the header names and the array values the header values. This is useful
Chris@17 304 * to set for example the "Accept-Language" header for requesting the page
Chris@17 305 * in a different language. Note that not all headers are supported, for
Chris@17 306 * example the "Accept" header is always overridden by the browser. For
Chris@17 307 * testing REST APIs it is recommended to obtain a separate HTTP client
Chris@17 308 * using getHttpClient() and performing requests that way.
Chris@17 309 *
Chris@17 310 * @return string
Chris@17 311 * The retrieved HTML string, also available as $this->getRawContent()
Chris@17 312 *
Chris@17 313 * @see \Drupal\Tests\BrowserTestBase::getHttpClient()
Chris@17 314 */
Chris@17 315 protected function drupalGet($path, array $options = [], array $headers = []) {
Chris@17 316 $options['absolute'] = TRUE;
Chris@17 317 $url = $this->buildUrl($path, $options);
Chris@17 318
Chris@17 319 $session = $this->getSession();
Chris@17 320
Chris@17 321 $this->prepareRequest();
Chris@17 322 foreach ($headers as $header_name => $header_value) {
Chris@17 323 $session->setRequestHeader($header_name, $header_value);
Chris@17 324 }
Chris@17 325
Chris@17 326 $session->visit($url);
Chris@17 327 $out = $session->getPage()->getContent();
Chris@17 328
Chris@17 329 // Ensure that any changes to variables in the other thread are picked up.
Chris@17 330 $this->refreshVariables();
Chris@17 331
Chris@17 332 // Replace original page output with new output from redirected page(s).
Chris@17 333 if ($new = $this->checkForMetaRefresh()) {
Chris@17 334 $out = $new;
Chris@17 335 // We are finished with all meta refresh redirects, so reset the counter.
Chris@17 336 $this->metaRefreshCount = 0;
Chris@17 337 }
Chris@17 338
Chris@17 339 // Log only for JavascriptTestBase tests because for Goutte we log with
Chris@17 340 // ::getResponseLogHandler.
Chris@17 341 if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
Chris@17 342 $html_output = 'GET request to: ' . $url .
Chris@17 343 '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
Chris@17 344 $html_output .= '<hr />' . $out;
Chris@17 345 $html_output .= $this->getHtmlOutputHeaders();
Chris@17 346 $this->htmlOutput($html_output);
Chris@17 347 }
Chris@17 348
Chris@17 349 return $out;
Chris@17 350 }
Chris@17 351
Chris@17 352 /**
Chris@17 353 * Builds an a absolute URL from a system path or a URL object.
Chris@17 354 *
Chris@17 355 * @param string|\Drupal\Core\Url $path
Chris@17 356 * A system path or a URL.
Chris@17 357 * @param array $options
Chris@17 358 * Options to be passed to Url::fromUri().
Chris@17 359 *
Chris@17 360 * @return string
Chris@17 361 * An absolute URL string.
Chris@17 362 */
Chris@17 363 protected function buildUrl($path, array $options = []) {
Chris@17 364 if ($path instanceof Url) {
Chris@17 365 $url_options = $path->getOptions();
Chris@17 366 $options = $url_options + $options;
Chris@17 367 $path->setOptions($options);
Chris@17 368 return $path->setAbsolute()->toString();
Chris@17 369 }
Chris@17 370 // The URL generator service is not necessarily available yet; e.g., in
Chris@17 371 // interactive installer tests.
Chris@17 372 elseif (\Drupal::hasService('url_generator')) {
Chris@17 373 $force_internal = isset($options['external']) && $options['external'] == FALSE;
Chris@17 374 if (!$force_internal && UrlHelper::isExternal($path)) {
Chris@17 375 return Url::fromUri($path, $options)->toString();
Chris@17 376 }
Chris@17 377 else {
Chris@17 378 $uri = $path === '<front>' ? 'base:/' : 'base:/' . $path;
Chris@17 379 // Path processing is needed for language prefixing. Skip it when a
Chris@17 380 // path that may look like an external URL is being used as internal.
Chris@17 381 $options['path_processing'] = !$force_internal;
Chris@17 382 return Url::fromUri($uri, $options)
Chris@17 383 ->setAbsolute()
Chris@17 384 ->toString();
Chris@17 385 }
Chris@17 386 }
Chris@17 387 else {
Chris@17 388 return $this->getAbsoluteUrl($path);
Chris@17 389 }
Chris@17 390 }
Chris@17 391
Chris@17 392 /**
Chris@17 393 * Takes a path and returns an absolute path.
Chris@17 394 *
Chris@17 395 * @param string $path
Chris@17 396 * A path from the Mink controlled browser content.
Chris@17 397 *
Chris@17 398 * @return string
Chris@17 399 * The $path with $base_url prepended, if necessary.
Chris@17 400 */
Chris@17 401 protected function getAbsoluteUrl($path) {
Chris@17 402 global $base_url, $base_path;
Chris@17 403
Chris@17 404 $parts = parse_url($path);
Chris@17 405 if (empty($parts['host'])) {
Chris@17 406 // Ensure that we have a string (and no xpath object).
Chris@17 407 $path = (string) $path;
Chris@17 408 // Strip $base_path, if existent.
Chris@17 409 $length = strlen($base_path);
Chris@17 410 if (substr($path, 0, $length) === $base_path) {
Chris@17 411 $path = substr($path, $length);
Chris@17 412 }
Chris@17 413 // Ensure that we have an absolute path.
Chris@17 414 if (empty($path) || $path[0] !== '/') {
Chris@17 415 $path = '/' . $path;
Chris@17 416 }
Chris@17 417 // Finally, prepend the $base_url.
Chris@17 418 $path = $base_url . $path;
Chris@17 419 }
Chris@17 420 return $path;
Chris@17 421 }
Chris@17 422
Chris@17 423 /**
Chris@17 424 * Prepare for a request to testing site.
Chris@17 425 *
Chris@17 426 * The testing site is protected via a SIMPLETEST_USER_AGENT cookie that is
Chris@17 427 * checked by drupal_valid_test_ua().
Chris@17 428 *
Chris@17 429 * @see drupal_valid_test_ua()
Chris@17 430 */
Chris@17 431 protected function prepareRequest() {
Chris@17 432 $session = $this->getSession();
Chris@17 433 $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
Chris@17 434 }
Chris@17 435
Chris@17 436 /**
Chris@17 437 * Returns whether a given user account is logged in.
Chris@17 438 *
Chris@17 439 * @param \Drupal\Core\Session\AccountInterface $account
Chris@17 440 * The user account object to check.
Chris@17 441 *
Chris@17 442 * @return bool
Chris@17 443 * Return TRUE if the user is logged in, FALSE otherwise.
Chris@17 444 */
Chris@17 445 protected function drupalUserIsLoggedIn(AccountInterface $account) {
Chris@17 446 $logged_in = FALSE;
Chris@17 447
Chris@17 448 if (isset($account->sessionId)) {
Chris@17 449 $session_handler = \Drupal::service('session_handler.storage');
Chris@17 450 $logged_in = (bool) $session_handler->read($account->sessionId);
Chris@17 451 }
Chris@17 452
Chris@17 453 return $logged_in;
Chris@17 454 }
Chris@17 455
Chris@17 456 /**
Chris@17 457 * Clicks the element with the given CSS selector.
Chris@17 458 *
Chris@17 459 * @param string $css_selector
Chris@17 460 * The CSS selector identifying the element to click.
Chris@17 461 */
Chris@17 462 protected function click($css_selector) {
Chris@17 463 $starting_url = $this->getSession()->getCurrentUrl();
Chris@17 464 $this->getSession()->getDriver()->click($this->cssSelectToXpath($css_selector));
Chris@17 465 // Log only for JavascriptTestBase tests because for Goutte we log with
Chris@17 466 // ::getResponseLogHandler.
Chris@17 467 if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
Chris@17 468 $out = $this->getSession()->getPage()->getContent();
Chris@17 469 $html_output =
Chris@17 470 'Clicked element with CSS selector: ' . $css_selector .
Chris@17 471 '<hr />Starting URL: ' . $starting_url .
Chris@17 472 '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
Chris@17 473 $html_output .= '<hr />' . $out;
Chris@17 474 $html_output .= $this->getHtmlOutputHeaders();
Chris@17 475 $this->htmlOutput($html_output);
Chris@17 476 }
Chris@17 477 }
Chris@17 478
Chris@17 479 /**
Chris@17 480 * Follows a link by complete name.
Chris@17 481 *
Chris@17 482 * Will click the first link found with this link text.
Chris@17 483 *
Chris@17 484 * If the link is discovered and clicked, the test passes. Fail otherwise.
Chris@17 485 *
Chris@17 486 * @param string|\Drupal\Component\Render\MarkupInterface $label
Chris@17 487 * Text between the anchor tags.
Chris@17 488 * @param int $index
Chris@17 489 * (optional) The index number for cases where multiple links have the same
Chris@17 490 * text. Defaults to 0.
Chris@17 491 */
Chris@17 492 protected function clickLink($label, $index = 0) {
Chris@17 493 $label = (string) $label;
Chris@17 494 $links = $this->getSession()->getPage()->findAll('named', ['link', $label]);
Chris@17 495 $this->assertArrayHasKey($index, $links, 'The link ' . $label . ' was not found on the page.');
Chris@17 496 $links[$index]->click();
Chris@17 497 }
Chris@17 498
Chris@17 499 /**
Chris@17 500 * Retrieves the plain-text content from the current page.
Chris@17 501 */
Chris@17 502 protected function getTextContent() {
Chris@17 503 return $this->getSession()->getPage()->getText();
Chris@17 504 }
Chris@17 505
Chris@17 506 /**
Chris@17 507 * Get the current URL from the browser.
Chris@17 508 *
Chris@17 509 * @return string
Chris@17 510 * The current URL.
Chris@17 511 */
Chris@17 512 protected function getUrl() {
Chris@17 513 return $this->getSession()->getCurrentUrl();
Chris@17 514 }
Chris@17 515
Chris@17 516 /**
Chris@17 517 * Checks for meta refresh tag and if found call drupalGet() recursively.
Chris@17 518 *
Chris@17 519 * This function looks for the http-equiv attribute to be set to "Refresh" and
Chris@17 520 * is case-insensitive.
Chris@17 521 *
Chris@17 522 * @return string|false
Chris@17 523 * Either the new page content or FALSE.
Chris@17 524 */
Chris@17 525 protected function checkForMetaRefresh() {
Chris@17 526 $refresh = $this->cssSelect('meta[http-equiv="Refresh"], meta[http-equiv="refresh"]');
Chris@17 527 if (!empty($refresh) && (!isset($this->maximumMetaRefreshCount) || $this->metaRefreshCount < $this->maximumMetaRefreshCount)) {
Chris@17 528 // Parse the content attribute of the meta tag for the format:
Chris@17 529 // "[delay]: URL=[page_to_redirect_to]".
Chris@17 530 if (preg_match('/\d+;\s*URL=(?<url>.*)/i', $refresh[0]->getAttribute('content'), $match)) {
Chris@17 531 $this->metaRefreshCount++;
Chris@17 532 return $this->drupalGet($this->getAbsoluteUrl(Html::decodeEntities($match['url'])));
Chris@17 533 }
Chris@17 534 }
Chris@17 535 return FALSE;
Chris@17 536 }
Chris@17 537
Chris@17 538 /**
Chris@17 539 * Searches elements using a CSS selector in the raw content.
Chris@17 540 *
Chris@17 541 * The search is relative to the root element (HTML tag normally) of the page.
Chris@17 542 *
Chris@17 543 * @param string $selector
Chris@17 544 * CSS selector to use in the search.
Chris@17 545 *
Chris@17 546 * @return \Behat\Mink\Element\NodeElement[]
Chris@17 547 * The list of elements on the page that match the selector.
Chris@17 548 */
Chris@17 549 protected function cssSelect($selector) {
Chris@17 550 return $this->getSession()->getPage()->findAll('css', $selector);
Chris@17 551 }
Chris@17 552
Chris@17 553 }