Issue #2232861 by grom358, daffie, alexpott, larowlan, pfrenssen, hussainweb, pcambra, jibran, phenaproxima, moshe weitzman, nick_schuch: Create BrowserTestBase for web-testing on top of Mink
parent
418497eef1
commit
300f14e860
|
@ -725,9 +725,12 @@ function drupal_valid_test_ua($new_prefix = NULL) {
|
|||
// a test environment.
|
||||
$test_prefix = FALSE;
|
||||
|
||||
// Perform a basic check on the User-Agent HTTP request header first. Any
|
||||
// inbound request that uses the simpletest UA header needs to be validated.
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
|
||||
// A valid Simpletest request will contain a hashed and salted authentication
|
||||
// code. Check if this code is present in a cookie or custom user agent
|
||||
// string.
|
||||
$http_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL;
|
||||
$user_agent = isset($_COOKIE['SIMPLETEST_USER_AGENT']) ? $_COOKIE['SIMPLETEST_USER_AGENT'] : $http_user_agent;
|
||||
if (isset($user_agent) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $user_agent, $matches)) {
|
||||
list(, $prefix, $time, $salt, $hmac) = $matches;
|
||||
$check_string = $prefix . ';' . $time . ';' . $salt;
|
||||
// Read the hash salt prepared by drupal_generate_test_ua().
|
||||
|
|
|
@ -303,7 +303,8 @@ function install_begin_request($class_loader, &$install_state) {
|
|||
// The user agent header is used to pass a database prefix in the request when
|
||||
// running tests. However, for security reasons, it is imperative that no
|
||||
// installation be permitted using such a prefix.
|
||||
if ($install_state['interactive'] && strpos($request->server->get('HTTP_USER_AGENT'), 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
|
||||
$user_agent = $request->cookies->get('SIMPLETEST_USER_AGENT') ?: $request->server->get('HTTP_USER_AGENT');
|
||||
if ($install_state['interactive'] && strpos($user_agent, 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
|
||||
header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
|
|
@ -443,4 +443,89 @@ abstract class Database {
|
|||
public static function ignoreTarget($key, $target) {
|
||||
self::$ignoreTargets[$key][$target] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a URL to a database connection info array.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL.
|
||||
* @param string $root
|
||||
* The root directory of the Drupal installation.
|
||||
*
|
||||
* @return array
|
||||
* The database connection info.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Exception thrown when the provided URL does not meet the minimum
|
||||
* requirements.
|
||||
*/
|
||||
public static function convertDbUrlToConnectionInfo($url, $root) {
|
||||
$info = parse_url($url);
|
||||
if (!isset($info['scheme'], $info['host'], $info['path'])) {
|
||||
throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
|
||||
}
|
||||
$info += array(
|
||||
'user' => '',
|
||||
'pass' => '',
|
||||
'fragment' => '',
|
||||
);
|
||||
|
||||
// A SQLite database path with two leading slashes indicates a system path.
|
||||
// Otherwise the path is relative to the Drupal root.
|
||||
if ($info['path'][0] === '/') {
|
||||
$info['path'] = substr($info['path'], 1);
|
||||
}
|
||||
if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
|
||||
$info['path'] = $root . '/' . $info['path'];
|
||||
}
|
||||
|
||||
$database = array(
|
||||
'driver' => $info['scheme'],
|
||||
'username' => $info['user'],
|
||||
'password' => $info['pass'],
|
||||
'host' => $info['host'],
|
||||
'database' => $info['path'],
|
||||
);
|
||||
if (isset($info['port'])) {
|
||||
$database['port'] = $info['port'];
|
||||
}
|
||||
return $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets database connection info as a URL.
|
||||
*
|
||||
* @param string $key
|
||||
* (Optional) The database connection key.
|
||||
*
|
||||
* @return string
|
||||
* The connection info as a URL.
|
||||
*/
|
||||
public static function getConnectionInfoAsUrl($key = 'default') {
|
||||
$db_info = static::getConnectionInfo($key);
|
||||
if ($db_info['default']['driver'] == 'sqlite') {
|
||||
$db_url = 'sqlite://localhost/' . $db_info['default']['database'];
|
||||
}
|
||||
else {
|
||||
$user = '';
|
||||
if ($db_info['default']['username']) {
|
||||
$user = $db_info['default']['username'];
|
||||
if ($db_info['default']['password']) {
|
||||
$user .= ':' . $db_info['default']['password'];
|
||||
}
|
||||
$user .= '@';
|
||||
}
|
||||
|
||||
$db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host'];
|
||||
if (isset($db_info['default']['port'])) {
|
||||
$db_url .= ':' . $db_info['default']['port'];
|
||||
}
|
||||
$db_url .= '/' . $db_info['default']['database'];
|
||||
}
|
||||
if ($db_info['default']['prefix']['default']) {
|
||||
$db_url .= '#' . $db_info['default']['prefix']['default'];
|
||||
}
|
||||
return $db_url;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -238,6 +238,9 @@ function simpletest_phpunit_configuration_filepath() {
|
|||
* The results as returned by exec().
|
||||
*/
|
||||
function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file) {
|
||||
// Setup an environment variable containing the database connection so that
|
||||
// functional tests can connect to the database.
|
||||
putenv('SIMPLETEST_DB=' . Database::getConnectionInfoAsUrl());
|
||||
$phpunit_bin = simpletest_phpunit_command();
|
||||
|
||||
$command = array(
|
||||
|
@ -273,6 +276,7 @@ function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpun
|
|||
// via the simpletest UI.
|
||||
$ret = exec(join($command, " "));
|
||||
chdir($old_cwd);
|
||||
putenv('SIMPLETEST_DB=');
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -179,6 +179,7 @@ class SimpletestTestForm extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
global $base_url;
|
||||
// Test discovery does not run upon form submission.
|
||||
simpletest_classloader_register();
|
||||
|
||||
|
@ -209,6 +210,7 @@ class SimpletestTestForm extends FormBase {
|
|||
}
|
||||
}
|
||||
if (!empty($tests_list)) {
|
||||
putenv('SIMPLETEST_BASE_URL=' . $base_url);
|
||||
$test_id = simpletest_run_tests($tests_list, 'drupal');
|
||||
$form_state->setRedirect(
|
||||
'simpletest.result_form',
|
||||
|
|
|
@ -79,8 +79,9 @@ class TestDiscovery {
|
|||
|
||||
$existing = $this->classLoader->getPrefixesPsr4();
|
||||
|
||||
// Add PHPUnit test namespace of Drupal core.
|
||||
// Add PHPUnit test namespaces of Drupal core.
|
||||
$this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
|
||||
$this->testNamespaces['Drupal\\FunctionalTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/FunctionalTests'];
|
||||
|
||||
$this->availableExtensions = array();
|
||||
foreach ($this->getExtensions() as $name => $extension) {
|
||||
|
@ -95,8 +96,9 @@ class TestDiscovery {
|
|||
// Add Simpletest test namespace.
|
||||
$this->testNamespaces["Drupal\\$name\\Tests\\"][] = "$base_path/src/Tests";
|
||||
|
||||
// Add PHPUnit test namespace.
|
||||
$this->testNamespaces["Drupal\\Tests\\$name\\"][] = "$base_path/tests/src";
|
||||
// Add PHPUnit test namespaces.
|
||||
$this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit";
|
||||
$this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional";
|
||||
}
|
||||
|
||||
foreach ($this->testNamespaces as $prefix => $paths) {
|
||||
|
@ -322,7 +324,7 @@ class TestDiscovery {
|
|||
throw new MissingGroupException(sprintf('Missing @group annotation in %s', $classname));
|
||||
}
|
||||
// Force all PHPUnit tests into the same group.
|
||||
if (strpos($classname, 'Drupal\\Tests\\') === 0) {
|
||||
if (static::isUnitTest($classname)) {
|
||||
$info['group'] = 'PHPUnit';
|
||||
}
|
||||
else {
|
||||
|
@ -407,6 +409,31 @@ class TestDiscovery {
|
|||
return $annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided classname is a unit test.
|
||||
*
|
||||
* @param $classname
|
||||
* The test classname.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the class is a unit test. FALSE if not.
|
||||
*/
|
||||
public static function isUnitTest($classname) {
|
||||
if (strpos($classname, 'Drupal\\Tests\\') === 0) {
|
||||
$namespace = explode('\\', $classname);
|
||||
$first_letter = Unicode::substr($namespace[2], 0, 1);
|
||||
if (Unicode::strtoupper($first_letter) === $first_letter) {
|
||||
// A core unit test.
|
||||
return TRUE;
|
||||
}
|
||||
elseif ($namespace[3] == 'Unit') {
|
||||
// A module unit test.
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available extensions.
|
||||
*
|
||||
|
|
|
@ -128,21 +128,23 @@ class SimpleTestBrowserTest extends WebTestBase {
|
|||
// to be created. However this scenario is covered by the testception of
|
||||
// \Drupal\simpletest\Tests\SimpleTestTest.
|
||||
|
||||
$this->drupalGet('admin/config/development/testing');
|
||||
$edit = array(
|
||||
$tests = array(
|
||||
// A KernelTestBase test.
|
||||
'tests[Drupal\field\Tests\String\StringFormatterTest]' => TRUE,
|
||||
'Drupal\field\Tests\String\StringFormatterTest',
|
||||
// A PHPUnit unit test.
|
||||
'Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest',
|
||||
// A PHPUnit functional test.
|
||||
'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Run tests'));
|
||||
$this->assertText('0 fails, 0 exceptions');
|
||||
|
||||
$this->drupalGet('admin/config/development/testing');
|
||||
$edit = array(
|
||||
// A PHPUnit test.
|
||||
'tests[Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest]' => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Run tests'));
|
||||
$this->assertText('0 fails, 0 exceptions');
|
||||
foreach ($tests as $test) {
|
||||
$this->drupalGet('admin/config/development/testing');
|
||||
$edit = array(
|
||||
"tests[$test]" => TRUE,
|
||||
);
|
||||
$this->drupalPostForm(NULL, $edit, t('Run tests'));
|
||||
$this->assertText('0 fails, 0 exceptions');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\simpletest\WebAssert.
|
||||
*/
|
||||
|
||||
namespace Drupal\simpletest;
|
||||
|
||||
use Behat\Mink\WebAssert as MinkWebAssert;
|
||||
use Behat\Mink\Element\TraversableElement;
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Defines a class with methods for asserting presence of elements during tests.
|
||||
*/
|
||||
class WebAssert extends MinkWebAssert {
|
||||
|
||||
/**
|
||||
* Checks that specific button exists on the current page.
|
||||
*
|
||||
* @param string $button
|
||||
* One of id|name|label|value for the button.
|
||||
* @param \Behat\Mink\Element\TraversableElement $container
|
||||
* (optional) The document to check against. Defaults to the current page.
|
||||
*
|
||||
* @return \Behat\Mink\Element\NodeElement
|
||||
* The matching element.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
* When the element doesn't exist.
|
||||
*/
|
||||
public function buttonExists($button, TraversableElement $container = NULL) {
|
||||
$container = $container ?: $this->session->getPage();
|
||||
$node = $container->findButton($button);
|
||||
|
||||
if ($node === NULL) {
|
||||
throw new ElementNotFoundException($this->session, 'button', 'id|name|label|value', $button);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that specific select field exists on the current page.
|
||||
*
|
||||
* @param string $select
|
||||
* One of id|name|label|value for the select field.
|
||||
* @param \Behat\Mink\Element\TraversableElement $container
|
||||
* (optional) The document to check against. Defaults to the current page.
|
||||
*
|
||||
* @return \Behat\Mink\Element\NodeElement
|
||||
* The matching element
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
* When the element doesn't exist.
|
||||
*/
|
||||
public function selectExists($select, TraversableElement $container = NULL) {
|
||||
$container = $container ?: $this->session->getPage();
|
||||
$node = $container->find('named', array(
|
||||
'select',
|
||||
$this->session->getSelectorsHandler()->xpathLiteral($select),
|
||||
));
|
||||
|
||||
if ($node === NULL) {
|
||||
throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\simpletest\Functional\BrowserTestBaseTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\simpletest\Functional;
|
||||
|
||||
use Drupal\simpletest\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests BrowserTestBase functionality.
|
||||
*
|
||||
* @group simpletest
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
class BrowserTestBaseTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('test_page_test', 'form_test');
|
||||
|
||||
/**
|
||||
* Tests basic page test.
|
||||
*/
|
||||
public function testGoTo() {
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($account);
|
||||
|
||||
// Visit a Drupal page that requires login.
|
||||
$this->drupalGet('/test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test page contains some text.
|
||||
$this->assertSession()->pageTextContains('Test page text.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic form functionality.
|
||||
*/
|
||||
public function testForm() {
|
||||
// Ensure the proper response code for a _form route.
|
||||
$this->drupalGet('/form-test/object-builder');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Ensure the form and text field exist.
|
||||
$this->assertSession()->elementExists('css', 'form#form-test-form-test-object');
|
||||
$this->assertSession()->fieldExists('bananas');
|
||||
|
||||
$edit = ['bananas' => 'green'];
|
||||
$this->submitForm($edit, 'Save', 'form-test-form-test-object');
|
||||
|
||||
$config_factory = $this->container->get('config.factory');
|
||||
$value = $config_factory->get('form_test.object')->get('bananas');
|
||||
$this->assertSame('green', $value);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ class TestInfoParsingTest extends UnitTestCase {
|
|||
}
|
||||
|
||||
public function infoParserProvider() {
|
||||
// A module provided unit test.
|
||||
$tests[] = [
|
||||
// Expected result.
|
||||
[
|
||||
|
@ -31,6 +32,32 @@ class TestInfoParsingTest extends UnitTestCase {
|
|||
'Drupal\Tests\simpletest\Unit\TestInfoParsingTest',
|
||||
];
|
||||
|
||||
// A core unit test.
|
||||
$tests[] = [
|
||||
// Expected result.
|
||||
[
|
||||
'name' => 'Drupal\Tests\Core\DrupalTest',
|
||||
'group' => 'PHPUnit',
|
||||
'description' => 'Tests \Drupal.',
|
||||
],
|
||||
// Classname.
|
||||
'Drupal\Tests\Core\DrupalTest',
|
||||
];
|
||||
|
||||
// Functional PHPUnit test.
|
||||
$tests[] = [
|
||||
// Expected result.
|
||||
[
|
||||
'name' => 'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
|
||||
'group' => 'simpletest',
|
||||
'description' => 'Tests BrowserTestBase functionality.',
|
||||
],
|
||||
// Classname.
|
||||
'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
|
||||
];
|
||||
|
||||
// Simpletest classes can not be autoloaded in a PHPUnit test, therefore
|
||||
// provide a docblock.
|
||||
$tests[] = [
|
||||
// Expected result.
|
||||
[
|
||||
|
|
|
@ -6,19 +6,31 @@
|
|||
<ini name="error_reporting" value="32767"/>
|
||||
<!-- Do not limit the amount of memory tests take to run. -->
|
||||
<ini name="memory_limit" value="-1"/>
|
||||
<env name="SIMPLETEST_BASE_URL" value=""/>
|
||||
<!-- Example SIMPLETEST_BASE_URL value: http://localhost -->
|
||||
<env name="SIMPLETEST_DB" value=""/>
|
||||
<!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/databasename#table_prefix -->
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="Drupal Unit Test Suite">
|
||||
<directory>./tests</directory>
|
||||
<directory>./modules/*/tests</directory>
|
||||
<directory>../modules</directory>
|
||||
<directory>../sites/*/modules</directory>
|
||||
<testsuite name="unit">
|
||||
<directory>./tests/Drupal/Tests</directory>
|
||||
<directory>./modules/*/tests/src/Unit</directory>
|
||||
<directory>../modules/*/tests/src/Unit</directory>
|
||||
<directory>../sites/*/modules/*/tests/src/Unit</directory>
|
||||
<!-- Exclude Composer's vendor directory so we don't run tests there. -->
|
||||
<exclude>./vendor</exclude>
|
||||
<!-- Exclude Drush tests. -->
|
||||
<exclude>./drush/tests</exclude>
|
||||
</testsuite>
|
||||
<testsuite name="functional">
|
||||
<directory>./tests/Drupal/FunctionalTests</directory>
|
||||
<directory>./modules/*/tests/src/Functional</directory>
|
||||
<directory>../modules/*/tests/src/Functional</directory>
|
||||
<directory>../sites/*/modules/*/tests/src/Functional</directory>
|
||||
<!-- Exclude Composer's vendor directory so we don't run tests there. -->
|
||||
<exclude>./vendor</exclude>
|
||||
<!-- Exclude Drush tests. -->
|
||||
<exclude>./drush/tests</exclude>
|
||||
<!-- Exclude special-case files from config's test modules. -->
|
||||
<exclude>./modules/config/tests/config_test/src</exclude>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<listeners>
|
||||
|
|
|
@ -356,6 +356,17 @@ function simpletest_script_init() {
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||
$base_url = 'https://';
|
||||
}
|
||||
else {
|
||||
$base_url = 'http://';
|
||||
}
|
||||
$base_url .= $host;
|
||||
if ($path !== '') {
|
||||
$base_url .= $path;
|
||||
}
|
||||
putenv('SIMPLETEST_BASE_URL=' . $base_url);
|
||||
$_SERVER['HTTP_HOST'] = $host;
|
||||
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
|
||||
$_SERVER['SERVER_ADDR'] = '127.0.0.1';
|
||||
|
@ -419,36 +430,13 @@ function simpletest_script_setup_database($new = FALSE) {
|
|||
if (!empty($args['dburl'])) {
|
||||
// Remove a possibly existing default connection (from settings.php).
|
||||
Database::removeConnection('default');
|
||||
|
||||
$info = parse_url($args['dburl']);
|
||||
if (!isset($info['scheme'], $info['host'], $info['path'])) {
|
||||
simpletest_script_print_error('Invalid --dburl. Minimum requirement: driver://host/database');
|
||||
try {
|
||||
$databases['default']['default'] = Database::convertDbUrlToConnectionInfo($args['dburl'], DRUPAL_ROOT);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
simpletest_script_print_error('Invalid --dburl. Reason: ' . $e->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
$info += array(
|
||||
'user' => '',
|
||||
'pass' => '',
|
||||
'fragment' => '',
|
||||
);
|
||||
if ($info['path'][0] === '/') {
|
||||
$info['path'] = substr($info['path'], 1);
|
||||
}
|
||||
if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
|
||||
$info['path'] = DRUPAL_ROOT . '/' . $info['path'];
|
||||
}
|
||||
$databases['default']['default'] = array(
|
||||
'driver' => $info['scheme'],
|
||||
'username' => $info['user'],
|
||||
'password' => $info['pass'],
|
||||
'host' => $info['host'],
|
||||
'database' => $info['path'],
|
||||
'prefix' => array(
|
||||
'default' => $info['fragment'],
|
||||
),
|
||||
);
|
||||
if (isset($info['port'])) {
|
||||
$databases['default']['default']['port'] = $info['port'];
|
||||
}
|
||||
}
|
||||
// Otherwise, use the default database connection from settings.php.
|
||||
else {
|
||||
|
|
Loading…
Reference in New Issue