annotate core/tests/Drupal/Tests/UiHelperTrait.php @ 4:a9cd425dd02b

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