2016-04-20 16:56:34 +00:00
< ? php
namespace Drupal\Tests ;
use Behat\Mink\Driver\GoutteDriver ;
use Behat\Mink\Element\Element ;
use Behat\Mink\Mink ;
use Behat\Mink\Session ;
use Drupal\Component\Utility\Html ;
use Drupal\Component\Utility\SafeMarkup ;
use Drupal\Component\Utility\UrlHelper ;
use Drupal\Core\Cache\Cache ;
use Drupal\Core\Database\ConnectionNotDefinedException ;
use Drupal\Core\Database\Database ;
use Drupal\Core\DrupalKernel ;
use Drupal\Core\Session\AccountInterface ;
use Drupal\Core\Session\AnonymousUserSession ;
use Drupal\Core\Session\UserSession ;
use Drupal\Core\Site\Settings ;
use Drupal\Core\StreamWrapper\StreamWrapperInterface ;
use Drupal\Core\Test\TestRunnerKernel ;
use Drupal\Core\Url ;
use Drupal\user\Entity\Role ;
use Drupal\user\Entity\User ;
use Drupal\user\UserInterface ;
use Symfony\Component\CssSelector\CssSelector ;
use Symfony\Component\HttpFoundation\Request ;
/**
* Provides a test case for functional Drupal tests .
*
* Tests extending BrowserTestBase must exist in the
* Drupal\Tests\yourmodule\Functional namespace and live in the
* modules / yourmodule / Tests / Functional directory .
*
* @ ingroup testing
*/
abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
use RandomGeneratorTrait ;
use SessionTestTrait ;
/**
* Class loader .
*
* @ var object
*/
protected $classLoader ;
/**
* The site directory of this test run .
*
* @ var string
*/
protected $siteDirectory ;
/**
* The database prefix of this test run .
*
* @ var string
*/
protected $databasePrefix ;
/**
* The site directory of the original parent site .
*
* @ var string
*/
protected $originalSiteDirectory ;
/**
* Time limit in seconds for the test .
*
* @ var int
*/
protected $timeLimit = 500 ;
/**
* The public file directory for the test environment .
*
* This is set in BrowserTestBase :: prepareEnvironment () .
*
* @ var string
*/
protected $publicFilesDirectory ;
/**
* The private file directory for the test environment .
*
* This is set in BrowserTestBase :: prepareEnvironment () .
*
* @ var string
*/
protected $privateFilesDirectory ;
/**
* The temp file directory for the test environment .
*
* This is set in BrowserTestBase :: prepareEnvironment () .
*
* @ var string
*/
protected $tempFilesDirectory ;
/**
* The translation file directory for the test environment .
*
* This is set in BrowserTestBase :: prepareEnvironment () .
*
* @ var string
*/
protected $translationFilesDirectory ;
/**
* The DrupalKernel instance used in the test .
*
* @ var \Drupal\Core\DrupalKernel
*/
protected $kernel ;
/**
* The dependency injection container used in the test .
*
* @ var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container ;
/**
* The config importer that can be used in a test .
*
* @ var \Drupal\Core\Config\ConfigImporter
*/
protected $configImporter ;
/**
* The profile to install as a basis for testing .
*
* @ var string
*/
protected $profile = 'testing' ;
/**
* The current user logged in using the Mink controlled browser .
*
* @ var \Drupal\user\UserInterface
*/
protected $loggedInUser = FALSE ;
/**
* The root user .
*
* @ var \Drupal\Core\Session\UserSession
*/
protected $rootUser ;
/**
* The config directories used in this test .
*
* @ var array
*/
protected $configDirectories = array ();
/**
* An array of custom translations suitable for drupal_rewrite_settings () .
*
* @ var array
*/
protected $customTranslations ;
/*
* Mink class for the default driver to use .
*
* Shoud be a fully qualified class name that implements
* Behat\Mink\Driver\DriverInterface .
*
* Value can be overridden using the environment variable MINK_DRIVER_CLASS .
*
* @ var string .
*/
protected $minkDefaultDriverClass = GoutteDriver :: class ;
/*
* Mink default driver params .
*
* If it ' s an array its contents are used as constructor params when default
* Mink driver class is instantiated .
*
* Can be overridden using the environment variable MINK_DRIVER_ARGS . In this
* case that variable should be a JSON array , for example :
* '["firefox", null, "http://localhost:4444/wd/hub"]' .
*
*
* @ var array
*/
protected $minkDefaultDriverArgs ;
/**
* Mink session manager .
*
* @ var \Behat\Mink\Mink
*/
protected $mink ;
/**
* { @ inheritdoc }
*
* Browser tests are run in separate processes to prevent collisions between
* code that may be loaded by tests .
*/
protected $runTestInSeparateProcess = TRUE ;
/**
* { @ inheritdoc }
*/
protected $preserveGlobalState = FALSE ;
/**
* Class name for HTML output logging .
*
* @ var string
*/
protected $htmlOutputClassName ;
/**
* Directory name for HTML output logging .
*
* @ var string
*/
protected $htmlOutputDirectory ;
/**
* Counter storage for HTML output logging .
*
* @ var string
*/
protected $htmlOutputCounterStorage ;
/**
* Counter for HTML output logging .
*
* @ var int
*/
protected $htmlOutputCounter = 1 ;
/**
* HTML output output enabled .
*
* @ var bool
*/
protected $htmlOutputEnabled = FALSE ;
/**
* The file name to write the list of URLs to .
*
* This file is read by the PHPUnit result printer .
*
* @ var string
*
* @ see \Drupal\Tests\Listeners\HtmlOutputPrinter
*/
protected $htmlOutputFile ;
/**
* HTML output test ID .
*
* @ var int
*/
protected $htmlOutputTestId ;
/**
* The base URL .
*
* @ var string
*/
protected $baseUrl ;
/**
* Initializes Mink sessions .
*/
protected function initMink () {
$driver = $this -> getDefaultDriverInstance ();
if ( $driver instanceof GoutteDriver ) {
$driver -> getClient () -> setClient ( \Drupal :: httpClient ());
}
$session = new Session ( $driver );
$this -> mink = new Mink ();
$this -> mink -> registerSession ( 'default' , $session );
$this -> mink -> setDefaultSessionName ( 'default' );
$this -> registerSessions ();
// According to the W3C WebDriver specification a cookie can only be set if
// the cookie domain is equal to the domain of the active document. When the
// browser starts up the active document is not our domain but 'about:blank'
// or similar. To be able to set our User-Agent and Xdebug cookies at the
// start of the test we now do a request to the front page so the active
// document matches the domain.
// @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie
// @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975
$session = $this -> getSession ();
$session -> visit ( $this -> baseUrl );
return $session ;
}
/**
* Gets an instance of the default Mink driver .
*
* @ return Behat\Mink\Driver\DriverInterface
* Instance of default Mink driver .
*
* @ throws \InvalidArgumentException
* When provided default Mink driver class can ' t be instantiated .
*/
protected function getDefaultDriverInstance () {
// Get default driver params from environment if availables.
if ( $arg_json = getenv ( 'MINK_DRIVER_ARGS' )) {
$this -> minkDefaultDriverArgs = json_decode ( $arg_json );
}
// Get and check default driver class from environment if availables.
if ( $minkDriverClass = getenv ( 'MINK_DRIVER_CLASS' )) {
if ( class_exists ( $minkDriverClass )) {
$this -> minkDefaultDriverClass = $minkDriverClass ;
}
else {
throw new \InvalidArgumentException ( " Can't instantiate provided $minkDriverClass class by environment as default driver class. " );
}
}
if ( is_array ( $this -> minkDefaultDriverArgs )) {
// Use ReflectionClass to instantiate class with received params.
$reflector = new \ReflectionClass ( $this -> minkDefaultDriverClass );
$driver = $reflector -> newInstanceArgs ( $this -> minkDefaultDriverArgs );
}
else {
2016-05-04 21:35:41 +00:00
$driver = new $this -> minkDefaultDriverClass ();
2016-04-20 16:56:34 +00:00
}
return $driver ;
}
/**
* Registers additional Mink sessions .
*
* Tests wishing to use a different driver or change the default driver should
* override this method .
*
* @ code
* // Register a new session that uses the MinkPonyDriver.
* $pony = new MinkPonyDriver ();
* $session = new Session ( $pony );
* $this -> mink -> registerSession ( 'pony' , $session );
* @ endcode
*/
protected function registerSessions () {}
/**
* { @ inheritdoc }
*/
protected function setUp () {
global $base_url ;
parent :: setUp ();
// Get and set the domain of the environment we are running our test
// coverage against.
$base_url = getenv ( 'SIMPLETEST_BASE_URL' );
if ( ! $base_url ) {
throw new \Exception (
'You must provide a SIMPLETEST_BASE_URL environment variable to run some PHPUnit based functional tests.'
);
}
// Setup $_SERVER variable.
$parsed_url = parse_url ( $base_url );
$host = $parsed_url [ 'host' ] . ( isset ( $parsed_url [ 'port' ]) ? ':' . $parsed_url [ 'port' ] : '' );
$path = isset ( $parsed_url [ 'path' ]) ? rtrim ( rtrim ( $parsed_url [ 'path' ]), '/' ) : '' ;
$port = isset ( $parsed_url [ 'port' ]) ? $parsed_url [ 'port' ] : 80 ;
$this -> baseUrl = $base_url ;
// If the passed URL schema is 'https' then setup the $_SERVER variables
// properly so that testing will run under HTTPS.
if ( $parsed_url [ 'scheme' ] === 'https' ) {
$_SERVER [ 'HTTPS' ] = 'on' ;
}
$_SERVER [ 'HTTP_HOST' ] = $host ;
$_SERVER [ 'REMOTE_ADDR' ] = '127.0.0.1' ;
$_SERVER [ 'SERVER_ADDR' ] = '127.0.0.1' ;
$_SERVER [ 'SERVER_PORT' ] = $port ;
$_SERVER [ 'SERVER_SOFTWARE' ] = NULL ;
$_SERVER [ 'SERVER_NAME' ] = 'localhost' ;
$_SERVER [ 'REQUEST_URI' ] = $path . '/' ;
$_SERVER [ 'REQUEST_METHOD' ] = 'GET' ;
$_SERVER [ 'SCRIPT_NAME' ] = $path . '/index.php' ;
$_SERVER [ 'SCRIPT_FILENAME' ] = $path . '/index.php' ;
$_SERVER [ 'PHP_SELF' ] = $path . '/index.php' ;
$_SERVER [ 'HTTP_USER_AGENT' ] = 'Drupal command line' ;
// Install Drupal test site.
$this -> prepareEnvironment ();
$this -> installDrupal ();
// Setup Mink.
$session = $this -> initMink ();
// In order to debug web tests you need to either set a cookie, have the
// Xdebug session in the URL or set an environment variable in case of CLI
// requests. If the developer listens to connection when running tests, by
// default the cookie is not forwarded to the client side, so you cannot
// debug the code running on the test site. In order to make debuggers work
// this bit of information is forwarded. Make sure that the debugger listens
// to at least three external connections.
$request = \Drupal :: request ();
$cookie_params = $request -> cookies ;
if ( $cookie_params -> has ( 'XDEBUG_SESSION' )) {
$session -> setCookie ( 'XDEBUG_SESSION' , $cookie_params -> get ( 'XDEBUG_SESSION' ));
}
// For CLI requests, the information is stored in $_SERVER.
$server = $request -> server ;
if ( $server -> has ( 'XDEBUG_CONFIG' )) {
// $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...".
$pairs = explode ( ' ' , $server -> get ( 'XDEBUG_CONFIG' ));
foreach ( $pairs as $pair ) {
list ( $key , $value ) = explode ( '=' , $pair );
// Account for key-value pairs being separated by multiple spaces.
if ( trim ( $key ) == 'idekey' ) {
$session -> setCookie ( 'XDEBUG_SESSION' , trim ( $value ));
}
}
}
// Creates the directory to store browser output in if a file to write
// URLs to has been created by \Drupal\Tests\Listeners\HtmlOutputPrinter.
$browser_output_file = getenv ( 'BROWSERTEST_OUTPUT_FILE' );
$this -> htmlOutputEnabled = is_file ( $browser_output_file );
if ( $this -> htmlOutputEnabled ) {
$this -> htmlOutputFile = $browser_output_file ;
$this -> htmlOutputClassName = str_replace ( " \\ " , " _ " , get_called_class ());
$this -> htmlOutputDirectory = DRUPAL_ROOT . '/sites/simpletest/browser_output' ;
if ( file_prepare_directory ( $this -> htmlOutputDirectory , FILE_CREATE_DIRECTORY ) && ! file_exists ( $this -> htmlOutputDirectory . '/.htaccess' )) {
file_put_contents ( $this -> htmlOutputDirectory . '/.htaccess' , " <IfModule mod_expires.c> \n ExpiresActive Off \n </IfModule> \n " );
}
$this -> htmlOutputCounterStorage = $this -> htmlOutputDirectory . '/' . $this -> htmlOutputClassName . '.counter' ;
$this -> htmlOutputTestId = str_replace ( 'sites/simpletest/' , '' , $this -> siteDirectory );
if ( is_file ( $this -> htmlOutputCounterStorage )) {
$this -> htmlOutputCounter = max ( 1 , ( int ) file_get_contents ( $this -> htmlOutputCounterStorage )) + 1 ;
}
}
}
/**
* Ensures test files are deletable within file_unmanaged_delete_recursive () .
*
* Some tests chmod generated files to be read only . During
* BrowserTestBase :: cleanupEnvironment () and other cleanup operations ,
* these files need to get deleted too .
*
* @ param string $path
* The file path .
*/
public static function filePreDeleteCallback ( $path ) {
$success = @ chmod ( $path , 0700 );
if ( ! $success ) {
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. " );
}
}
/**
* Clean up the Simpletest environment .
*/
protected function cleanupEnvironment () {
// Remove all prefixed tables.
$original_connection_info = Database :: getConnectionInfo ( 'simpletest_original_default' );
$original_prefix = $original_connection_info [ 'default' ][ 'prefix' ][ 'default' ];
$test_connection_info = Database :: getConnectionInfo ( 'default' );
$test_prefix = $test_connection_info [ 'default' ][ 'prefix' ][ 'default' ];
if ( $original_prefix != $test_prefix ) {
$tables = Database :: getConnection () -> schema () -> findTables ( '%' );
foreach ( $tables as $table ) {
if ( Database :: getConnection () -> schema () -> dropTable ( $table )) {
unset ( $tables [ $table ]);
}
}
}
// Delete test site directory.
file_unmanaged_delete_recursive ( $this -> siteDirectory , array ( $this , 'filePreDeleteCallback' ));
}
/**
* { @ inheritdoc }
*/
protected function tearDown () {
parent :: tearDown ();
// Destroy the testing kernel.
if ( isset ( $this -> kernel )) {
$this -> cleanupEnvironment ();
$this -> kernel -> shutdown ();
}
// Ensure that internal logged in variable is reset.
$this -> loggedInUser = FALSE ;
if ( $this -> mink ) {
$this -> mink -> stopSessions ();
}
}
/**
* Returns Mink session .
*
* @ param string $name
* ( optional ) Name of the session . Defaults to the active session .
*
* @ return \Behat\Mink\Session
* The active Mink session object .
*/
public function getSession ( $name = NULL ) {
return $this -> mink -> getSession ( $name );
}
/**
* Returns WebAssert object .
*
* @ param string $name
* ( optional ) Name of the session . Defaults to the active session .
*
2016-05-04 21:35:41 +00:00
* @ return \Drupal\Tests\WebAssert
2016-04-20 16:56:34 +00:00
* A new web - assert option for asserting the presence of elements with .
*/
public function assertSession ( $name = NULL ) {
return new WebAssert ( $this -> getSession ( $name ));
}
/**
* Prepare for a request to testing site .
*
* The testing site is protected via a SIMPLETEST_USER_AGENT cookie that is
* checked by drupal_valid_test_ua () .
*
* @ see drupal_valid_test_ua ()
*/
protected function prepareRequest () {
$session = $this -> getSession ();
$session -> setCookie ( 'SIMPLETEST_USER_AGENT' , drupal_generate_test_ua ( $this -> databasePrefix ));
}
/**
* Retrieves a Drupal path or an absolute path .
*
* @ param string $path
* Drupal path or URL to load into Mink controlled browser .
* @ param array $options
* ( optional ) Options to be forwarded to the url generator .
*
* @ return string
* The retrieved HTML string , also available as $this -> getRawContent ()
*/
protected function drupalGet ( $path , array $options = array ()) {
$options [ 'absolute' ] = TRUE ;
// The URL generator service is not necessarily available yet; e.g., in
// interactive installer tests.
if ( $this -> container -> has ( 'url_generator' )) {
if ( UrlHelper :: isExternal ( $path )) {
$url = Url :: fromUri ( $path , $options ) -> toString ();
}
else {
// This is needed for language prefixing.
$options [ 'path_processing' ] = TRUE ;
$url = Url :: fromUri ( 'base:/' . $path , $options ) -> toString ();
}
}
else {
$url = $this -> getAbsoluteUrl ( $path );
}
$session = $this -> getSession ();
$this -> prepareRequest ();
$session -> visit ( $url );
$out = $session -> getPage () -> getContent ();
// Ensure that any changes to variables in the other thread are picked up.
$this -> refreshVariables ();
if ( $this -> htmlOutputEnabled ) {
$html_output = 'GET request to: ' . $url .
'<hr />Ending URL: ' . $this -> getSession () -> getCurrentUrl ();
$html_output .= '<hr />' . $out ;
$html_output .= $this -> getHtmlOutputHeaders ();
$this -> htmlOutput ( $html_output );
}
return $out ;
}
/**
* Takes a path and returns an absolute path .
*
* @ param string $path
* A path from the Mink controlled browser content .
*
* @ return string
* The $path with $base_url prepended , if necessary .
*/
protected function getAbsoluteUrl ( $path ) {
global $base_url , $base_path ;
$parts = parse_url ( $path );
if ( empty ( $parts [ 'host' ])) {
// Ensure that we have a string (and no xpath object).
$path = ( string ) $path ;
// Strip $base_path, if existent.
$length = strlen ( $base_path );
if ( substr ( $path , 0 , $length ) === $base_path ) {
$path = substr ( $path , $length );
}
// Ensure that we have an absolute path.
if ( empty ( $path ) || $path [ 0 ] !== '/' ) {
$path = '/' . $path ;
}
// Finally, prepend the $base_url.
$path = $base_url . $path ;
}
return $path ;
}
/**
* Creates a user with a given set of permissions .
*
* @ param array $permissions
* ( optional ) Array of permission names to assign to user . Note that the
* user always has the default permissions derived from the
* " authenticated users " role .
* @ param string $name
* ( optional ) The user name .
*
* @ return \Drupal\user\Entity\User | false
* A fully loaded user object with passRaw property , or FALSE if account
* creation fails .
*/
protected function drupalCreateUser ( array $permissions = array (), $name = NULL ) {
// Create a role with the given permission set, if any.
$rid = FALSE ;
if ( $permissions ) {
$rid = $this -> drupalCreateRole ( $permissions );
if ( ! $rid ) {
return FALSE ;
}
}
// Create a user assigned to that role.
$edit = array ();
$edit [ 'name' ] = ! empty ( $name ) ? $name : $this -> randomMachineName ();
$edit [ 'mail' ] = $edit [ 'name' ] . '@example.com' ;
$edit [ 'pass' ] = user_password ();
$edit [ 'status' ] = 1 ;
if ( $rid ) {
$edit [ 'roles' ] = array ( $rid );
}
$account = User :: create ( $edit );
$account -> save ();
$this -> assertNotNull ( $account -> id (), SafeMarkup :: format ( 'User created with name %name and pass %pass' , array ( '%name' => $edit [ 'name' ], '%pass' => $edit [ 'pass' ])));
if ( ! $account -> id ()) {
return FALSE ;
}
// Add the raw password so that we can log in as this user.
$account -> passRaw = $edit [ 'pass' ];
return $account ;
}
/**
* Creates a role with specified permissions .
*
* @ param array $permissions
* Array of permission names to assign to role .
* @ param string $rid
* ( optional ) The role ID ( machine name ) . Defaults to a random name .
* @ param string $name
* ( optional ) The label for the role . Defaults to a random string .
* @ param int $weight
* ( optional ) The weight for the role . Defaults NULL so that entity_create ()
* sets the weight to maximum + 1.
*
* @ return string
* Role ID of newly created role , or FALSE if role creation failed .
*/
protected function drupalCreateRole ( array $permissions , $rid = NULL , $name = NULL , $weight = NULL ) {
// Generate a random, lowercase machine name if none was passed.
if ( ! isset ( $rid )) {
$rid = strtolower ( $this -> randomMachineName ( 8 ));
}
// Generate a random label.
if ( ! isset ( $name )) {
// In the role UI role names are trimmed and random string can start or
// end with a space.
$name = trim ( $this -> randomString ( 8 ));
}
// Check the all the permissions strings are valid.
if ( ! $this -> checkPermissions ( $permissions )) {
return FALSE ;
}
// Create new role.
/* @var \Drupal\user\RoleInterface $role */
$role = Role :: create ( array (
'id' => $rid ,
'label' => $name ,
));
if ( ! is_null ( $weight )) {
$role -> set ( 'weight' , $weight );
}
$result = $role -> save ();
$this -> assertSame ( $result , SAVED_NEW , SafeMarkup :: format ( 'Created role ID @rid with name @name.' , array (
'@name' => var_export ( $role -> label (), TRUE ),
'@rid' => var_export ( $role -> id (), TRUE ),
)));
if ( $result === SAVED_NEW ) {
// Grant the specified permissions to the role, if any.
if ( ! empty ( $permissions )) {
user_role_grant_permissions ( $role -> id (), $permissions );
$assigned_permissions = entity_load ( 'user_role' , $role -> id ()) -> getPermissions ();
$missing_permissions = array_diff ( $permissions , $assigned_permissions );
if ( $missing_permissions ) {
$this -> fail ( SafeMarkup :: format ( 'Failed to create permissions: @perms' , array ( '@perms' => implode ( ', ' , $missing_permissions ))));
}
}
return $role -> id ();
}
return FALSE ;
}
/**
* Checks whether a given list of permission names is valid .
*
* @ param array $permissions
* The permission names to check .
*
* @ return bool
* TRUE if the permissions are valid , FALSE otherwise .
*/
protected function checkPermissions ( array $permissions ) {
$available = array_keys ( \Drupal :: service ( 'user.permissions' ) -> getPermissions ());
$valid = TRUE ;
foreach ( $permissions as $permission ) {
if ( ! in_array ( $permission , $available )) {
$this -> fail ( SafeMarkup :: format ( 'Invalid permission %permission.' , array ( '%permission' => $permission )));
$valid = FALSE ;
}
}
return $valid ;
}
/**
* Logs in a user using the Mink controlled browser .
*
* If a user is already logged in , then the current user is logged out before
* logging in the specified user .
*
* Please note that neither the current user nor the passed - in user object is
* populated with data of the logged in user . If you need full access to the
* user object after logging in , it must be updated manually . If you also need
* access to the plain - text password of the user ( set by drupalCreateUser ()),
* e . g . to log in the same user again , then it must be re - assigned manually .
* For example :
* @ code
* // Create a user.
* $account = $this -> drupalCreateUser ( array ());
* $this -> drupalLogin ( $account );
* // Load real user object.
* $pass_raw = $account -> passRaw ;
* $account = User :: load ( $account -> id ());
* $account -> passRaw = $pass_raw ;
* @ endcode
*
* @ param \Drupal\Core\Session\AccountInterface $account
* User object representing the user to log in .
*
* @ see drupalCreateUser ()
*/
protected function drupalLogin ( AccountInterface $account ) {
if ( $this -> loggedInUser ) {
$this -> drupalLogout ();
}
$this -> drupalGet ( 'user' );
$this -> assertSession () -> statusCodeEquals ( 200 );
$this -> submitForm ( array (
'name' => $account -> getUsername (),
'pass' => $account -> passRaw ,
), t ( 'Log in' ));
// @see BrowserTestBase::drupalUserIsLoggedIn()
$account -> sessionId = $this -> getSession () -> getCookie ( $this -> getSessionName ());
$this -> assertTrue ( $this -> drupalUserIsLoggedIn ( $account ), SafeMarkup :: format ( 'User %name successfully logged in.' , array ( 'name' => $account -> getUsername ())));
$this -> loggedInUser = $account ;
$this -> container -> get ( 'current_user' ) -> setAccount ( $account );
}
/**
* Logs a user out of the Mink controlled browser and confirms .
*
* Confirms logout by checking the login page .
*/
protected function drupalLogout () {
// Make a request to the logout page, and redirect to the user page, the
// idea being if you were properly logged out you should be seeing a login
// screen.
$assert_session = $this -> assertSession ();
$this -> drupalGet ( 'user/logout' , array ( 'query' => array ( 'destination' => 'user' )));
$assert_session -> statusCodeEquals ( 200 );
$assert_session -> fieldExists ( 'name' );
$assert_session -> fieldExists ( 'pass' );
// @see BrowserTestBase::drupalUserIsLoggedIn()
unset ( $this -> loggedInUser -> sessionId );
$this -> loggedInUser = FALSE ;
$this -> container -> get ( 'current_user' ) -> setAccount ( new AnonymousUserSession ());
}
/**
* Fills and submits a form .
*
* @ param array $edit
* Field data in an associative array . Changes the current input fields
* ( where possible ) to the values indicated .
*
* A checkbox can be set to TRUE to be checked and should be set to FALSE to
* be unchecked .
* @ param string $submit
* Value of the submit button whose click is to be emulated . For example ,
* t ( 'Save' ) . The processing of the request depends on this value . For
* example , a form may have one button with the value t ( 'Save' ) and another
* button with the value t ( 'Delete' ), and execute different code depending
* on which one is clicked .
* @ param string $form_html_id
* ( optional ) HTML ID of the form to be submitted . On some pages
* there are many identical forms , so just using the value of the submit
* button is not enough . For example : 'trigger-node-presave-assign-form' .
* Note that this is not the Drupal $form_id , but rather the HTML ID of the
* form , which is typically the same thing but with hyphens replacing the
* underscores .
*/
protected function submitForm ( array $edit , $submit , $form_html_id = NULL ) {
$assert_session = $this -> assertSession ();
// Get the form.
if ( isset ( $form_html_id )) {
$form = $assert_session -> elementExists ( 'xpath' , " //form[@id=' $form_html_id '] " );
$submit_button = $assert_session -> buttonExists ( $submit , $form );
$action = $form -> getAttribute ( 'action' );
}
else {
$submit_button = $assert_session -> buttonExists ( $submit );
$form = $assert_session -> elementExists ( 'xpath' , './ancestor::form' , $submit_button );
$action = $form -> getAttribute ( 'action' );
}
// Edit the form values.
foreach ( $edit as $name => $value ) {
$field = $assert_session -> fieldExists ( $name , $form );
$field -> setValue ( $value );
}
// Submit form.
$this -> prepareRequest ();
$submit_button -> press ();
// Ensure that any changes to variables in the other thread are picked up.
$this -> refreshVariables ();
if ( $this -> htmlOutputEnabled ) {
$out = $this -> getSession () -> getPage () -> getContent ();
$html_output = 'POST request to: ' . $action .
'<hr />Ending URL: ' . $this -> getSession () -> getCurrentUrl ();
$html_output .= '<hr />' . $out ;
$html_output .= $this -> getHtmlOutputHeaders ();
$this -> htmlOutput ( $html_output );
}
}
/**
* Helper function to get the options of select field .
*
* @ param \Behat\Mink\Element\NodeElement | string $select
* Name , ID , or Label of select field to assert .
* @ param \Behat\Mink\Element\Element $container
* ( optional ) Container element to check against . Defaults to current page .
*
* @ return array
* Associative array of option keys and values .
*/
protected function getOptions ( $select , Element $container = NULL ) {
if ( is_string ( $select )) {
$select = $this -> assertSession () -> selectExists ( $select , $container );
}
$options = [];
/* @var \Behat\Mink\Element\NodeElement $option */
foreach ( $select -> findAll ( 'xpath' , '//option' ) as $option ) {
$label = $option -> getText ();
$value = $option -> getAttribute ( 'value' ) ? : $label ;
$options [ $value ] = $label ;
}
return $options ;
}
/**
* Installs Drupal into the Simpletest site .
*/
public function installDrupal () {
// Define information about the user 1 account.
$this -> rootUser = new UserSession ( array (
'uid' => 1 ,
'name' => 'admin' ,
'mail' => 'admin@example.com' ,
'passRaw' => $this -> randomMachineName (),
));
// The child site derives its session name from the database prefix when
// running web tests.
$this -> generateSessionName ( $this -> databasePrefix );
// Get parameters for install_drupal() before removing global variables.
$parameters = $this -> installParameters ();
// Prepare installer settings that are not install_drupal() parameters.
// Copy and prepare an actual settings.php, so as to resemble a regular
// installation.
// Not using File API; a potential error must trigger a PHP warning.
$directory = DRUPAL_ROOT . '/' . $this -> siteDirectory ;
copy ( DRUPAL_ROOT . '/sites/default/default.settings.php' , $directory . '/settings.php' );
// All file system paths are created by System module during installation.
// @see system_requirements()
// @see TestBase::prepareEnvironment()
$settings [ 'settings' ][ 'file_public_path' ] = ( object ) array (
'value' => $this -> publicFilesDirectory ,
'required' => TRUE ,
);
$this -> writeSettings ( $settings );
// Allow for test-specific overrides.
$settings_testing_file = DRUPAL_ROOT . '/' . $this -> originalSiteDirectory . '/settings.testing.php' ;
if ( file_exists ( $settings_testing_file )) {
// Copy the testing-specific settings.php overrides in place.
copy ( $settings_testing_file , $directory . '/settings.testing.php' );
// Add the name of the testing class to settings.php and include the
// testing specific overrides.
file_put_contents ( $directory . '/settings.php' , " \n \$ test_class = ' " . get_class ( $this ) . " '; \n " . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . " \n " , FILE_APPEND );
}
$settings_services_file = DRUPAL_ROOT . '/' . $this -> originalSiteDirectory . '/testing.services.yml' ;
if ( file_exists ( $settings_services_file )) {
// Copy the testing-specific service overrides in place.
copy ( $settings_services_file , $directory . '/services.yml' );
}
// Since Drupal is bootstrapped already, install_begin_request() will not
// bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
// reload the newly written custom settings.php manually.
Settings :: initialize ( DRUPAL_ROOT , $directory , $this -> classLoader );
// Execute the non-interactive installer.
require_once DRUPAL_ROOT . '/core/includes/install.core.inc' ;
install_drupal ( $parameters );
// Import new settings.php written by the installer.
Settings :: initialize ( DRUPAL_ROOT , $directory , $this -> classLoader );
foreach ( $GLOBALS [ 'config_directories' ] as $type => $path ) {
$this -> configDirectories [ $type ] = $path ;
}
// After writing settings.php, the installer removes write permissions from
// the site directory. To allow drupal_generate_test_ua() to write a file
// containing the private key for drupal_valid_test_ua(), the site directory
// has to be writable.
// TestBase::restoreEnvironment() will delete the entire site directory. Not
// using File API; a potential error must trigger a PHP warning.
chmod ( $directory , 0777 );
$request = \Drupal :: request ();
$this -> kernel = DrupalKernel :: createFromRequest ( $request , $this -> classLoader , 'prod' , TRUE );
$this -> kernel -> prepareLegacyRequest ( $request );
// Force the container to be built from scratch instead of loaded from the
// disk. This forces us to not accidentally load the parent site.
$container = $this -> kernel -> rebuildContainer ();
$config = $container -> get ( 'config.factory' );
// Manually create and configure private and temporary files directories.
// While these could be preset/enforced in settings.php like the public
// files directory above, some tests expect them to be configurable in the
// UI. If declared in settings.php, they would no longer be configurable.
file_prepare_directory ( $this -> privateFilesDirectory , FILE_CREATE_DIRECTORY );
file_prepare_directory ( $this -> tempFilesDirectory , FILE_CREATE_DIRECTORY );
$config -> getEditable ( 'system.file' )
-> set ( 'path.private' , $this -> privateFilesDirectory )
-> set ( 'path.temporary' , $this -> tempFilesDirectory )
-> save ();
// Manually configure the test mail collector implementation to prevent
// tests from sending out emails and collect them in state instead.
// While this should be enforced via settings.php prior to installation,
// some tests expect to be able to test mail system implementations.
$config -> getEditable ( 'system.mail' )
-> set ( 'interface.default' , 'test_mail_collector' )
-> save ();
// By default, verbosely display all errors and disable all production
// environment optimizations for all tests to avoid needless overhead and
// ensure a sane default experience for test authors.
// @see https://www.drupal.org/node/2259167
$config -> getEditable ( 'system.logging' )
-> set ( 'error_level' , 'verbose' )
-> save ();
$config -> getEditable ( 'system.performance' )
-> set ( 'css.preprocess' , FALSE )
-> set ( 'js.preprocess' , FALSE )
-> save ();
// Collect modules to install.
$class = get_class ( $this );
$modules = array ();
while ( $class ) {
if ( property_exists ( $class , 'modules' )) {
$modules = array_merge ( $modules , $class :: $modules );
}
$class = get_parent_class ( $class );
}
if ( $modules ) {
$modules = array_unique ( $modules );
$success = $container -> get ( 'module_installer' ) -> install ( $modules , TRUE );
$this -> assertTrue ( $success , SafeMarkup :: format ( 'Enabled modules: %modules' , array ( '%modules' => implode ( ', ' , $modules ))));
$this -> rebuildContainer ();
}
// Reset/rebuild all data structures after enabling the modules, primarily
// to synchronize all data structures and caches between the test runner and
// the child site.
// Affects e.g. StreamWrapperManagerInterface::getWrappers().
// @see \Drupal\Core\DrupalKernel::bootCode()
// @todo Test-specific setUp() methods may set up further fixtures; find a
// way to execute this after setUp() is done, or to eliminate it entirely.
$this -> resetAll ();
$this -> kernel -> prepareLegacyRequest ( $request );
}
/**
* Returns the parameters that will be used when Simpletest installs Drupal .
*
* @ see install_drupal ()
* @ see install_state_defaults ()
*/
protected function installParameters () {
$connection_info = Database :: getConnectionInfo ();
$driver = $connection_info [ 'default' ][ 'driver' ];
$connection_info [ 'default' ][ 'prefix' ] = $connection_info [ 'default' ][ 'prefix' ][ 'default' ];
unset ( $connection_info [ 'default' ][ 'driver' ]);
unset ( $connection_info [ 'default' ][ 'namespace' ]);
unset ( $connection_info [ 'default' ][ 'pdo' ]);
unset ( $connection_info [ 'default' ][ 'init_commands' ]);
$parameters = array (
'interactive' => FALSE ,
'parameters' => array (
'profile' => $this -> profile ,
'langcode' => 'en' ,
),
'forms' => array (
'install_settings_form' => array (
'driver' => $driver ,
$driver => $connection_info [ 'default' ],
),
'install_configure_form' => array (
'site_name' => 'Drupal' ,
'site_mail' => 'simpletest@example.com' ,
'account' => array (
'name' => $this -> rootUser -> name ,
'mail' => $this -> rootUser -> getEmail (),
'pass' => array (
'pass1' => $this -> rootUser -> passRaw ,
'pass2' => $this -> rootUser -> passRaw ,
),
),
// form_type_checkboxes_value() requires NULL instead of FALSE values
// for programmatic form submissions to disable a checkbox.
'update_status_module' => array (
1 => NULL ,
2 => NULL ,
),
),
),
);
return $parameters ;
}
/**
* Generates a database prefix for running tests .
*
* The database prefix is used by prepareEnvironment () to setup a public files
* directory for the test to be run , which also contains the PHP error log ,
* which is written to in case of a fatal error . Since that directory is based
* on the database prefix , all tests ( even unit tests ) need to have one , in
* order to access and read the error log .
*
* The generated database table prefix is used for the Drupal installation
* being performed for the test . It is also used by the cookie value of
* SIMPLETEST_USER_AGENT by the Mink controlled browser . During early Drupal
* bootstrap , the cookie is parsed , and if it matches , all database queries
* use the database table prefix that has been generated here .
*
* @ see drupal_valid_test_ua ()
* @ see BrowserTestBase :: prepareEnvironment ()
*/
private function prepareDatabasePrefix () {
// Ensure that the generated test site directory does not exist already,
// which may happen with a large amount of concurrent threads and
// long-running tests.
do {
$suffix = mt_rand ( 100000 , 999999 );
$this -> siteDirectory = 'sites/simpletest/' . $suffix ;
$this -> databasePrefix = 'simpletest' . $suffix ;
} while ( is_dir ( DRUPAL_ROOT . '/' . $this -> siteDirectory ));
}
/**
* Changes the database connection to the prefixed one .
*
* @ see BrowserTestBase :: prepareEnvironment ()
*/
private function changeDatabasePrefix () {
if ( empty ( $this -> databasePrefix )) {
$this -> prepareDatabasePrefix ();
}
// If the test is run with argument dburl then use it.
$db_url = getenv ( 'SIMPLETEST_DB' );
if ( ! empty ( $db_url )) {
$database = Database :: convertDbUrlToConnectionInfo ( $db_url , DRUPAL_ROOT );
Database :: addConnectionInfo ( 'default' , 'default' , $database );
}
// Clone the current connection and replace the current prefix.
$connection_info = Database :: getConnectionInfo ( 'default' );
if ( is_null ( $connection_info )) {
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.' );
}
else {
Database :: renameConnection ( 'default' , 'simpletest_original_default' );
foreach ( $connection_info as $target => $value ) {
// Replace the full table prefix definition to ensure that no table
// prefixes of the test runner leak into the test.
$connection_info [ $target ][ 'prefix' ] = array (
'default' => $value [ 'prefix' ][ 'default' ] . $this -> databasePrefix ,
);
}
Database :: addConnectionInfo ( 'default' , 'default' , $connection_info [ 'default' ]);
}
}
/**
* Prepares the current environment for running the test .
*
* Also sets up new resources for the testing environment , such as the public
* filesystem and configuration directories .
*
* This method is private as it must only be called once by
* BrowserTestBase :: setUp () ( multiple invocations for the same test would have
* unpredictable consequences ) and it must not be callable or overridable by
* test classes .
*/
protected function prepareEnvironment () {
// Bootstrap Drupal so we can use Drupal's built in functions.
$this -> classLoader = require __DIR__ . '/../../../../autoload.php' ;
$request = Request :: createFromGlobals ();
$kernel = TestRunnerKernel :: createFromRequest ( $request , $this -> classLoader );
// TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
chdir ( DRUPAL_ROOT );
$kernel -> prepareLegacyRequest ( $request );
$this -> prepareDatabasePrefix ();
$this -> originalSiteDirectory = $kernel -> findSitePath ( $request );
// Create test directory ahead of installation so fatal errors and debug
// information can be logged during installation process.
file_prepare_directory ( $this -> siteDirectory , FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS );
// Prepare filesystem directory paths.
$this -> publicFilesDirectory = $this -> siteDirectory . '/files' ;
$this -> privateFilesDirectory = $this -> siteDirectory . '/private' ;
$this -> tempFilesDirectory = $this -> siteDirectory . '/temp' ;
$this -> translationFilesDirectory = $this -> siteDirectory . '/translations' ;
// Ensure the configImporter is refreshed for each test.
$this -> configImporter = NULL ;
// Unregister all custom stream wrappers of the parent site.
$wrappers = \Drupal :: service ( 'stream_wrapper_manager' ) -> getWrappers ( StreamWrapperInterface :: ALL );
foreach ( $wrappers as $scheme => $info ) {
stream_wrapper_unregister ( $scheme );
}
// Reset statics.
drupal_static_reset ();
// Ensure there is no service container.
$this -> container = NULL ;
\Drupal :: unsetContainer ();
// Unset globals.
unset ( $GLOBALS [ 'config_directories' ]);
unset ( $GLOBALS [ 'config' ]);
unset ( $GLOBALS [ 'conf' ]);
// Log fatal errors.
ini_set ( 'log_errors' , 1 );
ini_set ( 'error_log' , DRUPAL_ROOT . '/' . $this -> siteDirectory . '/error.log' );
// Change the database prefix.
$this -> changeDatabasePrefix ();
// After preparing the environment and changing the database prefix, we are
// in a valid test environment.
drupal_valid_test_ua ( $this -> databasePrefix );
// Reset settings.
new Settings ( array (
// For performance, simply use the database prefix as hash salt.
'hash_salt' => $this -> databasePrefix ,
));
drupal_set_time_limit ( $this -> timeLimit );
}
/**
* Returns the database connection to the site running Simpletest .
*
* @ return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions .
*/
public static function getDatabaseConnection () {
// Check whether there is a test runner connection.
// @see run-tests.sh
try {
$connection = Database :: getConnection ( 'default' , 'test-runner' );
}
catch ( ConnectionNotDefinedException $e ) {
// Check whether there is a backup of the original default connection.
// @see BrowserTestBase::prepareEnvironment()
try {
$connection = Database :: getConnection ( 'default' , 'simpletest_original_default' );
}
catch ( ConnectionNotDefinedException $e ) {
// If BrowserTestBase::prepareEnvironment() or
// BrowserTestBase::restoreEnvironment() failed, the test-specific
// database connection does not exist yet/anymore, so fall back to the
// default of the (UI) test runner.
$connection = Database :: getConnection ( 'default' , 'default' );
}
}
return $connection ;
}
/**
* Rewrites the settings . php file of the test site .
*
* @ param array $settings
* An array of settings to write out , in the format expected by
* drupal_rewrite_settings () .
*
* @ see drupal_rewrite_settings ()
*/
protected function writeSettings ( array $settings ) {
include_once DRUPAL_ROOT . '/core/includes/install.inc' ;
$filename = $this -> siteDirectory . '/settings.php' ;
// system_requirements() removes write permissions from settings.php
// whenever it is invoked.
// Not using File API; a potential error must trigger a PHP warning.
chmod ( $filename , 0666 );
drupal_rewrite_settings ( $settings , $filename );
}
/**
* Rebuilds \Drupal :: getContainer () .
*
* Use this to build a new kernel and service container . For example , when the
* list of enabled modules is changed via the Mink controlled browser , in
* which case the test process still contains an old kernel and service
* container with an old module list .
*
* @ see BrowserTestBase :: prepareEnvironment ()
* @ see BrowserTestBase :: restoreEnvironment ()
*
* @ todo Fix https :// www . drupal . org / node / 2021959 so that module enable / disable
* changes are immediately reflected in \Drupal :: getContainer () . Until then ,
* tests can invoke this workaround when requiring services from newly
* enabled modules to be immediately available in the same request .
*/
protected function rebuildContainer () {
// Rebuild the kernel and bring it back to a fully bootstrapped state.
$this -> container = $this -> kernel -> rebuildContainer ();
// Make sure the url generator has a request object, otherwise calls to
// $this->drupalGet() will fail.
$this -> prepareRequestForGenerator ();
}
/**
* Creates a mock request and sets it on the generator .
*
* This is used to manipulate how the generator generates paths during tests .
* It also ensures that calls to $this -> drupalGet () will work when running
* from run - tests . sh because the url generator no longer looks at the global
* variables that are set there but relies on getting this information from a
* request object .
*
* @ param bool $clean_urls
* Whether to mock the request using clean urls .
* @ param array $override_server_vars
* An array of server variables to override .
*
* @ return Request
* The mocked request object .
*/
protected function prepareRequestForGenerator ( $clean_urls = TRUE , $override_server_vars = array ()) {
$request = Request :: createFromGlobals ();
$server = $request -> server -> all ();
if ( basename ( $server [ 'SCRIPT_FILENAME' ]) != basename ( $server [ 'SCRIPT_NAME' ])) {
// We need this for when the test is executed by run-tests.sh.
// @todo Remove this once run-tests.sh has been converted to use a Request
// object.
$cwd = getcwd ();
$server [ 'SCRIPT_FILENAME' ] = $cwd . '/' . basename ( $server [ 'SCRIPT_NAME' ]);
$base_path = rtrim ( $server [ 'REQUEST_URI' ], '/' );
}
else {
$base_path = $request -> getBasePath ();
}
if ( $clean_urls ) {
$request_path = $base_path ? $base_path . '/user' : 'user' ;
}
else {
$request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user' ;
}
$server = array_merge ( $server , $override_server_vars );
$request = Request :: create ( $request_path , 'GET' , array (), array (), array (), $server );
// Ensure the request time is REQUEST_TIME to ensure that API calls
// in the test use the right timestamp.
$request -> server -> set ( 'REQUEST_TIME' , REQUEST_TIME );
$this -> container -> get ( 'request_stack' ) -> push ( $request );
// The request context is normally set by the router_listener from within
// its KernelEvents::REQUEST listener. In the Simpletest parent site this
// event is not fired, therefore it is necessary to updated the request
// context manually here.
$this -> container -> get ( 'router.request_context' ) -> fromRequest ( $request );
return $request ;
}
/**
* Resets all data structures after having enabled new modules .
*
* This method is called by \Drupal\simpletest\BrowserTestBase :: setUp () after
* enabling the requested modules . It must be called again when additional
* modules are enabled later .
*/
protected function resetAll () {
// Clear all database and static caches and rebuild data structures.
drupal_flush_all_caches ();
$this -> container = \Drupal :: getContainer ();
// Reset static variables and reload permissions.
$this -> refreshVariables ();
}
/**
* Refreshes in - memory configuration and state information .
*
* Useful after a page request is made that changes configuration or state in
* a different thread .
*
* In other words calling a settings page with $this -> submitForm () with a
* changed value would update configuration to reflect that change , but in the
* thread that made the call ( thread running the test ) the changed values
* would not be picked up .
*
* This method clears the cache and loads a fresh copy .
*/
protected function refreshVariables () {
// Clear the tag cache.
// @todo Replace drupal_static() usage within classes and provide a
// proper interface for invoking reset() on a cache backend:
// https://www.drupal.org/node/2311945.
drupal_static_reset ( 'Drupal\Core\Cache\CacheBackendInterface::tagCache' );
drupal_static_reset ( 'Drupal\Core\Cache\DatabaseBackend::deletedTags' );
drupal_static_reset ( 'Drupal\Core\Cache\DatabaseBackend::invalidatedTags' );
foreach ( Cache :: getBins () as $backend ) {
if ( is_callable ( array ( $backend , 'reset' ))) {
$backend -> reset ();
}
}
$this -> container -> get ( 'config.factory' ) -> reset ();
$this -> container -> get ( 'state' ) -> resetCache ();
}
/**
* Returns whether a given user account is logged in .
*
* @ param \Drupal\user\UserInterface $account
* The user account object to check .
*
* @ return bool
* Return TRUE if the user is logged in , FALSE otherwise .
*/
protected function drupalUserIsLoggedIn ( UserInterface $account ) {
$logged_in = FALSE ;
if ( isset ( $account -> sessionId )) {
$session_handler = $this -> container -> get ( 'session_handler.storage' );
$logged_in = ( bool ) $session_handler -> read ( $account -> sessionId );
}
return $logged_in ;
}
/**
* Asserts that the element with the given CSS selector is present .
*
* @ param string $css_selector
* The CSS selector identifying the element to check .
* @ param string $message
* Optional message to show alongside the assertion .
*/
protected function assertElementPresent ( $css_selector , $message = '' ) {
$this -> assertNotEmpty ( $this -> getSession () -> getDriver () -> find ( CssSelector :: toXPath ( $css_selector )), $message );
}
/**
* Asserts that the element with the given CSS selector is not present .
*
* @ param string $css_selector
* The CSS selector identifying the element to check .
* @ param string $message
* Optional message to show alongside the assertion .
*/
protected function assertElementNotPresent ( $css_selector , $message = '' ) {
$this -> assertEmpty ( $this -> getSession () -> getDriver () -> find ( CssSelector :: toXPath ( $css_selector )), $message );
}
/**
* Clicks the element with the given CSS selector .
*
* @ param string $css_selector
* The CSS selector identifying the element to click .
*/
protected function click ( $css_selector ) {
$this -> getSession () -> getDriver () -> click ( CssSelector :: toXPath ( $css_selector ));
}
/**
* Prevents serializing any properties .
*
* Browser tests are run in a separate process . To do this PHPUnit creates a
* script to run the test . If it fails , the test result object will contain a
* stack trace which includes the test object . It will attempt to serialize
* it . Returning an empty array prevents it from serializing anything it
* should not .
*
* @ return array
* An empty array .
*
* @ see vendor / phpunit / phpunit / src / Util / PHP / Template / TestCaseMethod . tpl . dist
*/
public function __sleep () {
return [];
}
/**
* Logs a HTML output message in a text file .
*
* The link to the HTML output message will be printed by the results printer .
*
* @ param string $message
* The HTML output message to be stored .
*
* @ see \Drupal\Tests\Listeners\VerbosePrinter :: printResult ()
*/
protected function htmlOutput ( $message ) {
if ( ! $this -> htmlOutputEnabled ) {
return ;
}
$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 ;
$html_output_filename = $this -> htmlOutputClassName . '-' . $this -> htmlOutputCounter . '-' . $this -> htmlOutputTestId . '.html' ;
file_put_contents ( $this -> htmlOutputDirectory . '/' . $html_output_filename , $message );
file_put_contents ( $this -> htmlOutputCounterStorage , $this -> htmlOutputCounter ++ );
file_put_contents ( $this -> htmlOutputFile , file_create_url ( 'sites/simpletest/browser_output/' . $html_output_filename ) . " \n " , FILE_APPEND );
}
/**
* Returns headers in HTML output format .
*
* @ return string
* HTML output headers .
*/
protected function getHtmlOutputHeaders () {
$headers = array_map ( function ( $headers ) {
if ( is_array ( $headers )) {
return implode ( ';' , array_map ( 'trim' , $headers ));
}
else {
return $headers ;
}
}, $this -> getSession () -> getResponseHeaders ());
return '<hr />Headers: <pre>' . Html :: escape ( var_export ( $headers , TRUE )) . '</pre>' ;
}
}