1. 8.2.x core/tests/Drupal/Tests/BrowserTestBase.php
  2. 8.2.x core/modules/simpletest/src/BrowserTestBase.php
  3. 8.0.x core/modules/simpletest/src/BrowserTestBase.php
  4. 8.1.x core/tests/Drupal/Tests/BrowserTestBase.php
  5. 8.1.x core/modules/simpletest/src/BrowserTestBase.php
  6. 8.3.x core/tests/Drupal/Tests/BrowserTestBase.php
  7. 8.3.x core/modules/simpletest/src/BrowserTestBase.php

Namespace

Drupal\Tests

File

core/tests/Drupal/Tests/BrowserTestBase.php
View source
  1. <?php
  2. namespace Drupal\Tests;
  3. use Behat\Mink\Driver\GoutteDriver;
  4. use Behat\Mink\Element\Element;
  5. use Behat\Mink\Mink;
  6. use Behat\Mink\Session;
  7. use Drupal\Component\FileCache\FileCacheFactory;
  8. use Drupal\Component\Serialization\Json;
  9. use Drupal\Component\Utility\Html;
  10. use Drupal\Component\Utility\SafeMarkup;
  11. use Drupal\Component\Utility\UrlHelper;
  12. use Drupal\Core\Cache\Cache;
  13. use Drupal\Core\Config\Testing\ConfigSchemaChecker;
  14. use Drupal\Core\Database\Database;
  15. use Drupal\Core\DrupalKernel;
  16. use Drupal\Core\Serialization\Yaml;
  17. use Drupal\Core\Session\AccountInterface;
  18. use Drupal\Core\Session\AnonymousUserSession;
  19. use Drupal\Core\Session\UserSession;
  20. use Drupal\Core\Site\Settings;
  21. use Drupal\Core\StreamWrapper\StreamWrapperInterface;
  22. use Drupal\Core\Test\TestRunnerKernel;
  23. use Drupal\Core\Url;
  24. use Drupal\Core\Test\TestDatabase;
  25. use Drupal\FunctionalTests\AssertLegacyTrait;
  26. use Drupal\simpletest\AssertHelperTrait;
  27. use Drupal\simpletest\ContentTypeCreationTrait;
  28. use Drupal\simpletest\BlockCreationTrait;
  29. use Drupal\simpletest\NodeCreationTrait;
  30. use Drupal\simpletest\UserCreationTrait;
  31. use Symfony\Component\CssSelector\CssSelectorConverter;
  32. use Symfony\Component\HttpFoundation\Request;
  33. /**
  34. * Provides a test case for functional Drupal tests.
  35. *
  36. * Tests extending BrowserTestBase must exist in the
  37. * Drupal\Tests\yourmodule\Functional namespace and live in the
  38. * modules/yourmodule/tests/src/Functional directory.
  39. *
  40. * @ingroup testing
  41. */
  42. abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
  43. use AssertHelperTrait;
  44. use BlockCreationTrait {
  45. placeBlock as drupalPlaceBlock;
  46. }
  47. use AssertLegacyTrait;
  48. use RandomGeneratorTrait;
  49. use SessionTestTrait;
  50. use NodeCreationTrait {
  51. getNodeByTitle as drupalGetNodeByTitle;
  52. createNode as drupalCreateNode;
  53. }
  54. use ContentTypeCreationTrait {
  55. createContentType as drupalCreateContentType;
  56. }
  57. use ConfigTestTrait;
  58. use UserCreationTrait {
  59. createRole as drupalCreateRole;
  60. createUser as drupalCreateUser;
  61. }
  62. /**
  63. * Class loader.
  64. *
  65. * @var object
  66. */
  67. protected $classLoader;
  68. /**
  69. * The site directory of this test run.
  70. *
  71. * @var string
  72. */
  73. protected $siteDirectory;
  74. /**
  75. * The database prefix of this test run.
  76. *
  77. * @var string
  78. */
  79. protected $databasePrefix;
  80. /**
  81. * The site directory of the original parent site.
  82. *
  83. * @var string
  84. */
  85. protected $originalSiteDirectory;
  86. /**
  87. * Time limit in seconds for the test.
  88. *
  89. * @var int
  90. */
  91. protected $timeLimit = 500;
  92. /**
  93. * The public file directory for the test environment.
  94. *
  95. * This is set in BrowserTestBase::prepareEnvironment().
  96. *
  97. * @var string
  98. */
  99. protected $publicFilesDirectory;
  100. /**
  101. * The private file directory for the test environment.
  102. *
  103. * This is set in BrowserTestBase::prepareEnvironment().
  104. *
  105. * @var string
  106. */
  107. protected $privateFilesDirectory;
  108. /**
  109. * The temp file directory for the test environment.
  110. *
  111. * This is set in BrowserTestBase::prepareEnvironment(). This value has to
  112. * match the temporary directory created in install_base_system() for test
  113. * installs.
  114. *
  115. * @see install_base_system()
  116. *
  117. * @var string
  118. */
  119. protected $tempFilesDirectory;
  120. /**
  121. * The translation file directory for the test environment.
  122. *
  123. * This is set in BrowserTestBase::prepareEnvironment().
  124. *
  125. * @var string
  126. */
  127. protected $translationFilesDirectory;
  128. /**
  129. * The DrupalKernel instance used in the test.
  130. *
  131. * @var \Drupal\Core\DrupalKernel
  132. */
  133. protected $kernel;
  134. /**
  135. * The dependency injection container used in the test.
  136. *
  137. * @var \Symfony\Component\DependencyInjection\ContainerInterface
  138. */
  139. protected $container;
  140. /**
  141. * The config importer that can be used in a test.
  142. *
  143. * @var \Drupal\Core\Config\ConfigImporter
  144. */
  145. protected $configImporter;
  146. /**
  147. * Set to TRUE to strict check all configuration saved.
  148. *
  149. * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
  150. *
  151. * @var bool
  152. */
  153. protected $strictConfigSchema = TRUE;
  154. /**
  155. * Modules to enable.
  156. *
  157. * The test runner will merge the $modules lists from this class, the class
  158. * it extends, and so on up the class hierarchy. It is not necessary to
  159. * include modules in your list that a parent class has already declared.
  160. *
  161. * @var string[]
  162. *
  163. * @see \Drupal\Tests\BrowserTestBase::installDrupal()
  164. */
  165. protected static $modules = [];
  166. /**
  167. * An array of config object names that are excluded from schema checking.
  168. *
  169. * @var string[]
  170. */
  171. protected static $configSchemaCheckerExclusions = array(
  172. // Following are used to test lack of or partial schema. Where partial
  173. // schema is provided, that is explicitly tested in specific tests.
  174. 'config_schema_test.noschema',
  175. 'config_schema_test.someschema',
  176. 'config_schema_test.schema_data_types',
  177. 'config_schema_test.no_schema_data_types',
  178. // Used to test application of schema to filtering of configuration.
  179. 'config_test.dynamic.system',
  180. );
  181. /**
  182. * The profile to install as a basis for testing.
  183. *
  184. * @var string
  185. */
  186. protected $profile = 'testing';
  187. /**
  188. * The current user logged in using the Mink controlled browser.
  189. *
  190. * @var \Drupal\user\UserInterface
  191. */
  192. protected $loggedInUser = FALSE;
  193. /**
  194. * The root user.
  195. *
  196. * @var \Drupal\Core\Session\UserSession
  197. */
  198. protected $rootUser;
  199. /**
  200. * The config directories used in this test.
  201. *
  202. * @var array
  203. */
  204. protected $configDirectories = array();
  205. /**
  206. * An array of custom translations suitable for drupal_rewrite_settings().
  207. *
  208. * @var array
  209. */
  210. protected $customTranslations;
  211. /*
  212. * Mink class for the default driver to use.
  213. *
  214. * Shoud be a fully qualified class name that implements
  215. * Behat\Mink\Driver\DriverInterface.
  216. *
  217. * Value can be overridden using the environment variable MINK_DRIVER_CLASS.
  218. *
  219. * @var string.
  220. */
  221. protected $minkDefaultDriverClass = GoutteDriver::class;
  222. /*
  223. * Mink default driver params.
  224. *
  225. * If it's an array its contents are used as constructor params when default
  226. * Mink driver class is instantiated.
  227. *
  228. * Can be overridden using the environment variable MINK_DRIVER_ARGS. In this
  229. * case that variable should be a JSON array, for example:
  230. * '["firefox", null, "http://localhost:4444/wd/hub"]'.
  231. *
  232. *
  233. * @var array
  234. */
  235. protected $minkDefaultDriverArgs;
  236. /**
  237. * Mink session manager.
  238. *
  239. * This will not be initialized if there was an error during the test setup.
  240. *
  241. * @var \Behat\Mink\Mink|null
  242. */
  243. protected $mink;
  244. /**
  245. * {@inheritdoc}
  246. *
  247. * Browser tests are run in separate processes to prevent collisions between
  248. * code that may be loaded by tests.
  249. */
  250. protected $runTestInSeparateProcess = TRUE;
  251. /**
  252. * {@inheritdoc}
  253. */
  254. protected $preserveGlobalState = FALSE;
  255. /**
  256. * Class name for HTML output logging.
  257. *
  258. * @var string
  259. */
  260. protected $htmlOutputClassName;
  261. /**
  262. * Directory name for HTML output logging.
  263. *
  264. * @var string
  265. */
  266. protected $htmlOutputDirectory;
  267. /**
  268. * Counter storage for HTML output logging.
  269. *
  270. * @var string
  271. */
  272. protected $htmlOutputCounterStorage;
  273. /**
  274. * Counter for HTML output logging.
  275. *
  276. * @var int
  277. */
  278. protected $htmlOutputCounter = 1;
  279. /**
  280. * HTML output output enabled.
  281. *
  282. * @var bool
  283. */
  284. protected $htmlOutputEnabled = FALSE;
  285. /**
  286. * The file name to write the list of URLs to.
  287. *
  288. * This file is read by the PHPUnit result printer.
  289. *
  290. * @var string
  291. *
  292. * @see \Drupal\Tests\Listeners\HtmlOutputPrinter
  293. */
  294. protected $htmlOutputFile;
  295. /**
  296. * HTML output test ID.
  297. *
  298. * @var int
  299. */
  300. protected $htmlOutputTestId;
  301. /**
  302. * The base URL.
  303. *
  304. * @var string
  305. */
  306. protected $baseUrl;
  307. /**
  308. * The original array of shutdown function callbacks.
  309. *
  310. * @var array
  311. */
  312. protected $originalShutdownCallbacks = [];
  313. /**
  314. * Initializes Mink sessions.
  315. */
  316. protected function initMink() {
  317. $driver = $this->getDefaultDriverInstance();
  318. if ($driver instanceof GoutteDriver) {
  319. // Turn off curl timeout. Having a timeout is not a problem in a normal
  320. // test running, but it is a problem when debugging.
  321. /** @var \GuzzleHttp\Client $client */
  322. $client = $this->container->get('http_client_factory')->fromOptions(['timeout' => NULL]);
  323. $driver->getClient()->setClient($client);
  324. }
  325. $session = new Session($driver);
  326. $this->mink = new Mink();
  327. $this->mink->registerSession('default', $session);
  328. $this->mink->setDefaultSessionName('default');
  329. $this->registerSessions();
  330. // According to the W3C WebDriver specification a cookie can only be set if
  331. // the cookie domain is equal to the domain of the active document. When the
  332. // browser starts up the active document is not our domain but 'about:blank'
  333. // or similar. To be able to set our User-Agent and Xdebug cookies at the
  334. // start of the test we now do a request to the front page so the active
  335. // document matches the domain.
  336. // @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
  337. // @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975
  338. $session = $this->getSession();
  339. $session->visit($this->baseUrl);
  340. return $session;
  341. }
  342. /**
  343. * Gets an instance of the default Mink driver.
  344. *
  345. * @return Behat\Mink\Driver\DriverInterface
  346. * Instance of default Mink driver.
  347. *
  348. * @throws \InvalidArgumentException
  349. * When provided default Mink driver class can't be instantiated.
  350. */
  351. protected function getDefaultDriverInstance() {
  352. // Get default driver params from environment if availables.
  353. if ($arg_json = getenv('MINK_DRIVER_ARGS')) {
  354. $this->minkDefaultDriverArgs = json_decode($arg_json);
  355. }
  356. // Get and check default driver class from environment if availables.
  357. if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) {
  358. if (class_exists($minkDriverClass)) {
  359. $this->minkDefaultDriverClass = $minkDriverClass;
  360. }
  361. else {
  362. throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class.");
  363. }
  364. }
  365. if (is_array($this->minkDefaultDriverArgs)) {
  366. // Use ReflectionClass to instantiate class with received params.
  367. $reflector = new \ReflectionClass($this->minkDefaultDriverClass);
  368. $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
  369. }
  370. else {
  371. $driver = new $this->minkDefaultDriverClass();
  372. }
  373. return $driver;
  374. }
  375. /**
  376. * Registers additional Mink sessions.
  377. *
  378. * Tests wishing to use a different driver or change the default driver should
  379. * override this method.
  380. *
  381. * @code
  382. * // Register a new session that uses the MinkPonyDriver.
  383. * $pony = new MinkPonyDriver();
  384. * $session = new Session($pony);
  385. * $this->mink->registerSession('pony', $session);
  386. * @endcode
  387. */
  388. protected function registerSessions() {}
  389. /**
  390. * {@inheritdoc}
  391. */
  392. protected function setUp() {
  393. global $base_url;
  394. parent::setUp();
  395. // Get and set the domain of the environment we are running our test
  396. // coverage against.
  397. $base_url = getenv('SIMPLETEST_BASE_URL');
  398. if (!$base_url) {
  399. throw new \Exception(
  400. 'You must provide a SIMPLETEST_BASE_URL environment variable to run some PHPUnit based functional tests.'
  401. );
  402. }
  403. // Setup $_SERVER variable.
  404. $parsed_url = parse_url($base_url);
  405. $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
  406. $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : '';
  407. $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80;
  408. $this->baseUrl = $base_url;
  409. // If the passed URL schema is 'https' then setup the $_SERVER variables
  410. // properly so that testing will run under HTTPS.
  411. if ($parsed_url['scheme'] === 'https') {
  412. $_SERVER['HTTPS'] = 'on';
  413. }
  414. $_SERVER['HTTP_HOST'] = $host;
  415. $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
  416. $_SERVER['SERVER_ADDR'] = '127.0.0.1';
  417. $_SERVER['SERVER_PORT'] = $port;
  418. $_SERVER['SERVER_SOFTWARE'] = NULL;
  419. $_SERVER['SERVER_NAME'] = 'localhost';
  420. $_SERVER['REQUEST_URI'] = $path . '/';
  421. $_SERVER['REQUEST_METHOD'] = 'GET';
  422. $_SERVER['SCRIPT_NAME'] = $path . '/index.php';
  423. $_SERVER['SCRIPT_FILENAME'] = $path . '/index.php';
  424. $_SERVER['PHP_SELF'] = $path . '/index.php';
  425. $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
  426. // Install Drupal test site.
  427. $this->prepareEnvironment();
  428. $this->installDrupal();
  429. // Setup Mink.
  430. $session = $this->initMink();
  431. // In order to debug web tests you need to either set a cookie, have the
  432. // Xdebug session in the URL or set an environment variable in case of CLI
  433. // requests. If the developer listens to connection when running tests, by
  434. // default the cookie is not forwarded to the client side, so you cannot
  435. // debug the code running on the test site. In order to make debuggers work
  436. // this bit of information is forwarded. Make sure that the debugger listens
  437. // to at least three external connections.
  438. $request = \Drupal::request();
  439. $cookie_params = $request->cookies;
  440. if ($cookie_params->has('XDEBUG_SESSION')) {
  441. $session->setCookie('XDEBUG_SESSION', $cookie_params->get('XDEBUG_SESSION'));
  442. }
  443. // For CLI requests, the information is stored in $_SERVER.
  444. $server = $request->server;
  445. if ($server->has('XDEBUG_CONFIG')) {
  446. // $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...".
  447. $pairs = explode(' ', $server->get('XDEBUG_CONFIG'));
  448. foreach ($pairs as $pair) {
  449. list($key, $value) = explode('=', $pair);
  450. // Account for key-value pairs being separated by multiple spaces.
  451. if (trim($key) == 'idekey') {
  452. $session->setCookie('XDEBUG_SESSION', trim($value));
  453. }
  454. }
  455. }
  456. // Creates the directory to store browser output in if a file to write
  457. // URLs to has been created by \Drupal\Tests\Listeners\HtmlOutputPrinter.
  458. $browser_output_file = getenv('BROWSERTEST_OUTPUT_FILE');
  459. $this->htmlOutputEnabled = is_file($browser_output_file);
  460. if ($this->htmlOutputEnabled) {
  461. $this->htmlOutputFile = $browser_output_file;
  462. $this->htmlOutputClassName = str_replace("\\", "_", get_called_class());
  463. $this->htmlOutputDirectory = DRUPAL_ROOT . '/sites/simpletest/browser_output';
  464. if (file_prepare_directory($this->htmlOutputDirectory, FILE_CREATE_DIRECTORY) && !file_exists($this->htmlOutputDirectory . '/.htaccess')) {
  465. file_put_contents($this->htmlOutputDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
  466. }
  467. $this->htmlOutputCounterStorage = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '.counter';
  468. $this->htmlOutputTestId = str_replace('sites/simpletest/', '', $this->siteDirectory);
  469. if (is_file($this->htmlOutputCounterStorage)) {
  470. $this->htmlOutputCounter = max(1, (int) file_get_contents($this->htmlOutputCounterStorage)) + 1;
  471. }
  472. }
  473. }
  474. /**
  475. * Ensures test files are deletable within file_unmanaged_delete_recursive().
  476. *
  477. * Some tests chmod generated files to be read only. During
  478. * BrowserTestBase::cleanupEnvironment() and other cleanup operations,
  479. * these files need to get deleted too.
  480. *
  481. * @param string $path
  482. * The file path.
  483. */
  484. public static function filePreDeleteCallback($path) {
  485. // When the webserver runs with the same system user as phpunit, we can
  486. // make read-only files writable again. If not, chmod will fail while the
  487. // file deletion still works if file permissions have been configured
  488. // correctly. Thus, we ignore any problems while running chmod.
  489. @chmod($path, 0700);
  490. }
  491. /**
  492. * Clean up the Simpletest environment.
  493. */
  494. protected function cleanupEnvironment() {
  495. // Remove all prefixed tables.
  496. $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
  497. $original_prefix = $original_connection_info['default']['prefix']['default'];
  498. $test_connection_info = Database::getConnectionInfo('default');
  499. $test_prefix = $test_connection_info['default']['prefix']['default'];
  500. if ($original_prefix != $test_prefix) {
  501. $tables = Database::getConnection()->schema()->findTables('%');
  502. foreach ($tables as $table) {
  503. if (Database::getConnection()->schema()->dropTable($table)) {
  504. unset($tables[$table]);
  505. }
  506. }
  507. }
  508. // Delete test site directory.
  509. file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
  510. }
  511. /**
  512. * {@inheritdoc}
  513. */
  514. protected function tearDown() {
  515. parent::tearDown();
  516. // Destroy the testing kernel.
  517. if (isset($this->kernel)) {
  518. $this->cleanupEnvironment();
  519. $this->kernel->shutdown();
  520. }
  521. // Ensure that internal logged in variable is reset.
  522. $this->loggedInUser = FALSE;
  523. if ($this->mink) {
  524. $this->mink->stopSessions();
  525. }
  526. // Restore original shutdown callbacks.
  527. if (function_exists('drupal_register_shutdown_function')) {
  528. $callbacks = &drupal_register_shutdown_function();
  529. $callbacks = $this->originalShutdownCallbacks;
  530. }
  531. }
  532. /**
  533. * Returns Mink session.
  534. *
  535. * @param string $name
  536. * (optional) Name of the session. Defaults to the active session.
  537. *
  538. * @return \Behat\Mink\Session
  539. * The active Mink session object.
  540. */
  541. public function getSession($name = NULL) {
  542. return $this->mink->getSession($name);
  543. }
  544. /**
  545. * Returns WebAssert object.
  546. *
  547. * @param string $name
  548. * (optional) Name of the session. Defaults to the active session.
  549. *
  550. * @return \Drupal\Tests\WebAssert
  551. * A new web-assert option for asserting the presence of elements with.
  552. */
  553. public function assertSession($name = NULL) {
  554. return new WebAssert($this->getSession($name), $this->baseUrl);
  555. }
  556. /**
  557. * Prepare for a request to testing site.
  558. *
  559. * The testing site is protected via a SIMPLETEST_USER_AGENT cookie that is
  560. * checked by drupal_valid_test_ua().
  561. *
  562. * @see drupal_valid_test_ua()
  563. */
  564. protected function prepareRequest() {
  565. $session = $this->getSession();
  566. $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
  567. }
  568. /**
  569. * Builds an a absolute URL from a system path or a URL object.
  570. *
  571. * @param string|\Drupal\Core\Url $path
  572. * A system path or a URL.
  573. * @param array $options
  574. * Options to be passed to Url::fromUri().
  575. *
  576. * @return string
  577. * An absolute URL stsring.
  578. */
  579. protected function buildUrl($path, array $options = array()) {
  580. if ($path instanceof Url) {
  581. $url_options = $path->getOptions();
  582. $options = $url_options + $options;
  583. $path->setOptions($options);
  584. return $path->setAbsolute()->toString();
  585. }
  586. // The URL generator service is not necessarily available yet; e.g., in
  587. // interactive installer tests.
  588. elseif ($this->container->has('url_generator')) {
  589. $force_internal = isset($options['external']) && $options['external'] == FALSE;
  590. if (!$force_internal && UrlHelper::isExternal($path)) {
  591. return Url::fromUri($path, $options)->toString();
  592. }
  593. else {
  594. $uri = $path === '<front>' ? 'base:/' : 'base:/' . $path;
  595. // Path processing is needed for language prefixing. Skip it when a
  596. // path that may look like an external URL is being used as internal.
  597. $options['path_processing'] = !$force_internal;
  598. return Url::fromUri($uri, $options)
  599. ->setAbsolute()
  600. ->toString();
  601. }
  602. }
  603. else {
  604. return $this->getAbsoluteUrl($path);
  605. }
  606. }
  607. /**
  608. * Retrieves a Drupal path or an absolute path.
  609. *
  610. * @param string|\Drupal\Core\Url $path
  611. * Drupal path or URL to load into Mink controlled browser.
  612. * @param array $options
  613. * (optional) Options to be forwarded to the url generator.
  614. * @param string[] $headers
  615. * An array containing additional HTTP request headers, the array keys are
  616. * the header names and the array values the header values. This is useful
  617. * to set for example the "Accept-Language" header for requesting the page
  618. * in a different language. Note that not all headers are supported, for
  619. * example the "Accept" header is always overridden by the browser. For
  620. * testing REST APIs it is recommended to directly use an HTTP client such
  621. * as Guzzle instead.
  622. *
  623. * @return string
  624. * The retrieved HTML string, also available as $this->getRawContent()
  625. */
  626. protected function drupalGet($path, array $options = array(), array $headers = array()) {
  627. $options['absolute'] = TRUE;
  628. $url = $this->buildUrl($path, $options);
  629. $session = $this->getSession();
  630. $this->prepareRequest();
  631. foreach ($headers as $header_name => $header_value) {
  632. $session->setRequestHeader($header_name, $header_value);
  633. }
  634. $session->visit($url);
  635. $out = $session->getPage()->getContent();
  636. // Ensure that any changes to variables in the other thread are picked up.
  637. $this->refreshVariables();
  638. if ($this->htmlOutputEnabled) {
  639. $html_output = 'GET request to: ' . $url .
  640. '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
  641. $html_output .= '<hr />' . $out;
  642. $html_output .= $this->getHtmlOutputHeaders();
  643. $this->htmlOutput($html_output);
  644. }
  645. return $out;
  646. }
  647. /**
  648. * Takes a path and returns an absolute path.
  649. *
  650. * @param string $path
  651. * A path from the Mink controlled browser content.
  652. *
  653. * @return string
  654. * The $path with $base_url prepended, if necessary.
  655. */
  656. protected function getAbsoluteUrl($path) {
  657. global $base_url, $base_path;
  658. $parts = parse_url($path);
  659. if (empty($parts['host'])) {
  660. // Ensure that we have a string (and no xpath object).
  661. $path = (string) $path;
  662. // Strip $base_path, if existent.
  663. $length = strlen($base_path);
  664. if (substr($path, 0, $length) === $base_path) {
  665. $path = substr($path, $length);
  666. }
  667. // Ensure that we have an absolute path.
  668. if (empty($path) || $path[0] !== '/') {
  669. $path = '/' . $path;
  670. }
  671. // Finally, prepend the $base_url.
  672. $path = $base_url . $path;
  673. }
  674. return $path;
  675. }
  676. /**
  677. * Logs in a user using the Mink controlled browser.
  678. *
  679. * If a user is already logged in, then the current user is logged out before
  680. * logging in the specified user.
  681. *
  682. * Please note that neither the current user nor the passed-in user object is
  683. * populated with data of the logged in user. If you need full access to the
  684. * user object after logging in, it must be updated manually. If you also need
  685. * access to the plain-text password of the user (set by drupalCreateUser()),
  686. * e.g. to log in the same user again, then it must be re-assigned manually.
  687. * For example:
  688. * @code
  689. * // Create a user.
  690. * $account = $this->drupalCreateUser(array());
  691. * $this->drupalLogin($account);
  692. * // Load real user object.
  693. * $pass_raw = $account->passRaw;
  694. * $account = User::load($account->id());
  695. * $account->passRaw = $pass_raw;
  696. * @endcode
  697. *
  698. * @param \Drupal\Core\Session\AccountInterface $account
  699. * User object representing the user to log in.
  700. *
  701. * @see drupalCreateUser()
  702. */
  703. protected function drupalLogin(AccountInterface $account) {
  704. if ($this->loggedInUser) {
  705. $this->drupalLogout();
  706. }
  707. $this->drupalGet('user');
  708. $this->assertSession()->statusCodeEquals(200);
  709. $this->submitForm(array(
  710. 'name' => $account->getUsername(),
  711. 'pass' => $account->passRaw,
  712. ), t('Log in'));
  713. // @see BrowserTestBase::drupalUserIsLoggedIn()
  714. $account->sessionId = $this->getSession()->getCookie($this->getSessionName());
  715. $this->assertTrue($this->drupalUserIsLoggedIn($account), SafeMarkup::format('User %name successfully logged in.', array('name' => $account->getUsername())));
  716. $this->loggedInUser = $account;
  717. $this->container->get('current_user')->setAccount($account);
  718. }
  719. /**
  720. * Logs a user out of the Mink controlled browser and confirms.
  721. *
  722. * Confirms logout by checking the login page.
  723. */
  724. protected function drupalLogout() {
  725. // Make a request to the logout page, and redirect to the user page, the
  726. // idea being if you were properly logged out you should be seeing a login
  727. // screen.
  728. $assert_session = $this->assertSession();
  729. $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
  730. $assert_session->statusCodeEquals(200);
  731. $assert_session->fieldExists('name');
  732. $assert_session->fieldExists('pass');
  733. // @see BrowserTestBase::drupalUserIsLoggedIn()
  734. unset($this->loggedInUser->sessionId);
  735. $this->loggedInUser = FALSE;
  736. $this->container->get('current_user')->setAccount(new AnonymousUserSession());
  737. }
  738. /**
  739. * Fills and submits a form.
  740. *
  741. * @param array $edit
  742. * Field data in an associative array. Changes the current input fields
  743. * (where possible) to the values indicated.
  744. *
  745. * A checkbox can be set to TRUE to be checked and should be set to FALSE to
  746. * be unchecked.
  747. * @param string $submit
  748. * Value of the submit button whose click is to be emulated. For example,
  749. * t('Save'). The processing of the request depends on this value. For
  750. * example, a form may have one button with the value t('Save') and another
  751. * button with the value t('Delete'), and execute different code depending
  752. * on which one is clicked.
  753. * @param string $form_html_id
  754. * (optional) HTML ID of the form to be submitted. On some pages
  755. * there are many identical forms, so just using the value of the submit
  756. * button is not enough. For example: 'trigger-node-presave-assign-form'.
  757. * Note that this is not the Drupal $form_id, but rather the HTML ID of the
  758. * form, which is typically the same thing but with hyphens replacing the
  759. * underscores.
  760. */
  761. protected function submitForm(array $edit, $submit, $form_html_id = NULL) {
  762. $assert_session = $this->assertSession();
  763. // Get the form.
  764. if (isset($form_html_id)) {
  765. $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']");
  766. $submit_button = $assert_session->buttonExists($submit, $form);
  767. $action = $form->getAttribute('action');
  768. }
  769. else {
  770. $submit_button = $assert_session->buttonExists($submit);
  771. $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button);
  772. $action = $form->getAttribute('action');
  773. }
  774. // Edit the form values.
  775. foreach ($edit as $name => $value) {
  776. $field = $assert_session->fieldExists($name, $form);
  777. // Provide support for the values '1' and '0' for checkboxes instead of
  778. // TRUE and FALSE.
  779. // @todo Get rid of supporting 1/0 by converting all tests cases using
  780. // this to boolean values.
  781. $field_type = $field->getAttribute('type');
  782. if ($field_type === 'checkbox') {
  783. $value = (bool) $value;
  784. }
  785. $field->setValue($value);
  786. }
  787. // Submit form.
  788. $this->prepareRequest();
  789. $submit_button->press();
  790. // Ensure that any changes to variables in the other thread are picked up.
  791. $this->refreshVariables();
  792. if ($this->htmlOutputEnabled) {
  793. $out = $this->getSession()->getPage()->getContent();
  794. $html_output = 'POST request to: ' . $action .
  795. '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
  796. $html_output .= '<hr />' . $out;
  797. $html_output .= $this->getHtmlOutputHeaders();
  798. $this->htmlOutput($html_output);
  799. }
  800. }
  801. /**
  802. * Executes a form submission.
  803. *
  804. * It will be done as usual POST request with Mink.
  805. *
  806. * @param \Drupal\Core\Url|string $path
  807. * Location of the post form. Either a Drupal path or an absolute path or
  808. * NULL to post to the current page. For multi-stage forms you can set the
  809. * path to NULL and have it post to the last received page. Example:
  810. *
  811. * @code
  812. * // First step in form.
  813. * $edit = array(...);
  814. * $this->drupalPostForm('some_url', $edit, t('Save'));
  815. *
  816. * // Second step in form.
  817. * $edit = array(...);
  818. * $this->drupalPostForm(NULL, $edit, t('Save'));
  819. * @endcode
  820. * @param array $edit
  821. * Field data in an associative array. Changes the current input fields
  822. * (where possible) to the values indicated.
  823. *
  824. * When working with form tests, the keys for an $edit element should match
  825. * the 'name' parameter of the HTML of the form. For example, the 'body'
  826. * field for a node has the following HTML:
  827. * @code
  828. * <textarea id="edit-body-und-0-value" class="text-full form-textarea
  829. * resize-vertical" placeholder="" cols="60" rows="9"
  830. * name="body[0][value]"></textarea>
  831. * @endcode
  832. * When testing this field using an $edit parameter, the code becomes:
  833. * @code
  834. * $edit["body[0][value]"] = 'My test value';
  835. * @endcode
  836. *
  837. * A checkbox can be set to TRUE to be checked and should be set to FALSE to
  838. * be unchecked. Multiple select fields can be tested using 'name[]' and
  839. * setting each of the desired values in an array:
  840. * @code
  841. * $edit = array();
  842. * $edit['name[]'] = array('value1', 'value2');
  843. * @endcode
  844. * @param string $submit
  845. * Value of the submit button whose click is to be emulated. For example,
  846. * t('Save'). The processing of the request depends on this value. For
  847. * example, a form may have one button with the value t('Save') and another
  848. * button with the value t('Delete'), and execute different code depending
  849. * on which one is clicked.
  850. *
  851. * This function can also be called to emulate an Ajax submission. In this
  852. * case, this value needs to be an array with the following keys:
  853. * - path: A path to submit the form values to for Ajax-specific processing.
  854. * - triggering_element: If the value for the 'path' key is a generic Ajax
  855. * processing path, this needs to be set to the name of the element. If
  856. * the name doesn't identify the element uniquely, then this should
  857. * instead be an array with a single key/value pair, corresponding to the
  858. * element name and value. The \Drupal\Core\Form\FormAjaxResponseBuilder
  859. * uses this to find the #ajax information for the element, including
  860. * which specific callback to use for processing the request.
  861. *
  862. * This can also be set to NULL in order to emulate an Internet Explorer
  863. * submission of a form with a single text field, and pressing ENTER in that
  864. * textfield: under these conditions, no button information is added to the
  865. * POST data.
  866. * @param array $options
  867. * Options to be forwarded to the url generator.
  868. */
  869. protected function drupalPostForm($path, array $edit, $submit, array $options = array()) {
  870. if (is_object($submit)) {
  871. // Cast MarkupInterface objects to string.
  872. $submit = (string) $submit;
  873. }
  874. if (is_array($edit)) {
  875. $edit = $this->castSafeStrings($edit);
  876. }
  877. if (isset($path)) {
  878. $this->drupalGet($path, $options);
  879. }
  880. $this->submitForm($edit, $submit);
  881. }
  882. /**
  883. * Helper function to get the options of select field.
  884. *
  885. * @param \Behat\Mink\Element\NodeElement|string $select
  886. * Name, ID, or Label of select field to assert.
  887. * @param \Behat\Mink\Element\Element $container
  888. * (optional) Container element to check against. Defaults to current page.
  889. *
  890. * @return array
  891. * Associative array of option keys and values.
  892. */
  893. protected function getOptions($select, Element $container = NULL) {
  894. if (is_string($select)) {
  895. $select = $this->assertSession()->selectExists($select, $container);
  896. }
  897. $options = [];
  898. /* @var \Behat\Mink\Element\NodeElement $option */
  899. foreach ($select->findAll('xpath', '//option') as $option) {
  900. $label = $option->getText();
  901. $value = $option->getAttribute('value') ?: $label;
  902. $options[$value] = $label;
  903. }
  904. return $options;
  905. }
  906. /**
  907. * Installs Drupal into the Simpletest site.
  908. */
  909. public function installDrupal() {
  910. // Define information about the user 1 account.
  911. $this->rootUser = new UserSession(array(
  912. 'uid' => 1,
  913. 'name' => 'admin',
  914. 'mail' => 'admin@example.com',
  915. 'passRaw' => $this->randomMachineName(),
  916. ));
  917. // The child site derives its session name from the database prefix when
  918. // running web tests.
  919. $this->generateSessionName($this->databasePrefix);
  920. // Get parameters for install_drupal() before removing global variables.
  921. $parameters = $this->installParameters();
  922. // Prepare installer settings that are not install_drupal() parameters.
  923. // Copy and prepare an actual settings.php, so as to resemble a regular
  924. // installation.
  925. // Not using File API; a potential error must trigger a PHP warning.
  926. $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
  927. copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
  928. // The public file system path is created during installation. Additionally,
  929. // during tests:
  930. // - The temporary directory is set and created by install_base_system().
  931. // - The private file directory is created post install by this method.
  932. // @see system_requirements()
  933. // @see TestBase::prepareEnvironment()
  934. // @see install_base_system()
  935. $settings['settings']['file_public_path'] = (object) array(
  936. 'value' => $this->publicFilesDirectory,
  937. 'required' => TRUE,
  938. );
  939. $settings['settings']['file_private_path'] = (object) [
  940. 'value' => $this->privateFilesDirectory,
  941. 'required' => TRUE,
  942. ];
  943. $this->writeSettings($settings);
  944. // Allow for test-specific overrides.
  945. $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/settings.testing.php';
  946. if (file_exists($settings_testing_file)) {
  947. // Copy the testing-specific settings.php overrides in place.
  948. copy($settings_testing_file, $directory . '/settings.testing.php');
  949. // Add the name of the testing class to settings.php and include the
  950. // testing specific overrides.
  951. file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
  952. }
  953. $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/testing.services.yml';
  954. if (!file_exists($settings_services_file)) {
  955. // Otherwise, use the default services as a starting point for overrides.
  956. $settings_services_file = DRUPAL_ROOT . '/sites/default/default.services.yml';
  957. }
  958. // Copy the testing-specific service overrides in place.
  959. copy($settings_services_file, $directory . '/services.yml');
  960. if ($this->strictConfigSchema) {
  961. // Add a listener to validate configuration schema on save.
  962. $content = file_get_contents($directory . '/services.yml');
  963. $services = Yaml::decode($content);
  964. $services['services']['simpletest.config_schema_checker'] = [
  965. 'class' => ConfigSchemaChecker::class,
  966. 'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()],
  967. 'tags' => [['name' => 'event_subscriber']]
  968. ];
  969. file_put_contents($directory . '/services.yml', Yaml::encode($services));
  970. }
  971. // Since Drupal is bootstrapped already, install_begin_request() will not
  972. // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
  973. // reload the newly written custom settings.php manually.
  974. Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
  975. // Execute the non-interactive installer.
  976. require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
  977. install_drupal($parameters);
  978. // Import new settings.php written by the installer.
  979. Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
  980. foreach ($GLOBALS['config_directories'] as $type => $path) {
  981. $this->configDirectories[$type] = $path;
  982. }
  983. // After writing settings.php, the installer removes write permissions from
  984. // the site directory. To allow drupal_generate_test_ua() to write a file
  985. // containing the private key for drupal_valid_test_ua(), the site directory
  986. // has to be writable.
  987. // TestBase::restoreEnvironment() will delete the entire site directory. Not
  988. // using File API; a potential error must trigger a PHP warning.
  989. chmod($directory, 0777);
  990. // During tests, cacheable responses should get the debugging cacheability
  991. // headers by default.
  992. $this->setContainerParameter('http.response.debug_cacheability_headers', TRUE);
  993. $request = \Drupal::request();
  994. $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
  995. $this->kernel->prepareLegacyRequest($request);
  996. // Force the container to be built from scratch instead of loaded from the
  997. // disk. This forces us to not accidentally load the parent site.
  998. $container = $this->kernel->rebuildContainer();
  999. $config = $container->get('config.factory');
  1000. // Manually create the private directory.
  1001. file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
  1002. // Manually configure the test mail collector implementation to prevent
  1003. // tests from sending out emails and collect them in state instead.
  1004. // While this should be enforced via settings.php prior to installation,
  1005. // some tests expect to be able to test mail system implementations.
  1006. $config->getEditable('system.mail')
  1007. ->set('interface.default', 'test_mail_collector')
  1008. ->save();
  1009. // By default, verbosely display all errors and disable all production
  1010. // environment optimizations for all tests to avoid needless overhead and
  1011. // ensure a sane default experience for test authors.
  1012. // @see https://www.drupal.org/node/2259167
  1013. $config->getEditable('system.logging')
  1014. ->set('error_level', 'verbose')
  1015. ->save();
  1016. $config->getEditable('system.performance')
  1017. ->set('css.preprocess', FALSE)
  1018. ->set('js.preprocess', FALSE)
  1019. ->save();
  1020. // Collect modules to install.
  1021. $class = get_class($this);
  1022. $modules = array();
  1023. while ($class) {
  1024. if (property_exists($class, 'modules')) {
  1025. $modules = array_merge($modules, $class::$modules);
  1026. }
  1027. $class = get_parent_class($class);
  1028. }
  1029. if ($modules) {
  1030. $modules = array_unique($modules);
  1031. $success = $container->get('module_installer')->install($modules, TRUE);
  1032. $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
  1033. $this->rebuildContainer();
  1034. }
  1035. // Reset/rebuild all data structures after enabling the modules, primarily
  1036. // to synchronize all data structures and caches between the test runner and
  1037. // the child site.
  1038. // Affects e.g. StreamWrapperManagerInterface::getWrappers().
  1039. // @see \Drupal\Core\DrupalKernel::bootCode()
  1040. // @todo Test-specific setUp() methods may set up further fixtures; find a
  1041. // way to execute this after setUp() is done, or to eliminate it entirely.
  1042. $this->resetAll();
  1043. $this->kernel->prepareLegacyRequest($request);
  1044. }
  1045. /**
  1046. * Returns the parameters that will be used when Simpletest installs Drupal.
  1047. *
  1048. * @see install_drupal()
  1049. * @see install_state_defaults()
  1050. */
  1051. protected function installParameters() {
  1052. $connection_info = Database::getConnectionInfo();
  1053. $driver = $connection_info['default']['driver'];
  1054. $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
  1055. unset($connection_info['default']['driver']);
  1056. unset($connection_info['default']['namespace']);
  1057. unset($connection_info['default']['pdo']);
  1058. unset($connection_info['default']['init_commands']);
  1059. $parameters = array(
  1060. 'interactive' => FALSE,
  1061. 'parameters' => array(
  1062. 'profile' => $this->profile,
  1063. 'langcode' => 'en',
  1064. ),
  1065. 'forms' => array(
  1066. 'install_settings_form' => array(
  1067. 'driver' => $driver,
  1068. $driver => $connection_info['default'],
  1069. ),
  1070. 'install_configure_form' => array(
  1071. 'site_name' => 'Drupal',
  1072. 'site_mail' => 'simpletest@example.com',
  1073. 'account' => array(
  1074. 'name' => $this->rootUser->name,
  1075. 'mail' => $this->rootUser->getEmail(),
  1076. 'pass' => array(
  1077. 'pass1' => $this->rootUser->passRaw,
  1078. 'pass2' => $this->rootUser->passRaw,
  1079. ),
  1080. ),
  1081. // form_type_checkboxes_value() requires NULL instead of FALSE values
  1082. // for programmatic form submissions to disable a checkbox.
  1083. 'update_status_module' => array(
  1084. 1 => NULL,
  1085. 2 => NULL,
  1086. ),
  1087. ),
  1088. ),
  1089. );
  1090. return $parameters;
  1091. }
  1092. /**
  1093. * Generates a database prefix for running tests.
  1094. *
  1095. * The database prefix is used by prepareEnvironment() to setup a public files
  1096. * directory for the test to be run, which also contains the PHP error log,
  1097. * which is written to in case of a fatal error. Since that directory is based
  1098. * on the database prefix, all tests (even unit tests) need to have one, in
  1099. * order to access and read the error log.
  1100. *
  1101. * The generated database table prefix is used for the Drupal installation
  1102. * being performed for the test. It is also used by the cookie value of
  1103. * SIMPLETEST_USER_AGENT by the Mink controlled browser. During early Drupal
  1104. * bootstrap, the cookie is parsed, and if it matches, all database queries
  1105. * use the database table prefix that has been generated here.
  1106. *
  1107. * @see drupal_valid_test_ua()
  1108. * @see BrowserTestBase::prepareEnvironment()
  1109. */
  1110. private function prepareDatabasePrefix() {
  1111. $test_db = new TestDatabase();
  1112. $this->siteDirectory = $test_db->getTestSitePath();
  1113. $this->databasePrefix = $test_db->getDatabasePrefix();
  1114. }
  1115. /**
  1116. * Changes the database connection to the prefixed one.
  1117. *
  1118. * @see BrowserTestBase::prepareEnvironment()
  1119. */
  1120. private function changeDatabasePrefix() {
  1121. if (empty($this->databasePrefix)) {
  1122. $this->prepareDatabasePrefix();
  1123. }
  1124. // If the test is run with argument dburl then use it.
  1125. $db_url = getenv('SIMPLETEST_DB');
  1126. if (!empty($db_url)) {
  1127. $database = Database::convertDbUrlToConnectionInfo($db_url, DRUPAL_ROOT);
  1128. Database::addConnectionInfo('default', 'default', $database);
  1129. }
  1130. // Clone the current connection and replace the current prefix.
  1131. $connection_info = Database::getConnectionInfo('default');
  1132. if (is_null($connection_info)) {
  1133. throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh.');
  1134. }
  1135. else {
  1136. Database::renameConnection('default', 'simpletest_original_default');
  1137. foreach ($connection_info as $target => $value) {
  1138. // Replace the full table prefix definition to ensure that no table
  1139. // prefixes of the test runner leak into the test.
  1140. $connection_info[$target]['prefix'] = array(
  1141. 'default' => $value['prefix']['default'] . $this->databasePrefix,
  1142. );
  1143. }
  1144. Database::addConnectionInfo('default', 'default', $connection_info['default']);
  1145. }
  1146. }
  1147. /**
  1148. * Prepares the current environment for running the test.
  1149. *
  1150. * Also sets up new resources for the testing environment, such as the public
  1151. * filesystem and configuration directories.
  1152. *
  1153. * This method is private as it must only be called once by
  1154. * BrowserTestBase::setUp() (multiple invocations for the same test would have
  1155. * unpredictable consequences) and it must not be callable or overridable by
  1156. * test classes.
  1157. */
  1158. protected function prepareEnvironment() {
  1159. // Bootstrap Drupal so we can use Drupal's built in functions.
  1160. $this->classLoader = require __DIR__ . '/../../../../autoload.php';
  1161. $request = Request::createFromGlobals();
  1162. $kernel = TestRunnerKernel::createFromRequest($request, $this->classLoader);
  1163. // TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
  1164. chdir(DRUPAL_ROOT);
  1165. $kernel->prepareLegacyRequest($request);
  1166. $this->prepareDatabasePrefix();
  1167. $this->originalSiteDirectory = $kernel->findSitePath($request);
  1168. // Create test directory ahead of installation so fatal errors and debug
  1169. // information can be logged during installation process.
  1170. file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
  1171. // Prepare filesystem directory paths.
  1172. $this->publicFilesDirectory = $this->siteDirectory . '/files';
  1173. $this->privateFilesDirectory = $this->siteDirectory . '/private';
  1174. $this->tempFilesDirectory = $this->siteDirectory . '/temp';
  1175. $this->translationFilesDirectory = $this->siteDirectory . '/translations';
  1176. // Ensure the configImporter is refreshed for each test.
  1177. $this->configImporter = NULL;
  1178. // Unregister all custom stream wrappers of the parent site.
  1179. $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::ALL);
  1180. foreach ($wrappers as $scheme => $info) {
  1181. stream_wrapper_unregister($scheme);
  1182. }
  1183. // Reset statics.
  1184. drupal_static_reset();
  1185. // Ensure there is no service container.
  1186. $this->container = NULL;
  1187. \Drupal::unsetContainer();
  1188. // Unset globals.
  1189. unset($GLOBALS['config_directories']);
  1190. unset($GLOBALS['config']);
  1191. unset($GLOBALS['conf']);
  1192. // Log fatal errors.
  1193. ini_set('log_errors', 1);
  1194. ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
  1195. // Change the database prefix.
  1196. $this->changeDatabasePrefix();
  1197. // After preparing the environment and changing the database prefix, we are
  1198. // in a valid test environment.
  1199. drupal_valid_test_ua($this->databasePrefix);
  1200. // Reset settings.
  1201. new Settings(array(
  1202. // For performance, simply use the database prefix as hash salt.
  1203. 'hash_salt' => $this->databasePrefix,
  1204. ));
  1205. drupal_set_time_limit($this->timeLimit);
  1206. // Save and clean the shutdown callbacks array because it is static cached
  1207. // and will be changed by the test run. Otherwise it will contain callbacks
  1208. // from both environments and the testing environment will try to call the
  1209. // handlers defined by the original one.
  1210. $callbacks = &drupal_register_shutdown_function();
  1211. $this->originalShutdownCallbacks = $callbacks;
  1212. $callbacks = [];
  1213. }
  1214. /**
  1215. * Returns the database connection to the site running Simpletest.
  1216. *
  1217. * @return \Drupal\Core\Database\Connection
  1218. * The database connection to use for inserting assertions.
  1219. */
  1220. public static function getDatabaseConnection() {
  1221. return TestDatabase::getConnection();
  1222. }
  1223. /**
  1224. * Rewrites the settings.php file of the test site.
  1225. *
  1226. * @param array $settings
  1227. * An array of settings to write out, in the format expected by
  1228. * drupal_rewrite_settings().
  1229. *
  1230. * @see drupal_rewrite_settings()
  1231. */
  1232. protected function writeSettings(array $settings) {
  1233. include_once DRUPAL_ROOT . '/core/includes/install.inc';
  1234. $filename = $this->siteDirectory . '/settings.php';
  1235. // system_requirements() removes write permissions from settings.php
  1236. // whenever it is invoked.
  1237. // Not using File API; a potential error must trigger a PHP warning.
  1238. chmod($filename, 0666);
  1239. drupal_rewrite_settings($settings, $filename);
  1240. }
  1241. /**
  1242. * Rebuilds \Drupal::getContainer().
  1243. *
  1244. * Use this to build a new kernel and service container. For example, when the
  1245. * list of enabled modules is changed via the Mink controlled browser, in
  1246. * which case the test process still contains an old kernel and service
  1247. * container with an old module list.
  1248. *
  1249. * @see BrowserTestBase::prepareEnvironment()
  1250. * @see BrowserTestBase::restoreEnvironment()
  1251. *
  1252. * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
  1253. * changes are immediately reflected in \Drupal::getContainer(). Until then,
  1254. * tests can invoke this workaround when requiring services from newly
  1255. * enabled modules to be immediately available in the same request.
  1256. */
  1257. protected function rebuildContainer() {
  1258. // Rebuild the kernel and bring it back to a fully bootstrapped state.
  1259. $this->container = $this->kernel->rebuildContainer();
  1260. // Make sure the url generator has a request object, otherwise calls to
  1261. // $this->drupalGet() will fail.
  1262. $this->prepareRequestForGenerator();
  1263. }
  1264. /**
  1265. * Creates a mock request and sets it on the generator.
  1266. *
  1267. * This is used to manipulate how the generator generates paths during tests.
  1268. * It also ensures that calls to $this->drupalGet() will work when running
  1269. * from run-tests.sh because the url generator no longer looks at the global
  1270. * variables that are set there but relies on getting this information from a
  1271. * request object.
  1272. *
  1273. * @param bool $clean_urls
  1274. * Whether to mock the request using clean urls.
  1275. * @param array $override_server_vars
  1276. * An array of server variables to override.
  1277. *
  1278. * @return Request
  1279. * The mocked request object.
  1280. */
  1281. protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
  1282. $request = Request::createFromGlobals();
  1283. $server = $request->server->all();
  1284. if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
  1285. // We need this for when the test is executed by run-tests.sh.
  1286. // @todo Remove this once run-tests.sh has been converted to use a Request
  1287. // object.
  1288. $cwd = getcwd();
  1289. $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
  1290. $base_path = rtrim($server['REQUEST_URI'], '/');
  1291. }
  1292. else {
  1293. $base_path = $request->getBasePath();
  1294. }
  1295. if ($clean_urls) {
  1296. $request_path = $base_path ? $base_path . '/user' : 'user';
  1297. }
  1298. else {
  1299. $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
  1300. }
  1301. $server = array_merge($server, $override_server_vars);
  1302. $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
  1303. // Ensure the request time is REQUEST_TIME to ensure that API calls
  1304. // in the test use the right timestamp.
  1305. $request->server->set('REQUEST_TIME', REQUEST_TIME);
  1306. $this->container->get('request_stack')->push($request);
  1307. // The request context is normally set by the router_listener from within
  1308. // its KernelEvents::REQUEST listener. In the Simpletest parent site this
  1309. // event is not fired, therefore it is necessary to updated the request
  1310. // context manually here.
  1311. $this->container->get('router.request_context')->fromRequest($request);
  1312. return $request;
  1313. }
  1314. /**
  1315. * Resets all data structures after having enabled new modules.
  1316. *
  1317. * This method is called by \Drupal\simpletest\BrowserTestBase::setUp() after
  1318. * enabling the requested modules. It must be called again when additional
  1319. * modules are enabled later.
  1320. */
  1321. protected function resetAll() {
  1322. // Clear all database and static caches and rebuild data structures.
  1323. drupal_flush_all_caches();
  1324. $this->container = \Drupal::getContainer();
  1325. // Reset static variables and reload permissions.
  1326. $this->refreshVariables();
  1327. }
  1328. /**
  1329. * Refreshes in-memory configuration and state information.
  1330. *
  1331. * Useful after a page request is made that changes configuration or state in
  1332. * a different thread.
  1333. *
  1334. * In other words calling a settings page with $this->submitForm() with a
  1335. * changed value would update configuration to reflect that change, but in the
  1336. * thread that made the call (thread running the test) the changed values
  1337. * would not be picked up.
  1338. *
  1339. * This method clears the cache and loads a fresh copy.
  1340. */
  1341. protected function refreshVariables() {
  1342. // Clear the tag cache.
  1343. $this->container->get('cache_tags.invalidator')->resetChecksums();
  1344. // @todo Replace drupal_static() usage within classes and provide a
  1345. // proper interface for invoking reset() on a cache backend:
  1346. // https://www.drupal.org/node/2311945.
  1347. drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
  1348. drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags');
  1349. drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags');
  1350. foreach (Cache::getBins() as $backend) {
  1351. if (is_callable(array($backend, 'reset'))) {
  1352. $backend->reset();
  1353. }
  1354. }
  1355. $this->container->get('config.factory')->reset();
  1356. $this->container->get('state')->resetCache();
  1357. }
  1358. /**
  1359. * Returns whether a given user account is logged in.
  1360. *
  1361. * @param \Drupal\Core\Session\AccountInterface $account
  1362. * The user account object to check.
  1363. *
  1364. * @return bool
  1365. * Return TRUE if the user is logged in, FALSE otherwise.
  1366. */
  1367. protected function drupalUserIsLoggedIn(AccountInterface $account) {
  1368. $logged_in = FALSE;
  1369. if (isset($account->sessionId)) {
  1370. $session_handler = $this->container->get('session_handler.storage');
  1371. $logged_in = (bool) $session_handler->read($account->sessionId);
  1372. }
  1373. return $logged_in;
  1374. }
  1375. /**
  1376. * Clicks the element with the given CSS selector.
  1377. *
  1378. * @param string $css_selector
  1379. * The CSS selector identifying the element to click.
  1380. */
  1381. protected function click($css_selector) {
  1382. $this->getSession()->getDriver()->click($this->cssSelectToXpath($css_selector));
  1383. }
  1384. /**
  1385. * Prevents serializing any properties.
  1386. *
  1387. * Browser tests are run in a separate process. To do this PHPUnit creates a
  1388. * script to run the test. If it fails, the test result object will contain a
  1389. * stack trace which includes the test object. It will attempt to serialize
  1390. * it. Returning an empty array prevents it from serializing anything it
  1391. * should not.
  1392. *
  1393. * @return array
  1394. * An empty array.
  1395. *
  1396. * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
  1397. */
  1398. public function __sleep() {
  1399. return [];
  1400. }
  1401. /**
  1402. * Logs a HTML output message in a text file.
  1403. *
  1404. * The link to the HTML output message will be printed by the results printer.
  1405. *
  1406. * @param string $message
  1407. * The HTML output message to be stored.
  1408. *
  1409. * @see \Drupal\Tests\Listeners\VerbosePrinter::printResult()
  1410. */
  1411. protected function htmlOutput($message) {
  1412. if (!$this->htmlOutputEnabled) {
  1413. return;
  1414. }
  1415. $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;
  1416. $html_output_filename = $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '.html';
  1417. file_put_contents($this->htmlOutputDirectory . '/' . $html_output_filename, $message);
  1418. file_put_contents($this->htmlOutputCounterStorage, $this->htmlOutputCounter++);
  1419. file_put_contents($this->htmlOutputFile, file_create_url('sites/simpletest/browser_output/' . $html_output_filename) . "\n", FILE_APPEND);
  1420. }
  1421. /**
  1422. * Returns headers in HTML output format.
  1423. *
  1424. * @return string
  1425. * HTML output headers.
  1426. */
  1427. protected function getHtmlOutputHeaders() {
  1428. $headers = array_map(function($headers) {
  1429. if (is_array($headers)) {
  1430. return implode(';', array_map('trim', $headers));
  1431. }
  1432. else {
  1433. return $headers;
  1434. }
  1435. }, $this->getSession()->getResponseHeaders());
  1436. return '<hr />Headers: <pre>' . Html::escape(var_export($headers, TRUE)) . '</pre>';
  1437. }
  1438. /**
  1439. * Translates a CSS expression to its XPath equivalent.
  1440. *
  1441. * The search is relative to the root element (HTML tag normally) of the page.
  1442. *
  1443. * @param string $selector
  1444. * CSS selector to use in the search.
  1445. * @param bool $html
  1446. * (optional) Enables HTML support. Disable it for XML documents.
  1447. * @param string $prefix
  1448. * (optional) The prefix for the XPath expression.
  1449. *
  1450. * @return string
  1451. * The equivalent XPath of a CSS expression.
  1452. */
  1453. protected function cssSelectToXpath($selector, $html = TRUE, $prefix = 'descendant-or-self::') {
  1454. return (new CssSelectorConverter($html))->toXPath($selector, $prefix);
  1455. }
  1456. /**
  1457. * Searches elements using a CSS selector in the raw content.
  1458. *
  1459. * The search is relative to the root element (HTML tag normally) of the page.
  1460. *
  1461. * @param string $selector
  1462. * CSS selector to use in the search.
  1463. *
  1464. * @return \Behat\Mink\Element\NodeElement[]
  1465. * The list of elements on the page that match the selector.
  1466. */
  1467. protected function cssSelect($selector) {
  1468. return $this->getSession()->getPage()->findAll('css', $selector);
  1469. }
  1470. /**
  1471. * Follows a link by complete name.
  1472. *
  1473. * Will click the first link found with this link text.
  1474. *
  1475. * If the link is discovered and clicked, the test passes. Fail otherwise.
  1476. *
  1477. * @param string|\Drupal\Component\Render\MarkupInterface $label
  1478. * Text between the anchor tags.
  1479. * @param int $index
  1480. * (optional) The index number for cases where multiple links have the same
  1481. * text. Defaults to 0.
  1482. */
  1483. protected function clickLink($label, $index = 0) {
  1484. $label = (string) $label;
  1485. $links = $this->getSession()->getPage()->findAll('named', ['link', $label]);
  1486. $links[$index]->click();
  1487. }
  1488. /**
  1489. * Retrieves the plain-text content from the current page.
  1490. */
  1491. protected function getTextContent() {
  1492. return $this->getSession()->getPage()->getText();
  1493. }
  1494. /**
  1495. * Performs an xpath search on the contents of the internal browser.
  1496. *
  1497. * The search is relative to the root element (HTML tag normally) of the page.
  1498. *
  1499. * @param string $xpath
  1500. * The xpath string to use in the search.
  1501. * @param array $arguments
  1502. * An array of arguments with keys in the form ':name' matching the
  1503. * placeholders in the query. The values may be either strings or numeric
  1504. * values.
  1505. *
  1506. * @return \Behat\Mink\Element\NodeElement[]
  1507. * The list of elements matching the xpath expression.
  1508. */
  1509. protected function xpath($xpath, array $arguments = []) {
  1510. $xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments);
  1511. return $this->getSession()->getPage()->findAll('xpath', $xpath);
  1512. }
  1513. /**
  1514. * Configuration accessor for tests. Returns non-overridden configuration.
  1515. *
  1516. * @param string $name
  1517. * Configuration name.
  1518. *
  1519. * @return \Drupal\Core\Config\Config
  1520. * The configuration object with original configuration data.
  1521. */
  1522. protected function config($name) {
  1523. return $this->container->get('config.factory')->getEditable($name);
  1524. }
  1525. /**
  1526. * Gets the value of an HTTP response header.
  1527. *
  1528. * If multiple requests were required to retrieve the page, only the headers
  1529. * from the last request will be checked by default.
  1530. *
  1531. * @param string $name
  1532. * The name of the header to retrieve. Names are case-insensitive (see RFC
  1533. * 2616 section 4.2).
  1534. *
  1535. * @return string|null
  1536. * The HTTP header value or NULL if not found.
  1537. */
  1538. protected function drupalGetHeader($name) {
  1539. return $this->getSession()->getResponseHeader($name);
  1540. }
  1541. /**
  1542. * Get the current URL from the browser.
  1543. *
  1544. * @return string
  1545. * The current URL.
  1546. */
  1547. protected function getUrl() {
  1548. return $this->getSession()->getCurrentUrl();
  1549. }
  1550. /**
  1551. * Gets the JavaScript drupalSettings variable for the currently-loaded page.
  1552. *
  1553. * @return array
  1554. * The JSON decoded drupalSettings value from the current page.
  1555. */
  1556. protected function getDrupalSettings() {
  1557. $html = $this->getSession()->getPage()->getHtml();
  1558. if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $html, $matches)) {
  1559. return Json::decode($matches[1]);
  1560. }
  1561. return [];
  1562. }
  1563. /**
  1564. * {@inheritdoc}
  1565. */
  1566. public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
  1567. // Cast objects implementing MarkupInterface to string instead of
  1568. // relying on PHP casting them to string depending on what they are being
  1569. // comparing with.
  1570. $expected = static::castSafeStrings($expected);
  1571. $actual = static::castSafeStrings($actual);
  1572. parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
  1573. }
  1574. /**
  1575. * Changes parameters in the services.yml file.
  1576. *
  1577. * @param string $name
  1578. * The name of the parameter.
  1579. * @param mixed $value
  1580. * The value of the parameter.
  1581. */
  1582. protected function setContainerParameter($name, $value) {
  1583. $filename = $this->siteDirectory . '/services.yml';
  1584. chmod($filename, 0666);
  1585. $services = Yaml::decode(file_get_contents($filename));
  1586. $services['parameters'][$name] = $value;
  1587. file_put_contents($filename, Yaml::encode($services));
  1588. // Ensure that the cache is deleted for the yaml file loader.
  1589. $file_cache = FileCacheFactory::get('container_yaml_loader');
  1590. $file_cache->delete($filename);
  1591. }
  1592. /**
  1593. * Gets the config schema exclusions for this test.
  1594. *
  1595. * @return string[]
  1596. * An array of config object names that are excluded from schema checking.
  1597. */
  1598. protected function getConfigSchemaExclusions() {
  1599. $class = get_class($this);
  1600. $exceptions = [];
  1601. while ($class) {
  1602. if (property_exists($class, 'configSchemaCheckerExclusions')) {
  1603. $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
  1604. }
  1605. $class = get_parent_class($class);
  1606. }
  1607. // Filter out any duplicates.
  1608. return array_unique($exceptions);
  1609. }
  1610. }

Classes

Namesort descending Description
BrowserTestBase Provides a test case for functional Drupal tests.