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 }
|