BrowserTestBase.php 58 KB

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