Mercurial > hg > cmmr2012-drupal-site
comparison core/tests/Drupal/Tests/BrowserTestBase.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Tests; | |
4 | |
5 use Behat\Mink\Driver\GoutteDriver; | |
6 use Behat\Mink\Element\Element; | |
7 use Behat\Mink\Mink; | |
8 use Behat\Mink\Selector\SelectorsHandler; | |
9 use Behat\Mink\Session; | |
10 use Drupal\Component\Render\FormattableMarkup; | |
11 use Drupal\Component\Serialization\Json; | |
12 use Drupal\Component\Utility\Html; | |
13 use Drupal\Component\Utility\UrlHelper; | |
14 use Drupal\Core\Database\Database; | |
15 use Drupal\Core\Session\AccountInterface; | |
16 use Drupal\Core\Session\AnonymousUserSession; | |
17 use Drupal\Core\Test\FunctionalTestSetupTrait; | |
18 use Drupal\Core\Test\TestSetupTrait; | |
19 use Drupal\Core\Url; | |
20 use Drupal\Core\Utility\Error; | |
21 use Drupal\FunctionalTests\AssertLegacyTrait; | |
22 use Drupal\Tests\block\Traits\BlockCreationTrait; | |
23 use Drupal\Tests\node\Traits\ContentTypeCreationTrait; | |
24 use Drupal\Tests\node\Traits\NodeCreationTrait; | |
25 use Drupal\Tests\user\Traits\UserCreationTrait; | |
26 use PHPUnit\Framework\TestCase; | |
27 use Psr\Http\Message\RequestInterface; | |
28 use Psr\Http\Message\ResponseInterface; | |
29 use Symfony\Component\CssSelector\CssSelectorConverter; | |
30 | |
31 /** | |
32 * Provides a test case for functional Drupal tests. | |
33 * | |
34 * Tests extending BrowserTestBase must exist in the | |
35 * Drupal\Tests\yourmodule\Functional namespace and live in the | |
36 * modules/yourmodule/tests/src/Functional directory. | |
37 * | |
38 * Tests extending this base class should only translate text when testing | |
39 * translation functionality. For example, avoid wrapping test text with t() | |
40 * or TranslatableMarkup(). | |
41 * | |
42 * @ingroup testing | |
43 */ | |
44 abstract class BrowserTestBase extends TestCase { | |
45 | |
46 use FunctionalTestSetupTrait; | |
47 use TestSetupTrait; | |
48 use AssertHelperTrait; | |
49 use BlockCreationTrait { | |
50 placeBlock as drupalPlaceBlock; | |
51 } | |
52 use AssertLegacyTrait; | |
53 use RandomGeneratorTrait; | |
54 use SessionTestTrait; | |
55 use NodeCreationTrait { | |
56 getNodeByTitle as drupalGetNodeByTitle; | |
57 createNode as drupalCreateNode; | |
58 } | |
59 use ContentTypeCreationTrait { | |
60 createContentType as drupalCreateContentType; | |
61 } | |
62 use ConfigTestTrait; | |
63 use TestRequirementsTrait; | |
64 use UserCreationTrait { | |
65 createRole as drupalCreateRole; | |
66 createUser as drupalCreateUser; | |
67 } | |
68 use XdebugRequestTrait; | |
69 use PhpunitCompatibilityTrait; | |
70 | |
71 /** | |
72 * The database prefix of this test run. | |
73 * | |
74 * @var string | |
75 */ | |
76 protected $databasePrefix; | |
77 | |
78 /** | |
79 * Time limit in seconds for the test. | |
80 * | |
81 * @var int | |
82 */ | |
83 protected $timeLimit = 500; | |
84 | |
85 /** | |
86 * The translation file directory for the test environment. | |
87 * | |
88 * This is set in BrowserTestBase::prepareEnvironment(). | |
89 * | |
90 * @var string | |
91 */ | |
92 protected $translationFilesDirectory; | |
93 | |
94 /** | |
95 * The config importer that can be used in a test. | |
96 * | |
97 * @var \Drupal\Core\Config\ConfigImporter | |
98 */ | |
99 protected $configImporter; | |
100 | |
101 /** | |
102 * Modules to enable. | |
103 * | |
104 * The test runner will merge the $modules lists from this class, the class | |
105 * it extends, and so on up the class hierarchy. It is not necessary to | |
106 * include modules in your list that a parent class has already declared. | |
107 * | |
108 * @var string[] | |
109 * | |
110 * @see \Drupal\Tests\BrowserTestBase::installDrupal() | |
111 */ | |
112 protected static $modules = []; | |
113 | |
114 /** | |
115 * The profile to install as a basis for testing. | |
116 * | |
117 * @var string | |
118 */ | |
119 protected $profile = 'testing'; | |
120 | |
121 /** | |
122 * The current user logged in using the Mink controlled browser. | |
123 * | |
124 * @var \Drupal\user\UserInterface | |
125 */ | |
126 protected $loggedInUser = FALSE; | |
127 | |
128 /** | |
129 * An array of custom translations suitable for drupal_rewrite_settings(). | |
130 * | |
131 * @var array | |
132 */ | |
133 protected $customTranslations; | |
134 | |
135 /* | |
136 * Mink class for the default driver to use. | |
137 * | |
138 * Shoud be a fully qualified class name that implements | |
139 * Behat\Mink\Driver\DriverInterface. | |
140 * | |
141 * Value can be overridden using the environment variable MINK_DRIVER_CLASS. | |
142 * | |
143 * @var string. | |
144 */ | |
145 protected $minkDefaultDriverClass = GoutteDriver::class; | |
146 | |
147 /* | |
148 * Mink default driver params. | |
149 * | |
150 * If it's an array its contents are used as constructor params when default | |
151 * Mink driver class is instantiated. | |
152 * | |
153 * Can be overridden using the environment variable MINK_DRIVER_ARGS. In this | |
154 * case that variable should be a JSON array, for example: | |
155 * '["firefox", null, "http://localhost:4444/wd/hub"]'. | |
156 * | |
157 * | |
158 * @var array | |
159 */ | |
160 protected $minkDefaultDriverArgs; | |
161 | |
162 /** | |
163 * Mink session manager. | |
164 * | |
165 * This will not be initialized if there was an error during the test setup. | |
166 * | |
167 * @var \Behat\Mink\Mink|null | |
168 */ | |
169 protected $mink; | |
170 | |
171 /** | |
172 * {@inheritdoc} | |
173 * | |
174 * Browser tests are run in separate processes to prevent collisions between | |
175 * code that may be loaded by tests. | |
176 */ | |
177 protected $runTestInSeparateProcess = TRUE; | |
178 | |
179 /** | |
180 * {@inheritdoc} | |
181 */ | |
182 protected $preserveGlobalState = FALSE; | |
183 | |
184 /** | |
185 * Class name for HTML output logging. | |
186 * | |
187 * @var string | |
188 */ | |
189 protected $htmlOutputClassName; | |
190 | |
191 /** | |
192 * Directory name for HTML output logging. | |
193 * | |
194 * @var string | |
195 */ | |
196 protected $htmlOutputDirectory; | |
197 | |
198 /** | |
199 * Counter storage for HTML output logging. | |
200 * | |
201 * @var string | |
202 */ | |
203 protected $htmlOutputCounterStorage; | |
204 | |
205 /** | |
206 * Counter for HTML output logging. | |
207 * | |
208 * @var int | |
209 */ | |
210 protected $htmlOutputCounter = 1; | |
211 | |
212 /** | |
213 * HTML output output enabled. | |
214 * | |
215 * @var bool | |
216 */ | |
217 protected $htmlOutputEnabled = FALSE; | |
218 | |
219 /** | |
220 * The file name to write the list of URLs to. | |
221 * | |
222 * This file is read by the PHPUnit result printer. | |
223 * | |
224 * @var string | |
225 * | |
226 * @see \Drupal\Tests\Listeners\HtmlOutputPrinter | |
227 */ | |
228 protected $htmlOutputFile; | |
229 | |
230 /** | |
231 * HTML output test ID. | |
232 * | |
233 * @var int | |
234 */ | |
235 protected $htmlOutputTestId; | |
236 | |
237 /** | |
238 * The base URL. | |
239 * | |
240 * @var string | |
241 */ | |
242 protected $baseUrl; | |
243 | |
244 /** | |
245 * The original array of shutdown function callbacks. | |
246 * | |
247 * @var array | |
248 */ | |
249 protected $originalShutdownCallbacks = []; | |
250 | |
251 /** | |
252 * The number of meta refresh redirects to follow, or NULL if unlimited. | |
253 * | |
254 * @var null|int | |
255 */ | |
256 protected $maximumMetaRefreshCount = NULL; | |
257 | |
258 /** | |
259 * The number of meta refresh redirects followed during ::drupalGet(). | |
260 * | |
261 * @var int | |
262 */ | |
263 protected $metaRefreshCount = 0; | |
264 | |
265 /** | |
266 * The app root. | |
267 * | |
268 * @var string | |
269 */ | |
270 protected $root; | |
271 | |
272 /** | |
273 * The original container. | |
274 * | |
275 * Move this to \Drupal\Core\Test\FunctionalTestSetupTrait once TestBase no | |
276 * longer provides the same value. | |
277 * | |
278 * @var \Symfony\Component\DependencyInjection\ContainerInterface | |
279 */ | |
280 protected $originalContainer; | |
281 | |
282 /** | |
283 * {@inheritdoc} | |
284 */ | |
285 public function __construct($name = NULL, array $data = [], $dataName = '') { | |
286 parent::__construct($name, $data, $dataName); | |
287 | |
288 $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); | |
289 } | |
290 | |
291 /** | |
292 * Initializes Mink sessions. | |
293 */ | |
294 protected function initMink() { | |
295 $driver = $this->getDefaultDriverInstance(); | |
296 | |
297 if ($driver instanceof GoutteDriver) { | |
298 // Turn off curl timeout. Having a timeout is not a problem in a normal | |
299 // test running, but it is a problem when debugging. Also, disable SSL | |
300 // peer verification so that testing under HTTPS always works. | |
301 /** @var \GuzzleHttp\Client $client */ | |
302 $client = $this->container->get('http_client_factory')->fromOptions([ | |
303 'timeout' => NULL, | |
304 'verify' => FALSE, | |
305 ]); | |
306 | |
307 // Inject a Guzzle middleware to generate debug output for every request | |
308 // performed in the test. | |
309 $handler_stack = $client->getConfig('handler'); | |
310 $handler_stack->push($this->getResponseLogHandler()); | |
311 | |
312 $driver->getClient()->setClient($client); | |
313 } | |
314 | |
315 $selectors_handler = new SelectorsHandler([ | |
316 'hidden_field_selector' => new HiddenFieldSelector() | |
317 ]); | |
318 $session = new Session($driver, $selectors_handler); | |
319 $this->mink = new Mink(); | |
320 $this->mink->registerSession('default', $session); | |
321 $this->mink->setDefaultSessionName('default'); | |
322 $this->registerSessions(); | |
323 | |
324 $this->initFrontPage(); | |
325 | |
326 return $session; | |
327 } | |
328 | |
329 /** | |
330 * Visits the front page when initializing Mink. | |
331 * | |
332 * According to the W3C WebDriver specification a cookie can only be set if | |
333 * the cookie domain is equal to the domain of the active document. When the | |
334 * browser starts up the active document is not our domain but 'about:blank' | |
335 * or similar. To be able to set our User-Agent and Xdebug cookies at the | |
336 * start of the test we now do a request to the front page so the active | |
337 * document matches the domain. | |
338 * | |
339 * @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie | |
340 * @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975 | |
341 */ | |
342 protected function initFrontPage() { | |
343 $session = $this->getSession(); | |
344 $session->visit($this->baseUrl); | |
345 } | |
346 | |
347 /** | |
348 * Gets an instance of the default Mink driver. | |
349 * | |
350 * @return Behat\Mink\Driver\DriverInterface | |
351 * Instance of default Mink driver. | |
352 * | |
353 * @throws \InvalidArgumentException | |
354 * When provided default Mink driver class can't be instantiated. | |
355 */ | |
356 protected function getDefaultDriverInstance() { | |
357 // Get default driver params from environment if available. | |
358 if ($arg_json = $this->getMinkDriverArgs()) { | |
359 $this->minkDefaultDriverArgs = json_decode($arg_json, TRUE); | |
360 } | |
361 | |
362 // Get and check default driver class from environment if available. | |
363 if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) { | |
364 if (class_exists($minkDriverClass)) { | |
365 $this->minkDefaultDriverClass = $minkDriverClass; | |
366 } | |
367 else { | |
368 throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class."); | |
369 } | |
370 } | |
371 | |
372 if (is_array($this->minkDefaultDriverArgs)) { | |
373 // Use ReflectionClass to instantiate class with received params. | |
374 $reflector = new \ReflectionClass($this->minkDefaultDriverClass); | |
375 $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs); | |
376 } | |
377 else { | |
378 $driver = new $this->minkDefaultDriverClass(); | |
379 } | |
380 return $driver; | |
381 } | |
382 | |
383 /** | |
384 * Creates the directory to store browser output. | |
385 * | |
386 * Creates the directory to store browser output in if a file to write | |
387 * URLs to has been created by \Drupal\Tests\Listeners\HtmlOutputPrinter. | |
388 */ | |
389 protected function initBrowserOutputFile() { | |
390 $browser_output_file = getenv('BROWSERTEST_OUTPUT_FILE'); | |
391 $this->htmlOutputEnabled = is_file($browser_output_file); | |
392 if ($this->htmlOutputEnabled) { | |
393 $this->htmlOutputFile = $browser_output_file; | |
394 $this->htmlOutputClassName = str_replace("\\", "_", get_called_class()); | |
395 $this->htmlOutputDirectory = DRUPAL_ROOT . '/sites/simpletest/browser_output'; | |
396 if (file_prepare_directory($this->htmlOutputDirectory, FILE_CREATE_DIRECTORY) && !file_exists($this->htmlOutputDirectory . '/.htaccess')) { | |
397 file_put_contents($this->htmlOutputDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n"); | |
398 } | |
399 $this->htmlOutputCounterStorage = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '.counter'; | |
400 $this->htmlOutputTestId = str_replace('sites/simpletest/', '', $this->siteDirectory); | |
401 if (is_file($this->htmlOutputCounterStorage)) { | |
402 $this->htmlOutputCounter = max(1, (int) file_get_contents($this->htmlOutputCounterStorage)) + 1; | |
403 } | |
404 } | |
405 } | |
406 | |
407 /** | |
408 * Get the Mink driver args from an environment variable, if it is set. Can | |
409 * be overridden in a derived class so it is possible to use a different | |
410 * value for a subset of tests, e.g. the JavaScript tests. | |
411 * | |
412 * @return string|false | |
413 * The JSON-encoded argument string. False if it is not set. | |
414 */ | |
415 protected function getMinkDriverArgs() { | |
416 return getenv('MINK_DRIVER_ARGS'); | |
417 } | |
418 | |
419 /** | |
420 * Provides a Guzzle middleware handler to log every response received. | |
421 * | |
422 * @return callable | |
423 * The callable handler that will do the logging. | |
424 */ | |
425 protected function getResponseLogHandler() { | |
426 return function (callable $handler) { | |
427 return function (RequestInterface $request, array $options) use ($handler) { | |
428 return $handler($request, $options) | |
429 ->then(function (ResponseInterface $response) use ($request) { | |
430 if ($this->htmlOutputEnabled) { | |
431 | |
432 $caller = $this->getTestMethodCaller(); | |
433 $html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line']; | |
434 $html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri(); | |
435 | |
436 // On redirect responses (status code starting with '3') we need | |
437 // to remove the meta tag that would do a browser refresh. We | |
438 // don't want to redirect developers away when they look at the | |
439 // debug output file in their browser. | |
440 $body = $response->getBody(); | |
441 $status_code = (string) $response->getStatusCode(); | |
442 if ($status_code[0] === '3') { | |
443 $body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1); | |
444 } | |
445 $html_output .= '<hr />' . $body; | |
446 $html_output .= $this->formatHtmlOutputHeaders($response->getHeaders()); | |
447 | |
448 $this->htmlOutput($html_output); | |
449 } | |
450 return $response; | |
451 }); | |
452 }; | |
453 }; | |
454 } | |
455 | |
456 /** | |
457 * Registers additional Mink sessions. | |
458 * | |
459 * Tests wishing to use a different driver or change the default driver should | |
460 * override this method. | |
461 * | |
462 * @code | |
463 * // Register a new session that uses the MinkPonyDriver. | |
464 * $pony = new MinkPonyDriver(); | |
465 * $session = new Session($pony); | |
466 * $this->mink->registerSession('pony', $session); | |
467 * @endcode | |
468 */ | |
469 protected function registerSessions() {} | |
470 | |
471 /** | |
472 * {@inheritdoc} | |
473 */ | |
474 protected function setUp() { | |
475 // Installing Drupal creates 1000s of objects. Garbage collection of these | |
476 // objects is expensive. This appears to be causing random segmentation | |
477 // faults in PHP 5.x due to https://bugs.php.net/bug.php?id=72286. Once | |
478 // Drupal is installed is rebuilt, garbage collection is re-enabled. | |
479 $disable_gc = version_compare(PHP_VERSION, '7', '<') && gc_enabled(); | |
480 if ($disable_gc) { | |
481 gc_collect_cycles(); | |
482 gc_disable(); | |
483 } | |
484 parent::setUp(); | |
485 | |
486 $this->setupBaseUrl(); | |
487 | |
488 // Install Drupal test site. | |
489 $this->prepareEnvironment(); | |
490 $this->installDrupal(); | |
491 | |
492 // Setup Mink. | |
493 $session = $this->initMink(); | |
494 | |
495 $cookies = $this->extractCookiesFromRequest(\Drupal::request()); | |
496 foreach ($cookies as $cookie_name => $values) { | |
497 foreach ($values as $value) { | |
498 $session->setCookie($cookie_name, $value); | |
499 } | |
500 } | |
501 | |
502 // Set up the browser test output file. | |
503 $this->initBrowserOutputFile(); | |
504 // If garbage collection was disabled prior to rebuilding container, | |
505 // re-enable it. | |
506 if ($disable_gc) { | |
507 gc_enable(); | |
508 } | |
509 | |
510 // Ensure that the test is not marked as risky because of no assertions. In | |
511 // PHPUnit 6 tests that only make assertions using $this->assertSession() | |
512 // can be marked as risky. | |
513 $this->addToAssertionCount(1); | |
514 } | |
515 | |
516 /** | |
517 * Ensures test files are deletable within file_unmanaged_delete_recursive(). | |
518 * | |
519 * Some tests chmod generated files to be read only. During | |
520 * BrowserTestBase::cleanupEnvironment() and other cleanup operations, | |
521 * these files need to get deleted too. | |
522 * | |
523 * @param string $path | |
524 * The file path. | |
525 */ | |
526 public static function filePreDeleteCallback($path) { | |
527 // When the webserver runs with the same system user as phpunit, we can | |
528 // make read-only files writable again. If not, chmod will fail while the | |
529 // file deletion still works if file permissions have been configured | |
530 // correctly. Thus, we ignore any problems while running chmod. | |
531 @chmod($path, 0700); | |
532 } | |
533 | |
534 /** | |
535 * Clean up the Simpletest environment. | |
536 */ | |
537 protected function cleanupEnvironment() { | |
538 // Remove all prefixed tables. | |
539 $original_connection_info = Database::getConnectionInfo('simpletest_original_default'); | |
540 $original_prefix = $original_connection_info['default']['prefix']['default']; | |
541 $test_connection_info = Database::getConnectionInfo('default'); | |
542 $test_prefix = $test_connection_info['default']['prefix']['default']; | |
543 if ($original_prefix != $test_prefix) { | |
544 $tables = Database::getConnection()->schema()->findTables('%'); | |
545 foreach ($tables as $table) { | |
546 if (Database::getConnection()->schema()->dropTable($table)) { | |
547 unset($tables[$table]); | |
548 } | |
549 } | |
550 } | |
551 | |
552 // Delete test site directory. | |
553 file_unmanaged_delete_recursive($this->siteDirectory, [$this, 'filePreDeleteCallback']); | |
554 } | |
555 | |
556 /** | |
557 * {@inheritdoc} | |
558 */ | |
559 protected function tearDown() { | |
560 parent::tearDown(); | |
561 | |
562 // Destroy the testing kernel. | |
563 if (isset($this->kernel)) { | |
564 $this->cleanupEnvironment(); | |
565 $this->kernel->shutdown(); | |
566 } | |
567 | |
568 // Ensure that internal logged in variable is reset. | |
569 $this->loggedInUser = FALSE; | |
570 | |
571 if ($this->mink) { | |
572 $this->mink->stopSessions(); | |
573 } | |
574 | |
575 // Restore original shutdown callbacks. | |
576 if (function_exists('drupal_register_shutdown_function')) { | |
577 $callbacks = &drupal_register_shutdown_function(); | |
578 $callbacks = $this->originalShutdownCallbacks; | |
579 } | |
580 } | |
581 | |
582 /** | |
583 * Returns Mink session. | |
584 * | |
585 * @param string $name | |
586 * (optional) Name of the session. Defaults to the active session. | |
587 * | |
588 * @return \Behat\Mink\Session | |
589 * The active Mink session object. | |
590 */ | |
591 public function getSession($name = NULL) { | |
592 return $this->mink->getSession($name); | |
593 } | |
594 | |
595 /** | |
596 * Obtain the HTTP client for the system under test. | |
597 * | |
598 * Use this method for arbitrary HTTP requests to the site under test. For | |
599 * most tests, you should not get the HTTP client and instead use navigation | |
600 * methods such as drupalGet() and clickLink() in order to benefit from | |
601 * assertions. | |
602 * | |
603 * Subclasses which substitute a different Mink driver should override this | |
604 * method and provide a Guzzle client if the Mink driver provides one. | |
605 * | |
606 * @return \GuzzleHttp\ClientInterface | |
607 * The client with BrowserTestBase configuration. | |
608 * | |
609 * @throws \RuntimeException | |
610 * If the Mink driver does not support a Guzzle HTTP client, throw an | |
611 * exception. | |
612 */ | |
613 protected function getHttpClient() { | |
614 /* @var $mink_driver \Behat\Mink\Driver\DriverInterface */ | |
615 $mink_driver = $this->getSession()->getDriver(); | |
616 if ($mink_driver instanceof GoutteDriver) { | |
617 return $mink_driver->getClient()->getClient(); | |
618 } | |
619 throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().'); | |
620 } | |
621 | |
622 /** | |
623 * Returns WebAssert object. | |
624 * | |
625 * @param string $name | |
626 * (optional) Name of the session. Defaults to the active session. | |
627 * | |
628 * @return \Drupal\Tests\WebAssert | |
629 * A new web-assert option for asserting the presence of elements with. | |
630 */ | |
631 public function assertSession($name = NULL) { | |
632 $this->addToAssertionCount(1); | |
633 return new WebAssert($this->getSession($name), $this->baseUrl); | |
634 } | |
635 | |
636 /** | |
637 * Prepare for a request to testing site. | |
638 * | |
639 * The testing site is protected via a SIMPLETEST_USER_AGENT cookie that is | |
640 * checked by drupal_valid_test_ua(). | |
641 * | |
642 * @see drupal_valid_test_ua() | |
643 */ | |
644 protected function prepareRequest() { | |
645 $session = $this->getSession(); | |
646 $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix)); | |
647 } | |
648 | |
649 /** | |
650 * Builds an a absolute URL from a system path or a URL object. | |
651 * | |
652 * @param string|\Drupal\Core\Url $path | |
653 * A system path or a URL. | |
654 * @param array $options | |
655 * Options to be passed to Url::fromUri(). | |
656 * | |
657 * @return string | |
658 * An absolute URL stsring. | |
659 */ | |
660 protected function buildUrl($path, array $options = []) { | |
661 if ($path instanceof Url) { | |
662 $url_options = $path->getOptions(); | |
663 $options = $url_options + $options; | |
664 $path->setOptions($options); | |
665 return $path->setAbsolute()->toString(); | |
666 } | |
667 // The URL generator service is not necessarily available yet; e.g., in | |
668 // interactive installer tests. | |
669 elseif ($this->container->has('url_generator')) { | |
670 $force_internal = isset($options['external']) && $options['external'] == FALSE; | |
671 if (!$force_internal && UrlHelper::isExternal($path)) { | |
672 return Url::fromUri($path, $options)->toString(); | |
673 } | |
674 else { | |
675 $uri = $path === '<front>' ? 'base:/' : 'base:/' . $path; | |
676 // Path processing is needed for language prefixing. Skip it when a | |
677 // path that may look like an external URL is being used as internal. | |
678 $options['path_processing'] = !$force_internal; | |
679 return Url::fromUri($uri, $options) | |
680 ->setAbsolute() | |
681 ->toString(); | |
682 } | |
683 } | |
684 else { | |
685 return $this->getAbsoluteUrl($path); | |
686 } | |
687 } | |
688 | |
689 /** | |
690 * Retrieves a Drupal path or an absolute path. | |
691 * | |
692 * @param string|\Drupal\Core\Url $path | |
693 * Drupal path or URL to load into Mink controlled browser. | |
694 * @param array $options | |
695 * (optional) Options to be forwarded to the url generator. | |
696 * @param string[] $headers | |
697 * An array containing additional HTTP request headers, the array keys are | |
698 * the header names and the array values the header values. This is useful | |
699 * to set for example the "Accept-Language" header for requesting the page | |
700 * in a different language. Note that not all headers are supported, for | |
701 * example the "Accept" header is always overridden by the browser. For | |
702 * testing REST APIs it is recommended to obtain a separate HTTP client | |
703 * using getHttpClient() and performing requests that way. | |
704 * | |
705 * @return string | |
706 * The retrieved HTML string, also available as $this->getRawContent() | |
707 * | |
708 * @see \Drupal\Tests\BrowserTestBase::getHttpClient() | |
709 */ | |
710 protected function drupalGet($path, array $options = [], array $headers = []) { | |
711 $options['absolute'] = TRUE; | |
712 $url = $this->buildUrl($path, $options); | |
713 | |
714 $session = $this->getSession(); | |
715 | |
716 $this->prepareRequest(); | |
717 foreach ($headers as $header_name => $header_value) { | |
718 $session->setRequestHeader($header_name, $header_value); | |
719 } | |
720 | |
721 $session->visit($url); | |
722 $out = $session->getPage()->getContent(); | |
723 | |
724 // Ensure that any changes to variables in the other thread are picked up. | |
725 $this->refreshVariables(); | |
726 | |
727 // Replace original page output with new output from redirected page(s). | |
728 if ($new = $this->checkForMetaRefresh()) { | |
729 $out = $new; | |
730 // We are finished with all meta refresh redirects, so reset the counter. | |
731 $this->metaRefreshCount = 0; | |
732 } | |
733 | |
734 // Log only for JavascriptTestBase tests because for Goutte we log with | |
735 // ::getResponseLogHandler. | |
736 if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) { | |
737 $html_output = 'GET request to: ' . $url . | |
738 '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl(); | |
739 $html_output .= '<hr />' . $out; | |
740 $html_output .= $this->getHtmlOutputHeaders(); | |
741 $this->htmlOutput($html_output); | |
742 } | |
743 | |
744 return $out; | |
745 } | |
746 | |
747 /** | |
748 * Takes a path and returns an absolute path. | |
749 * | |
750 * @param string $path | |
751 * A path from the Mink controlled browser content. | |
752 * | |
753 * @return string | |
754 * The $path with $base_url prepended, if necessary. | |
755 */ | |
756 protected function getAbsoluteUrl($path) { | |
757 global $base_url, $base_path; | |
758 | |
759 $parts = parse_url($path); | |
760 if (empty($parts['host'])) { | |
761 // Ensure that we have a string (and no xpath object). | |
762 $path = (string) $path; | |
763 // Strip $base_path, if existent. | |
764 $length = strlen($base_path); | |
765 if (substr($path, 0, $length) === $base_path) { | |
766 $path = substr($path, $length); | |
767 } | |
768 // Ensure that we have an absolute path. | |
769 if (empty($path) || $path[0] !== '/') { | |
770 $path = '/' . $path; | |
771 } | |
772 // Finally, prepend the $base_url. | |
773 $path = $base_url . $path; | |
774 } | |
775 return $path; | |
776 } | |
777 | |
778 /** | |
779 * Logs in a user using the Mink controlled browser. | |
780 * | |
781 * If a user is already logged in, then the current user is logged out before | |
782 * logging in the specified user. | |
783 * | |
784 * Please note that neither the current user nor the passed-in user object is | |
785 * populated with data of the logged in user. If you need full access to the | |
786 * user object after logging in, it must be updated manually. If you also need | |
787 * access to the plain-text password of the user (set by drupalCreateUser()), | |
788 * e.g. to log in the same user again, then it must be re-assigned manually. | |
789 * For example: | |
790 * @code | |
791 * // Create a user. | |
792 * $account = $this->drupalCreateUser(array()); | |
793 * $this->drupalLogin($account); | |
794 * // Load real user object. | |
795 * $pass_raw = $account->passRaw; | |
796 * $account = User::load($account->id()); | |
797 * $account->passRaw = $pass_raw; | |
798 * @endcode | |
799 * | |
800 * @param \Drupal\Core\Session\AccountInterface $account | |
801 * User object representing the user to log in. | |
802 * | |
803 * @see drupalCreateUser() | |
804 */ | |
805 protected function drupalLogin(AccountInterface $account) { | |
806 if ($this->loggedInUser) { | |
807 $this->drupalLogout(); | |
808 } | |
809 | |
810 $this->drupalGet('user/login'); | |
811 $this->submitForm([ | |
812 'name' => $account->getUsername(), | |
813 'pass' => $account->passRaw, | |
814 ], t('Log in')); | |
815 | |
816 // @see BrowserTestBase::drupalUserIsLoggedIn() | |
817 $account->sessionId = $this->getSession()->getCookie($this->getSessionName()); | |
818 $this->assertTrue($this->drupalUserIsLoggedIn($account), new FormattableMarkup('User %name successfully logged in.', ['%name' => $account->getAccountName()])); | |
819 | |
820 $this->loggedInUser = $account; | |
821 $this->container->get('current_user')->setAccount($account); | |
822 } | |
823 | |
824 /** | |
825 * Logs a user out of the Mink controlled browser and confirms. | |
826 * | |
827 * Confirms logout by checking the login page. | |
828 */ | |
829 protected function drupalLogout() { | |
830 // Make a request to the logout page, and redirect to the user page, the | |
831 // idea being if you were properly logged out you should be seeing a login | |
832 // screen. | |
833 $assert_session = $this->assertSession(); | |
834 $this->drupalGet('user/logout', ['query' => ['destination' => 'user']]); | |
835 $assert_session->fieldExists('name'); | |
836 $assert_session->fieldExists('pass'); | |
837 | |
838 // @see BrowserTestBase::drupalUserIsLoggedIn() | |
839 unset($this->loggedInUser->sessionId); | |
840 $this->loggedInUser = FALSE; | |
841 $this->container->get('current_user')->setAccount(new AnonymousUserSession()); | |
842 } | |
843 | |
844 /** | |
845 * Fills and submits a form. | |
846 * | |
847 * @param array $edit | |
848 * Field data in an associative array. Changes the current input fields | |
849 * (where possible) to the values indicated. | |
850 * | |
851 * A checkbox can be set to TRUE to be checked and should be set to FALSE to | |
852 * be unchecked. | |
853 * @param string $submit | |
854 * Value of the submit button whose click is to be emulated. For example, | |
855 * 'Save'. The processing of the request depends on this value. For example, | |
856 * a form may have one button with the value 'Save' and another button with | |
857 * the value 'Delete', and execute different code depending on which one is | |
858 * clicked. | |
859 * @param string $form_html_id | |
860 * (optional) HTML ID of the form to be submitted. On some pages | |
861 * there are many identical forms, so just using the value of the submit | |
862 * button is not enough. For example: 'trigger-node-presave-assign-form'. | |
863 * Note that this is not the Drupal $form_id, but rather the HTML ID of the | |
864 * form, which is typically the same thing but with hyphens replacing the | |
865 * underscores. | |
866 */ | |
867 protected function submitForm(array $edit, $submit, $form_html_id = NULL) { | |
868 $assert_session = $this->assertSession(); | |
869 | |
870 // Get the form. | |
871 if (isset($form_html_id)) { | |
872 $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']"); | |
873 $submit_button = $assert_session->buttonExists($submit, $form); | |
874 $action = $form->getAttribute('action'); | |
875 } | |
876 else { | |
877 $submit_button = $assert_session->buttonExists($submit); | |
878 $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button); | |
879 $action = $form->getAttribute('action'); | |
880 } | |
881 | |
882 // Edit the form values. | |
883 foreach ($edit as $name => $value) { | |
884 $field = $assert_session->fieldExists($name, $form); | |
885 | |
886 // Provide support for the values '1' and '0' for checkboxes instead of | |
887 // TRUE and FALSE. | |
888 // @todo Get rid of supporting 1/0 by converting all tests cases using | |
889 // this to boolean values. | |
890 $field_type = $field->getAttribute('type'); | |
891 if ($field_type === 'checkbox') { | |
892 $value = (bool) $value; | |
893 } | |
894 | |
895 $field->setValue($value); | |
896 } | |
897 | |
898 // Submit form. | |
899 $this->prepareRequest(); | |
900 $submit_button->press(); | |
901 | |
902 // Ensure that any changes to variables in the other thread are picked up. | |
903 $this->refreshVariables(); | |
904 | |
905 // Check if there are any meta refresh redirects (like Batch API pages). | |
906 if ($this->checkForMetaRefresh()) { | |
907 // We are finished with all meta refresh redirects, so reset the counter. | |
908 $this->metaRefreshCount = 0; | |
909 } | |
910 | |
911 // Log only for JavascriptTestBase tests because for Goutte we log with | |
912 // ::getResponseLogHandler. | |
913 if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) { | |
914 $out = $this->getSession()->getPage()->getContent(); | |
915 $html_output = 'POST request to: ' . $action . | |
916 '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl(); | |
917 $html_output .= '<hr />' . $out; | |
918 $html_output .= $this->getHtmlOutputHeaders(); | |
919 $this->htmlOutput($html_output); | |
920 } | |
921 | |
922 } | |
923 | |
924 /** | |
925 * Executes a form submission. | |
926 * | |
927 * It will be done as usual POST request with Mink. | |
928 * | |
929 * @param \Drupal\Core\Url|string $path | |
930 * Location of the post form. Either a Drupal path or an absolute path or | |
931 * NULL to post to the current page. For multi-stage forms you can set the | |
932 * path to NULL and have it post to the last received page. Example: | |
933 * | |
934 * @code | |
935 * // First step in form. | |
936 * $edit = array(...); | |
937 * $this->drupalPostForm('some_url', $edit, 'Save'); | |
938 * | |
939 * // Second step in form. | |
940 * $edit = array(...); | |
941 * $this->drupalPostForm(NULL, $edit, 'Save'); | |
942 * @endcode | |
943 * @param array $edit | |
944 * Field data in an associative array. Changes the current input fields | |
945 * (where possible) to the values indicated. | |
946 * | |
947 * When working with form tests, the keys for an $edit element should match | |
948 * the 'name' parameter of the HTML of the form. For example, the 'body' | |
949 * field for a node has the following HTML: | |
950 * @code | |
951 * <textarea id="edit-body-und-0-value" class="text-full form-textarea | |
952 * resize-vertical" placeholder="" cols="60" rows="9" | |
953 * name="body[0][value]"></textarea> | |
954 * @endcode | |
955 * When testing this field using an $edit parameter, the code becomes: | |
956 * @code | |
957 * $edit["body[0][value]"] = 'My test value'; | |
958 * @endcode | |
959 * | |
960 * A checkbox can be set to TRUE to be checked and should be set to FALSE to | |
961 * be unchecked. Multiple select fields can be tested using 'name[]' and | |
962 * setting each of the desired values in an array: | |
963 * @code | |
964 * $edit = array(); | |
965 * $edit['name[]'] = array('value1', 'value2'); | |
966 * @endcode | |
967 * @todo change $edit to disallow NULL as a value for Drupal 9. | |
968 * https://www.drupal.org/node/2802401 | |
969 * @param string $submit | |
970 * Value of the submit button whose click is to be emulated. For example, | |
971 * 'Save'. The processing of the request depends on this value. For example, | |
972 * a form may have one button with the value 'Save' and another button with | |
973 * the value 'Delete', and execute different code depending on which one is | |
974 * clicked. | |
975 * | |
976 * This function can also be called to emulate an Ajax submission. In this | |
977 * case, this value needs to be an array with the following keys: | |
978 * - path: A path to submit the form values to for Ajax-specific processing. | |
979 * - triggering_element: If the value for the 'path' key is a generic Ajax | |
980 * processing path, this needs to be set to the name of the element. If | |
981 * the name doesn't identify the element uniquely, then this should | |
982 * instead be an array with a single key/value pair, corresponding to the | |
983 * element name and value. The \Drupal\Core\Form\FormAjaxResponseBuilder | |
984 * uses this to find the #ajax information for the element, including | |
985 * which specific callback to use for processing the request. | |
986 * | |
987 * This can also be set to NULL in order to emulate an Internet Explorer | |
988 * submission of a form with a single text field, and pressing ENTER in that | |
989 * textfield: under these conditions, no button information is added to the | |
990 * POST data. | |
991 * @param array $options | |
992 * Options to be forwarded to the url generator. | |
993 * | |
994 * @return string | |
995 * (deprecated) The response content after submit form. It is necessary for | |
996 * backwards compatibility and will be removed before Drupal 9.0. You should | |
997 * just use the webAssert object for your assertions. | |
998 */ | |
999 protected function drupalPostForm($path, $edit, $submit, array $options = []) { | |
1000 if (is_object($submit)) { | |
1001 // Cast MarkupInterface objects to string. | |
1002 $submit = (string) $submit; | |
1003 } | |
1004 if ($edit === NULL) { | |
1005 $edit = []; | |
1006 } | |
1007 if (is_array($edit)) { | |
1008 $edit = $this->castSafeStrings($edit); | |
1009 } | |
1010 | |
1011 if (isset($path)) { | |
1012 $this->drupalGet($path, $options); | |
1013 } | |
1014 | |
1015 $this->submitForm($edit, $submit); | |
1016 | |
1017 return $this->getSession()->getPage()->getContent(); | |
1018 } | |
1019 | |
1020 /** | |
1021 * Helper function to get the options of select field. | |
1022 * | |
1023 * @param \Behat\Mink\Element\NodeElement|string $select | |
1024 * Name, ID, or Label of select field to assert. | |
1025 * @param \Behat\Mink\Element\Element $container | |
1026 * (optional) Container element to check against. Defaults to current page. | |
1027 * | |
1028 * @return array | |
1029 * Associative array of option keys and values. | |
1030 */ | |
1031 protected function getOptions($select, Element $container = NULL) { | |
1032 if (is_string($select)) { | |
1033 $select = $this->assertSession()->selectExists($select, $container); | |
1034 } | |
1035 $options = []; | |
1036 /* @var \Behat\Mink\Element\NodeElement $option */ | |
1037 foreach ($select->findAll('xpath', '//option') as $option) { | |
1038 $label = $option->getText(); | |
1039 $value = $option->getAttribute('value') ?: $label; | |
1040 $options[$value] = $label; | |
1041 } | |
1042 return $options; | |
1043 } | |
1044 | |
1045 /** | |
1046 * Installs Drupal into the Simpletest site. | |
1047 */ | |
1048 public function installDrupal() { | |
1049 $this->initUserSession(); | |
1050 $this->prepareSettings(); | |
1051 $this->doInstall(); | |
1052 $this->initSettings(); | |
1053 $container = $this->initKernel(\Drupal::request()); | |
1054 $this->initConfig($container); | |
1055 $this->installModulesFromClassProperty($container); | |
1056 $this->rebuildAll(); | |
1057 } | |
1058 | |
1059 /** | |
1060 * Returns whether a given user account is logged in. | |
1061 * | |
1062 * @param \Drupal\Core\Session\AccountInterface $account | |
1063 * The user account object to check. | |
1064 * | |
1065 * @return bool | |
1066 * Return TRUE if the user is logged in, FALSE otherwise. | |
1067 */ | |
1068 protected function drupalUserIsLoggedIn(AccountInterface $account) { | |
1069 $logged_in = FALSE; | |
1070 | |
1071 if (isset($account->sessionId)) { | |
1072 $session_handler = $this->container->get('session_handler.storage'); | |
1073 $logged_in = (bool) $session_handler->read($account->sessionId); | |
1074 } | |
1075 | |
1076 return $logged_in; | |
1077 } | |
1078 | |
1079 /** | |
1080 * Clicks the element with the given CSS selector. | |
1081 * | |
1082 * @param string $css_selector | |
1083 * The CSS selector identifying the element to click. | |
1084 */ | |
1085 protected function click($css_selector) { | |
1086 $this->getSession()->getDriver()->click($this->cssSelectToXpath($css_selector)); | |
1087 } | |
1088 | |
1089 /** | |
1090 * Prevents serializing any properties. | |
1091 * | |
1092 * Browser tests are run in a separate process. To do this PHPUnit creates a | |
1093 * script to run the test. If it fails, the test result object will contain a | |
1094 * stack trace which includes the test object. It will attempt to serialize | |
1095 * it. Returning an empty array prevents it from serializing anything it | |
1096 * should not. | |
1097 * | |
1098 * @return array | |
1099 * An empty array. | |
1100 * | |
1101 * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist | |
1102 */ | |
1103 public function __sleep() { | |
1104 return []; | |
1105 } | |
1106 | |
1107 /** | |
1108 * Logs a HTML output message in a text file. | |
1109 * | |
1110 * The link to the HTML output message will be printed by the results printer. | |
1111 * | |
1112 * @param string $message | |
1113 * The HTML output message to be stored. | |
1114 * | |
1115 * @see \Drupal\Tests\Listeners\VerbosePrinter::printResult() | |
1116 */ | |
1117 protected function htmlOutput($message) { | |
1118 if (!$this->htmlOutputEnabled) { | |
1119 return; | |
1120 } | |
1121 $message = '<hr />ID #' . $this->htmlOutputCounter . ' (<a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter - 1) . '-' . $this->htmlOutputTestId . '.html">Previous</a> | <a href="' . $this->htmlOutputClassName . '-' . ($this->htmlOutputCounter + 1) . '-' . $this->htmlOutputTestId . '.html">Next</a>)<hr />' . $message; | |
1122 $html_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.html'; | |
1123 file_put_contents($this->htmlOutputDirectory . '/' . $html_output_filename, $message); | |
1124 file_put_contents($this->htmlOutputCounterStorage, $this->htmlOutputCounter++); | |
1125 file_put_contents($this->htmlOutputFile, file_create_url('sites/simpletest/browser_output/' . $html_output_filename) . "\n", FILE_APPEND); | |
1126 } | |
1127 | |
1128 /** | |
1129 * Returns headers in HTML output format. | |
1130 * | |
1131 * @return string | |
1132 * HTML output headers. | |
1133 */ | |
1134 protected function getHtmlOutputHeaders() { | |
1135 return $this->formatHtmlOutputHeaders($this->getSession()->getResponseHeaders()); | |
1136 } | |
1137 | |
1138 /** | |
1139 * Formats HTTP headers as string for HTML output logging. | |
1140 * | |
1141 * @param array[] $headers | |
1142 * Headers that should be formatted. | |
1143 * | |
1144 * @return string | |
1145 * The formatted HTML string. | |
1146 */ | |
1147 protected function formatHtmlOutputHeaders(array $headers) { | |
1148 $flattened_headers = array_map(function ($header) { | |
1149 if (is_array($header)) { | |
1150 return implode(';', array_map('trim', $header)); | |
1151 } | |
1152 else { | |
1153 return $header; | |
1154 } | |
1155 }, $headers); | |
1156 return '<hr />Headers: <pre>' . Html::escape(var_export($flattened_headers, TRUE)) . '</pre>'; | |
1157 } | |
1158 | |
1159 /** | |
1160 * Translates a CSS expression to its XPath equivalent. | |
1161 * | |
1162 * The search is relative to the root element (HTML tag normally) of the page. | |
1163 * | |
1164 * @param string $selector | |
1165 * CSS selector to use in the search. | |
1166 * @param bool $html | |
1167 * (optional) Enables HTML support. Disable it for XML documents. | |
1168 * @param string $prefix | |
1169 * (optional) The prefix for the XPath expression. | |
1170 * | |
1171 * @return string | |
1172 * The equivalent XPath of a CSS expression. | |
1173 */ | |
1174 protected function cssSelectToXpath($selector, $html = TRUE, $prefix = 'descendant-or-self::') { | |
1175 return (new CssSelectorConverter($html))->toXPath($selector, $prefix); | |
1176 } | |
1177 | |
1178 /** | |
1179 * Searches elements using a CSS selector in the raw content. | |
1180 * | |
1181 * The search is relative to the root element (HTML tag normally) of the page. | |
1182 * | |
1183 * @param string $selector | |
1184 * CSS selector to use in the search. | |
1185 * | |
1186 * @return \Behat\Mink\Element\NodeElement[] | |
1187 * The list of elements on the page that match the selector. | |
1188 */ | |
1189 protected function cssSelect($selector) { | |
1190 return $this->getSession()->getPage()->findAll('css', $selector); | |
1191 } | |
1192 | |
1193 /** | |
1194 * Follows a link by complete name. | |
1195 * | |
1196 * Will click the first link found with this link text. | |
1197 * | |
1198 * If the link is discovered and clicked, the test passes. Fail otherwise. | |
1199 * | |
1200 * @param string|\Drupal\Component\Render\MarkupInterface $label | |
1201 * Text between the anchor tags. | |
1202 * @param int $index | |
1203 * (optional) The index number for cases where multiple links have the same | |
1204 * text. Defaults to 0. | |
1205 */ | |
1206 protected function clickLink($label, $index = 0) { | |
1207 $label = (string) $label; | |
1208 $links = $this->getSession()->getPage()->findAll('named', ['link', $label]); | |
1209 $this->assertArrayHasKey($index, $links, 'The link ' . $label . ' was not found on the page.'); | |
1210 $links[$index]->click(); | |
1211 } | |
1212 | |
1213 /** | |
1214 * Retrieves the plain-text content from the current page. | |
1215 */ | |
1216 protected function getTextContent() { | |
1217 return $this->getSession()->getPage()->getText(); | |
1218 } | |
1219 | |
1220 /** | |
1221 * Performs an xpath search on the contents of the internal browser. | |
1222 * | |
1223 * The search is relative to the root element (HTML tag normally) of the page. | |
1224 * | |
1225 * @param string $xpath | |
1226 * The xpath string to use in the search. | |
1227 * @param array $arguments | |
1228 * An array of arguments with keys in the form ':name' matching the | |
1229 * placeholders in the query. The values may be either strings or numeric | |
1230 * values. | |
1231 * | |
1232 * @return \Behat\Mink\Element\NodeElement[] | |
1233 * The list of elements matching the xpath expression. | |
1234 */ | |
1235 protected function xpath($xpath, array $arguments = []) { | |
1236 $xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments); | |
1237 return $this->getSession()->getPage()->findAll('xpath', $xpath); | |
1238 } | |
1239 | |
1240 /** | |
1241 * Configuration accessor for tests. Returns non-overridden configuration. | |
1242 * | |
1243 * @param string $name | |
1244 * Configuration name. | |
1245 * | |
1246 * @return \Drupal\Core\Config\Config | |
1247 * The configuration object with original configuration data. | |
1248 */ | |
1249 protected function config($name) { | |
1250 return $this->container->get('config.factory')->getEditable($name); | |
1251 } | |
1252 | |
1253 /** | |
1254 * Returns all response headers. | |
1255 * | |
1256 * @return array | |
1257 * The HTTP headers values. | |
1258 * | |
1259 * @deprecated Scheduled for removal in Drupal 9.0.0. | |
1260 * Use $this->getSession()->getResponseHeaders() instead. | |
1261 */ | |
1262 protected function drupalGetHeaders() { | |
1263 return $this->getSession()->getResponseHeaders(); | |
1264 } | |
1265 | |
1266 /** | |
1267 * Gets the value of an HTTP response header. | |
1268 * | |
1269 * If multiple requests were required to retrieve the page, only the headers | |
1270 * from the last request will be checked by default. | |
1271 * | |
1272 * @param string $name | |
1273 * The name of the header to retrieve. Names are case-insensitive (see RFC | |
1274 * 2616 section 4.2). | |
1275 * | |
1276 * @return string|null | |
1277 * The HTTP header value or NULL if not found. | |
1278 */ | |
1279 protected function drupalGetHeader($name) { | |
1280 return $this->getSession()->getResponseHeader($name); | |
1281 } | |
1282 | |
1283 /** | |
1284 * Get the current URL from the browser. | |
1285 * | |
1286 * @return string | |
1287 * The current URL. | |
1288 */ | |
1289 protected function getUrl() { | |
1290 return $this->getSession()->getCurrentUrl(); | |
1291 } | |
1292 | |
1293 /** | |
1294 * Gets the JavaScript drupalSettings variable for the currently-loaded page. | |
1295 * | |
1296 * @return array | |
1297 * The JSON decoded drupalSettings value from the current page. | |
1298 */ | |
1299 protected function getDrupalSettings() { | |
1300 $html = $this->getSession()->getPage()->getHtml(); | |
1301 if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $html, $matches)) { | |
1302 return Json::decode($matches[1]); | |
1303 } | |
1304 return []; | |
1305 } | |
1306 | |
1307 /** | |
1308 * {@inheritdoc} | |
1309 */ | |
1310 public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) { | |
1311 // Cast objects implementing MarkupInterface to string instead of | |
1312 // relying on PHP casting them to string depending on what they are being | |
1313 // comparing with. | |
1314 $expected = static::castSafeStrings($expected); | |
1315 $actual = static::castSafeStrings($actual); | |
1316 parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); | |
1317 } | |
1318 | |
1319 /** | |
1320 * Retrieves the current calling line in the class under test. | |
1321 * | |
1322 * @return array | |
1323 * An associative array with keys 'file', 'line' and 'function'. | |
1324 */ | |
1325 protected function getTestMethodCaller() { | |
1326 $backtrace = debug_backtrace(); | |
1327 // Find the test class that has the test method. | |
1328 while ($caller = Error::getLastCaller($backtrace)) { | |
1329 if (isset($caller['class']) && $caller['class'] === get_class($this)) { | |
1330 break; | |
1331 } | |
1332 // If the test method is implemented by a test class's parent then the | |
1333 // class name of $this will not be part of the backtrace. | |
1334 // In that case we process the backtrace until the caller is not a | |
1335 // subclass of $this and return the previous caller. | |
1336 if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) { | |
1337 // Return the last caller since that has to be the test class. | |
1338 $caller = $last_caller; | |
1339 break; | |
1340 } | |
1341 // Otherwise we have not reached our test class yet: save the last caller | |
1342 // and remove an element from to backtrace to process the next call. | |
1343 $last_caller = $caller; | |
1344 array_shift($backtrace); | |
1345 } | |
1346 | |
1347 return $caller; | |
1348 } | |
1349 | |
1350 /** | |
1351 * Transforms a nested array into a flat array suitable for drupalPostForm(). | |
1352 * | |
1353 * @param array $values | |
1354 * A multi-dimensional form values array to convert. | |
1355 * | |
1356 * @return array | |
1357 * The flattened $edit array suitable for BrowserTestBase::drupalPostForm(). | |
1358 */ | |
1359 protected function translatePostValues(array $values) { | |
1360 $edit = []; | |
1361 // The easiest and most straightforward way to translate values suitable for | |
1362 // BrowserTestBase::drupalPostForm() is to actually build the POST data | |
1363 // string and convert the resulting key/value pairs back into a flat array. | |
1364 $query = http_build_query($values); | |
1365 foreach (explode('&', $query) as $item) { | |
1366 list($key, $value) = explode('=', $item); | |
1367 $edit[urldecode($key)] = urldecode($value); | |
1368 } | |
1369 return $edit; | |
1370 } | |
1371 | |
1372 /** | |
1373 * Checks for meta refresh tag and if found call drupalGet() recursively. | |
1374 * | |
1375 * This function looks for the http-equiv attribute to be set to "Refresh" and | |
1376 * is case-insensitive. | |
1377 * | |
1378 * @return string|false | |
1379 * Either the new page content or FALSE. | |
1380 */ | |
1381 protected function checkForMetaRefresh() { | |
1382 $refresh = $this->cssSelect('meta[http-equiv="Refresh"], meta[http-equiv="refresh"]'); | |
1383 if (!empty($refresh) && (!isset($this->maximumMetaRefreshCount) || $this->metaRefreshCount < $this->maximumMetaRefreshCount)) { | |
1384 // Parse the content attribute of the meta tag for the format: | |
1385 // "[delay]: URL=[page_to_redirect_to]". | |
1386 if (preg_match('/\d+;\s*URL=(?<url>.*)/i', $refresh[0]->getAttribute('content'), $match)) { | |
1387 $this->metaRefreshCount++; | |
1388 return $this->drupalGet($this->getAbsoluteUrl(Html::decodeEntities($match['url']))); | |
1389 } | |
1390 } | |
1391 return FALSE; | |
1392 } | |
1393 | |
1394 } |