BrowserTestBase.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  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\Selector\SelectorsHandler;
  7. use Behat\Mink\Session;
  8. use Drupal\Component\Serialization\Json;
  9. use Drupal\Core\Database\Database;
  10. use Drupal\Core\Test\FunctionalTestSetupTrait;
  11. use Drupal\Core\Test\TestSetupTrait;
  12. use Drupal\Core\Utility\Error;
  13. use Drupal\FunctionalTests\AssertLegacyTrait;
  14. use Drupal\Tests\block\Traits\BlockCreationTrait;
  15. use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
  16. use Drupal\Tests\node\Traits\NodeCreationTrait;
  17. use Drupal\Tests\user\Traits\UserCreationTrait;
  18. use GuzzleHttp\Cookie\CookieJar;
  19. use PHPUnit\Framework\TestCase;
  20. use Psr\Http\Message\RequestInterface;
  21. use Psr\Http\Message\ResponseInterface;
  22. use Symfony\Component\CssSelector\CssSelectorConverter;
  23. /**
  24. * Provides a test case for functional Drupal tests.
  25. *
  26. * Tests extending BrowserTestBase must exist in the
  27. * Drupal\Tests\yourmodule\Functional namespace and live in the
  28. * modules/yourmodule/tests/src/Functional directory.
  29. *
  30. * Tests extending this base class should only translate text when testing
  31. * translation functionality. For example, avoid wrapping test text with t()
  32. * or TranslatableMarkup().
  33. *
  34. * @ingroup testing
  35. */
  36. abstract class BrowserTestBase extends TestCase {
  37. use FunctionalTestSetupTrait;
  38. use UiHelperTrait {
  39. FunctionalTestSetupTrait::refreshVariables insteadof UiHelperTrait;
  40. }
  41. use TestSetupTrait;
  42. use BlockCreationTrait {
  43. placeBlock as drupalPlaceBlock;
  44. }
  45. use AssertLegacyTrait;
  46. use RandomGeneratorTrait;
  47. use NodeCreationTrait {
  48. getNodeByTitle as drupalGetNodeByTitle;
  49. createNode as drupalCreateNode;
  50. }
  51. use ContentTypeCreationTrait {
  52. createContentType as drupalCreateContentType;
  53. }
  54. use ConfigTestTrait;
  55. use TestRequirementsTrait;
  56. use UserCreationTrait {
  57. createRole as drupalCreateRole;
  58. createUser as drupalCreateUser;
  59. }
  60. use XdebugRequestTrait;
  61. use PhpunitCompatibilityTrait;
  62. /**
  63. * The database prefix of this test run.
  64. *
  65. * @var string
  66. */
  67. protected $databasePrefix;
  68. /**
  69. * Time limit in seconds for the test.
  70. *
  71. * @var int
  72. */
  73. protected $timeLimit = 500;
  74. /**
  75. * The translation file directory for the test environment.
  76. *
  77. * This is set in BrowserTestBase::prepareEnvironment().
  78. *
  79. * @var string
  80. */
  81. protected $translationFilesDirectory;
  82. /**
  83. * The config importer that can be used in a test.
  84. *
  85. * @var \Drupal\Core\Config\ConfigImporter
  86. */
  87. protected $configImporter;
  88. /**
  89. * Modules to enable.
  90. *
  91. * The test runner will merge the $modules lists from this class, the class
  92. * it extends, and so on up the class hierarchy. It is not necessary to
  93. * include modules in your list that a parent class has already declared.
  94. *
  95. * @var string[]
  96. *
  97. * @see \Drupal\Tests\BrowserTestBase::installDrupal()
  98. */
  99. protected static $modules = [];
  100. /**
  101. * The profile to install as a basis for testing.
  102. *
  103. * @var string
  104. */
  105. protected $profile = 'testing';
  106. /**
  107. * An array of custom translations suitable for drupal_rewrite_settings().
  108. *
  109. * @var array
  110. */
  111. protected $customTranslations;
  112. /*
  113. * Mink class for the default driver to use.
  114. *
  115. * Shoud be a fully qualified class name that implements
  116. * Behat\Mink\Driver\DriverInterface.
  117. *
  118. * Value can be overridden using the environment variable MINK_DRIVER_CLASS.
  119. *
  120. * @var string
  121. */
  122. protected $minkDefaultDriverClass = GoutteDriver::class;
  123. /*
  124. * Mink default driver params.
  125. *
  126. * If it's an array its contents are used as constructor params when default
  127. * Mink driver class is instantiated.
  128. *
  129. * Can be overridden using the environment variable MINK_DRIVER_ARGS. In this
  130. * case that variable should be a JSON array, for example:
  131. * '["firefox", null, "http://localhost:4444/wd/hub"]'.
  132. *
  133. *
  134. * @var array
  135. */
  136. protected $minkDefaultDriverArgs;
  137. /**
  138. * Mink session manager.
  139. *
  140. * This will not be initialized if there was an error during the test setup.
  141. *
  142. * @var \Behat\Mink\Mink|null
  143. */
  144. protected $mink;
  145. /**
  146. * {@inheritdoc}
  147. *
  148. * Browser tests are run in separate processes to prevent collisions between
  149. * code that may be loaded by tests.
  150. */
  151. protected $runTestInSeparateProcess = TRUE;
  152. /**
  153. * {@inheritdoc}
  154. */
  155. protected $preserveGlobalState = FALSE;
  156. /**
  157. * The base URL.
  158. *
  159. * @var string
  160. */
  161. protected $baseUrl;
  162. /**
  163. * The original array of shutdown function callbacks.
  164. *
  165. * @var array
  166. */
  167. protected $originalShutdownCallbacks = [];
  168. /**
  169. * The app root.
  170. *
  171. * @var string
  172. */
  173. protected $root;
  174. /**
  175. * The original container.
  176. *
  177. * Move this to \Drupal\Core\Test\FunctionalTestSetupTrait once TestBase no
  178. * longer provides the same value.
  179. *
  180. * @var \Symfony\Component\DependencyInjection\ContainerInterface
  181. */
  182. protected $originalContainer;
  183. /**
  184. * {@inheritdoc}
  185. */
  186. public function __construct($name = NULL, array $data = [], $dataName = '') {
  187. parent::__construct($name, $data, $dataName);
  188. $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
  189. }
  190. /**
  191. * Initializes Mink sessions.
  192. */
  193. protected function initMink() {
  194. $driver = $this->getDefaultDriverInstance();
  195. if ($driver instanceof GoutteDriver) {
  196. // Turn off curl timeout. Having a timeout is not a problem in a normal
  197. // test running, but it is a problem when debugging. Also, disable SSL
  198. // peer verification so that testing under HTTPS always works.
  199. /** @var \GuzzleHttp\Client $client */
  200. $client = $this->container->get('http_client_factory')->fromOptions([
  201. 'timeout' => NULL,
  202. 'verify' => FALSE,
  203. ]);
  204. // Inject a Guzzle middleware to generate debug output for every request
  205. // performed in the test.
  206. $handler_stack = $client->getConfig('handler');
  207. $handler_stack->push($this->getResponseLogHandler());
  208. $driver->getClient()->setClient($client);
  209. }
  210. $selectors_handler = new SelectorsHandler([
  211. 'hidden_field_selector' => new HiddenFieldSelector(),
  212. ]);
  213. $session = new Session($driver, $selectors_handler);
  214. $this->mink = new Mink();
  215. $this->mink->registerSession('default', $session);
  216. $this->mink->setDefaultSessionName('default');
  217. $this->registerSessions();
  218. $this->initFrontPage();
  219. return $session;
  220. }
  221. /**
  222. * Visits the front page when initializing Mink.
  223. *
  224. * According to the W3C WebDriver specification a cookie can only be set if
  225. * the cookie domain is equal to the domain of the active document. When the
  226. * browser starts up the active document is not our domain but 'about:blank'
  227. * or similar. To be able to set our User-Agent and Xdebug cookies at the
  228. * start of the test we now do a request to the front page so the active
  229. * document matches the domain.
  230. *
  231. * @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
  232. * @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975
  233. */
  234. protected function initFrontPage() {
  235. $session = $this->getSession();
  236. $session->visit($this->baseUrl);
  237. }
  238. /**
  239. * Gets an instance of the default Mink driver.
  240. *
  241. * @return Behat\Mink\Driver\DriverInterface
  242. * Instance of default Mink driver.
  243. *
  244. * @throws \InvalidArgumentException
  245. * When provided default Mink driver class can't be instantiated.
  246. */
  247. protected function getDefaultDriverInstance() {
  248. // Get default driver params from environment if available.
  249. if ($arg_json = $this->getMinkDriverArgs()) {
  250. $this->minkDefaultDriverArgs = json_decode($arg_json, TRUE);
  251. }
  252. // Get and check default driver class from environment if available.
  253. if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) {
  254. if (class_exists($minkDriverClass)) {
  255. $this->minkDefaultDriverClass = $minkDriverClass;
  256. }
  257. else {
  258. throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class.");
  259. }
  260. }
  261. if (is_array($this->minkDefaultDriverArgs)) {
  262. // Use ReflectionClass to instantiate class with received params.
  263. $reflector = new \ReflectionClass($this->minkDefaultDriverClass);
  264. $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
  265. }
  266. else {
  267. $driver = new $this->minkDefaultDriverClass();
  268. }
  269. return $driver;
  270. }
  271. /**
  272. * Get the Mink driver args from an environment variable, if it is set. Can
  273. * be overridden in a derived class so it is possible to use a different
  274. * value for a subset of tests, e.g. the JavaScript tests.
  275. *
  276. * @return string|false
  277. * The JSON-encoded argument string. False if it is not set.
  278. */
  279. protected function getMinkDriverArgs() {
  280. return getenv('MINK_DRIVER_ARGS');
  281. }
  282. /**
  283. * Provides a Guzzle middleware handler to log every response received.
  284. *
  285. * @return callable
  286. * The callable handler that will do the logging.
  287. */
  288. protected function getResponseLogHandler() {
  289. return function (callable $handler) {
  290. return function (RequestInterface $request, array $options) use ($handler) {
  291. return $handler($request, $options)
  292. ->then(function (ResponseInterface $response) use ($request) {
  293. if ($this->htmlOutputEnabled) {
  294. $caller = $this->getTestMethodCaller();
  295. $html_output = 'Called from ' . $caller['function'] . ' line ' . $caller['line'];
  296. $html_output .= '<hr />' . $request->getMethod() . ' request to: ' . $request->getUri();
  297. // On redirect responses (status code starting with '3') we need
  298. // to remove the meta tag that would do a browser refresh. We
  299. // don't want to redirect developers away when they look at the
  300. // debug output file in their browser.
  301. $body = $response->getBody();
  302. $status_code = (string) $response->getStatusCode();
  303. if ($status_code[0] === '3') {
  304. $body = preg_replace('#<meta http-equiv="refresh" content=.+/>#', '', $body, 1);
  305. }
  306. $html_output .= '<hr />' . $body;
  307. $html_output .= $this->formatHtmlOutputHeaders($response->getHeaders());
  308. $this->htmlOutput($html_output);
  309. }
  310. return $response;
  311. });
  312. };
  313. };
  314. }
  315. /**
  316. * Registers additional Mink sessions.
  317. *
  318. * Tests wishing to use a different driver or change the default driver should
  319. * override this method.
  320. *
  321. * @code
  322. * // Register a new session that uses the MinkPonyDriver.
  323. * $pony = new MinkPonyDriver();
  324. * $session = new Session($pony);
  325. * $this->mink->registerSession('pony', $session);
  326. * @endcode
  327. */
  328. protected function registerSessions() {}
  329. /**
  330. * {@inheritdoc}
  331. */
  332. protected function setUp() {
  333. // Installing Drupal creates 1000s of objects. Garbage collection of these
  334. // objects is expensive. This appears to be causing random segmentation
  335. // faults in PHP 5.x due to https://bugs.php.net/bug.php?id=72286. Once
  336. // Drupal is installed is rebuilt, garbage collection is re-enabled.
  337. $disable_gc = version_compare(PHP_VERSION, '7', '<') && gc_enabled();
  338. if ($disable_gc) {
  339. gc_collect_cycles();
  340. gc_disable();
  341. }
  342. parent::setUp();
  343. $this->setupBaseUrl();
  344. // Install Drupal test site.
  345. $this->prepareEnvironment();
  346. $this->installDrupal();
  347. // Setup Mink.
  348. $session = $this->initMink();
  349. $cookies = $this->extractCookiesFromRequest(\Drupal::request());
  350. foreach ($cookies as $cookie_name => $values) {
  351. foreach ($values as $value) {
  352. $session->setCookie($cookie_name, $value);
  353. }
  354. }
  355. // Set up the browser test output file.
  356. $this->initBrowserOutputFile();
  357. // If garbage collection was disabled prior to rebuilding container,
  358. // re-enable it.
  359. if ($disable_gc) {
  360. gc_enable();
  361. }
  362. // Ensure that the test is not marked as risky because of no assertions. In
  363. // PHPUnit 6 tests that only make assertions using $this->assertSession()
  364. // can be marked as risky.
  365. $this->addToAssertionCount(1);
  366. }
  367. /**
  368. * Ensures test files are deletable within file_unmanaged_delete_recursive().
  369. *
  370. * Some tests chmod generated files to be read only. During
  371. * BrowserTestBase::cleanupEnvironment() and other cleanup operations,
  372. * these files need to get deleted too.
  373. *
  374. * @param string $path
  375. * The file path.
  376. */
  377. public static function filePreDeleteCallback($path) {
  378. // When the webserver runs with the same system user as phpunit, we can
  379. // make read-only files writable again. If not, chmod will fail while the
  380. // file deletion still works if file permissions have been configured
  381. // correctly. Thus, we ignore any problems while running chmod.
  382. @chmod($path, 0700);
  383. }
  384. /**
  385. * Clean up the Simpletest environment.
  386. */
  387. protected function cleanupEnvironment() {
  388. // Remove all prefixed tables.
  389. $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
  390. $original_prefix = $original_connection_info['default']['prefix']['default'];
  391. $test_connection_info = Database::getConnectionInfo('default');
  392. $test_prefix = $test_connection_info['default']['prefix']['default'];
  393. if ($original_prefix != $test_prefix) {
  394. $tables = Database::getConnection()->schema()->findTables('%');
  395. foreach ($tables as $table) {
  396. if (Database::getConnection()->schema()->dropTable($table)) {
  397. unset($tables[$table]);
  398. }
  399. }
  400. }
  401. // Delete test site directory.
  402. file_unmanaged_delete_recursive($this->siteDirectory, [$this, 'filePreDeleteCallback']);
  403. }
  404. /**
  405. * {@inheritdoc}
  406. */
  407. protected function tearDown() {
  408. parent::tearDown();
  409. // Destroy the testing kernel.
  410. if (isset($this->kernel)) {
  411. $this->cleanupEnvironment();
  412. $this->kernel->shutdown();
  413. }
  414. // Ensure that internal logged in variable is reset.
  415. $this->loggedInUser = FALSE;
  416. if ($this->mink) {
  417. $this->mink->stopSessions();
  418. }
  419. // Restore original shutdown callbacks.
  420. if (function_exists('drupal_register_shutdown_function')) {
  421. $callbacks = &drupal_register_shutdown_function();
  422. $callbacks = $this->originalShutdownCallbacks;
  423. }
  424. }
  425. /**
  426. * Returns Mink session.
  427. *
  428. * @param string $name
  429. * (optional) Name of the session. Defaults to the active session.
  430. *
  431. * @return \Behat\Mink\Session
  432. * The active Mink session object.
  433. */
  434. public function getSession($name = NULL) {
  435. return $this->mink->getSession($name);
  436. }
  437. /**
  438. * Get session cookies from current session.
  439. *
  440. * @return \GuzzleHttp\Cookie\CookieJar
  441. * A cookie jar with the current session.
  442. */
  443. protected function getSessionCookies() {
  444. $domain = parse_url($this->getUrl(), PHP_URL_HOST);
  445. $session_id = $this->getSession()->getCookie($this->getSessionName());
  446. $cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
  447. return $cookies;
  448. }
  449. /**
  450. * Obtain the HTTP client for the system under test.
  451. *
  452. * Use this method for arbitrary HTTP requests to the site under test. For
  453. * most tests, you should not get the HTTP client and instead use navigation
  454. * methods such as drupalGet() and clickLink() in order to benefit from
  455. * assertions.
  456. *
  457. * Subclasses which substitute a different Mink driver should override this
  458. * method and provide a Guzzle client if the Mink driver provides one.
  459. *
  460. * @return \GuzzleHttp\ClientInterface
  461. * The client with BrowserTestBase configuration.
  462. *
  463. * @throws \RuntimeException
  464. * If the Mink driver does not support a Guzzle HTTP client, throw an
  465. * exception.
  466. */
  467. protected function getHttpClient() {
  468. /* @var $mink_driver \Behat\Mink\Driver\DriverInterface */
  469. $mink_driver = $this->getSession()->getDriver();
  470. if ($mink_driver instanceof GoutteDriver) {
  471. return $mink_driver->getClient()->getClient();
  472. }
  473. throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().');
  474. }
  475. /**
  476. * Helper function to get the options of select field.
  477. *
  478. * @param \Behat\Mink\Element\NodeElement|string $select
  479. * Name, ID, or Label of select field to assert.
  480. * @param \Behat\Mink\Element\Element $container
  481. * (optional) Container element to check against. Defaults to current page.
  482. *
  483. * @return array
  484. * Associative array of option keys and values.
  485. */
  486. protected function getOptions($select, Element $container = NULL) {
  487. if (is_string($select)) {
  488. $select = $this->assertSession()->selectExists($select, $container);
  489. }
  490. $options = [];
  491. /* @var \Behat\Mink\Element\NodeElement $option */
  492. foreach ($select->findAll('xpath', '//option') as $option) {
  493. $label = $option->getText();
  494. $value = $option->getAttribute('value') ?: $label;
  495. $options[$value] = $label;
  496. }
  497. return $options;
  498. }
  499. /**
  500. * Installs Drupal into the Simpletest site.
  501. */
  502. public function installDrupal() {
  503. $this->initUserSession();
  504. $this->prepareSettings();
  505. $this->doInstall();
  506. $this->initSettings();
  507. $container = $this->initKernel(\Drupal::request());
  508. $this->initConfig($container);
  509. $this->installModulesFromClassProperty($container);
  510. $this->rebuildAll();
  511. }
  512. /**
  513. * Prevents serializing any properties.
  514. *
  515. * Browser tests are run in a separate process. To do this PHPUnit creates a
  516. * script to run the test. If it fails, the test result object will contain a
  517. * stack trace which includes the test object. It will attempt to serialize
  518. * it. Returning an empty array prevents it from serializing anything it
  519. * should not.
  520. *
  521. * @return array
  522. * An empty array.
  523. *
  524. * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
  525. */
  526. public function __sleep() {
  527. return [];
  528. }
  529. /**
  530. * Translates a CSS expression to its XPath equivalent.
  531. *
  532. * The search is relative to the root element (HTML tag normally) of the page.
  533. *
  534. * @param string $selector
  535. * CSS selector to use in the search.
  536. * @param bool $html
  537. * (optional) Enables HTML support. Disable it for XML documents.
  538. * @param string $prefix
  539. * (optional) The prefix for the XPath expression.
  540. *
  541. * @return string
  542. * The equivalent XPath of a CSS expression.
  543. */
  544. protected function cssSelectToXpath($selector, $html = TRUE, $prefix = 'descendant-or-self::') {
  545. return (new CssSelectorConverter($html))->toXPath($selector, $prefix);
  546. }
  547. /**
  548. * Performs an xpath search on the contents of the internal browser.
  549. *
  550. * The search is relative to the root element (HTML tag normally) of the page.
  551. *
  552. * @param string $xpath
  553. * The xpath string to use in the search.
  554. * @param array $arguments
  555. * An array of arguments with keys in the form ':name' matching the
  556. * placeholders in the query. The values may be either strings or numeric
  557. * values.
  558. *
  559. * @return \Behat\Mink\Element\NodeElement[]
  560. * The list of elements matching the xpath expression.
  561. */
  562. protected function xpath($xpath, array $arguments = []) {
  563. $xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments);
  564. return $this->getSession()->getPage()->findAll('xpath', $xpath);
  565. }
  566. /**
  567. * Configuration accessor for tests. Returns non-overridden configuration.
  568. *
  569. * @param string $name
  570. * Configuration name.
  571. *
  572. * @return \Drupal\Core\Config\Config
  573. * The configuration object with original configuration data.
  574. */
  575. protected function config($name) {
  576. return $this->container->get('config.factory')->getEditable($name);
  577. }
  578. /**
  579. * Returns all response headers.
  580. *
  581. * @return array
  582. * The HTTP headers values.
  583. *
  584. * @deprecated Scheduled for removal in Drupal 9.0.0.
  585. * Use $this->getSession()->getResponseHeaders() instead.
  586. */
  587. protected function drupalGetHeaders() {
  588. return $this->getSession()->getResponseHeaders();
  589. }
  590. /**
  591. * Gets the value of an HTTP response header.
  592. *
  593. * If multiple requests were required to retrieve the page, only the headers
  594. * from the last request will be checked by default.
  595. *
  596. * @param string $name
  597. * The name of the header to retrieve. Names are case-insensitive (see RFC
  598. * 2616 section 4.2).
  599. *
  600. * @return string|null
  601. * The HTTP header value or NULL if not found.
  602. */
  603. protected function drupalGetHeader($name) {
  604. return $this->getSession()->getResponseHeader($name);
  605. }
  606. /**
  607. * Gets the JavaScript drupalSettings variable for the currently-loaded page.
  608. *
  609. * @return array
  610. * The JSON decoded drupalSettings value from the current page.
  611. */
  612. protected function getDrupalSettings() {
  613. $html = $this->getSession()->getPage()->getHtml();
  614. if (preg_match('@<script type="application/json" data-drupal-selector="drupal-settings-json">([^<]*)</script>@', $html, $matches)) {
  615. return Json::decode($matches[1]);
  616. }
  617. return [];
  618. }
  619. /**
  620. * {@inheritdoc}
  621. */
  622. public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
  623. // Cast objects implementing MarkupInterface to string instead of
  624. // relying on PHP casting them to string depending on what they are being
  625. // comparing with.
  626. $expected = static::castSafeStrings($expected);
  627. $actual = static::castSafeStrings($actual);
  628. parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
  629. }
  630. /**
  631. * Retrieves the current calling line in the class under test.
  632. *
  633. * @return array
  634. * An associative array with keys 'file', 'line' and 'function'.
  635. */
  636. protected function getTestMethodCaller() {
  637. $backtrace = debug_backtrace();
  638. // Find the test class that has the test method.
  639. while ($caller = Error::getLastCaller($backtrace)) {
  640. if (isset($caller['class']) && $caller['class'] === get_class($this)) {
  641. break;
  642. }
  643. // If the test method is implemented by a test class's parent then the
  644. // class name of $this will not be part of the backtrace.
  645. // In that case we process the backtrace until the caller is not a
  646. // subclass of $this and return the previous caller.
  647. if (isset($last_caller) && (!isset($caller['class']) || !is_subclass_of($this, $caller['class']))) {
  648. // Return the last caller since that has to be the test class.
  649. $caller = $last_caller;
  650. break;
  651. }
  652. // Otherwise we have not reached our test class yet: save the last caller
  653. // and remove an element from to backtrace to process the next call.
  654. $last_caller = $caller;
  655. array_shift($backtrace);
  656. }
  657. return $caller;
  658. }
  659. /**
  660. * Transforms a nested array into a flat array suitable for drupalPostForm().
  661. *
  662. * @param array $values
  663. * A multi-dimensional form values array to convert.
  664. *
  665. * @return array
  666. * The flattened $edit array suitable for BrowserTestBase::drupalPostForm().
  667. */
  668. protected function translatePostValues(array $values) {
  669. $edit = [];
  670. // The easiest and most straightforward way to translate values suitable for
  671. // BrowserTestBase::drupalPostForm() is to actually build the POST data
  672. // string and convert the resulting key/value pairs back into a flat array.
  673. $query = http_build_query($values);
  674. foreach (explode('&', $query) as $item) {
  675. list($key, $value) = explode('=', $item);
  676. $edit[urldecode($key)] = urldecode($value);
  677. }
  678. return $edit;
  679. }
  680. }