Commit 38144b9e authored by catch's avatar catch

Issue #3176655 by alexpott, jungle, longwave, ayushmishra206, Gábor Hojtsy,...

Issue #3176655 by alexpott, jungle, longwave, ayushmishra206, Gábor Hojtsy, andypost: Deprecate GoutteDriver use in core and use Behat\Mink\Driver\BrowserKitDriver directly instead
parent 99b763a1
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1387b767469672675eafc323b5e0b55a", "content-hash": "460aef4d3073d9f5e8e6a2dae54468d6",
"packages": [ "packages": [
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
}, },
"require": { "require": {
"behat/mink": "^1.8", "behat/mink": "^1.8",
"behat/mink-browserkit-driver": "^1.3",
"behat/mink-goutte-driver": "^1.2", "behat/mink-goutte-driver": "^1.2",
"behat/mink-selenium2-driver": "^1.4", "behat/mink-selenium2-driver": "^1.4",
"composer/composer": "^2.0.2", "composer/composer": "^2.0.2",
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
namespace Drupal\BuildTests\Framework; namespace Drupal\BuildTests\Framework;
use Behat\Mink\Driver\Goutte\Client; use Behat\Mink\Driver\BrowserKitDriver;
use Behat\Mink\Driver\GoutteDriver;
use Behat\Mink\Mink; use Behat\Mink\Mink;
use Behat\Mink\Session; use Behat\Mink\Session;
use Drupal\Component\FileSystem\FileSystem as DrupalFilesystem; use Drupal\Component\FileSystem\FileSystem as DrupalFilesystem;
use Drupal\Tests\DrupalTestBrowser;
use Drupal\Tests\PhpUnitCompatibilityTrait; use Drupal\Tests\PhpUnitCompatibilityTrait;
use Drupal\Tests\Traits\PhpUnitWarnings; use Drupal\Tests\Traits\PhpUnitWarnings;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
...@@ -219,9 +219,9 @@ protected function getWorkingPath($working_dir = NULL) { ...@@ -219,9 +219,9 @@ protected function getWorkingPath($working_dir = NULL) {
* @return \Behat\Mink\Session * @return \Behat\Mink\Session
*/ */
protected function initMink() { protected function initMink() {
$client = new Client(); $client = new DrupalTestBrowser();
$client->followMetaRefresh(TRUE); $client->followMetaRefresh(TRUE);
$driver = new GoutteDriver($client); $driver = new BrowserKitDriver($client);
$session = new Session($driver); $session = new Session($driver);
$this->mink = new Mink(); $this->mink = new Mink();
$this->mink->registerSession('default', $session); $this->mink->registerSession('default', $session);
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
namespace Drupal\FunctionalJavascriptTests; namespace Drupal\FunctionalJavascriptTests;
use Behat\Mink\Driver\GoutteDriver;
use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\AssertionFailedError;
/** /**
...@@ -41,6 +40,10 @@ public function testJavascript() { ...@@ -41,6 +40,10 @@ public function testJavascript() {
}()); }());
JS; JS;
$this->assertJsCondition($javascript); $this->assertJsCondition($javascript);
// Ensure that \Drupal\Tests\UiHelperTrait::isTestUsingGuzzleClient() works
// as expected.
$this->assertFalse($this->isTestUsingGuzzleClient());
} }
public function testAssertJsCondition() { public function testAssertJsCondition() {
...@@ -151,9 +154,9 @@ protected function drupalGetWithAlert($path, array $options = [], array $headers ...@@ -151,9 +154,9 @@ protected function drupalGetWithAlert($path, array $options = [], array $headers
$this->metaRefreshCount = 0; $this->metaRefreshCount = 0;
} }
// Log only for WebDriverTestBase tests because for Goutte we log with // Log only for WebDriverTestBase tests because for DrupalTestBrowser we log
// ::getResponseLogHandler. // with ::getResponseLogHandler.
if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) { if ($this->htmlOutputEnabled && !$this->isTestUsingGuzzleClient()) {
$html_output = 'GET request to: ' . $url . $html_output = 'GET request to: ' . $url .
'<hr />Ending URL: ' . $this->getSession()->getCurrentUrl(); '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
$html_output .= '<hr />' . $out; $html_output .= '<hr />' . $out;
......
...@@ -78,6 +78,10 @@ public function testGoTo() { ...@@ -78,6 +78,10 @@ public function testGoTo() {
]); ]);
$this->assertSession()->responseHeaderExists('Test-Header'); $this->assertSession()->responseHeaderExists('Test-Header');
$this->assertSession()->responseHeaderEquals('Test-Header', 'header value'); $this->assertSession()->responseHeaderEquals('Test-Header', 'header value');
// Ensure that \Drupal\Tests\UiHelperTrait::isTestUsingGuzzleClient() works
// as expected.
$this->assertTrue($this->isTestUsingGuzzleClient());
} }
/** /**
......
<?php
namespace Drupal\FunctionalTests;
use Behat\Mink\Driver\GoutteDriver;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Tests legacy support for GoutteDriver
*
* @group browsertestbase
*/
class GoutteDriverTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected $minkDefaultDriverClass = GoutteDriver::class;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'test_page_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests basic page test.
*
* @group legacy
*/
public function testGoTo() {
$this->expectDeprecation('Using \Behat\Mink\Driver\GoutteDriver is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. The dependencies behat/mink-goutte-driver and fabpot/goutte will be removed. See https://www.drupal.org/node/3177235');
$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.');
// Check that returned plain text is correct.
$text = $this->getTextContent();
$this->assertStringContainsString('Test page text.', $text);
$this->assertStringNotContainsString('</html>', $text);
// Response includes cache tags that we can assert.
$this->assertSession()->responseHeaderExists('X-Drupal-Cache-Tags');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Tags', 'http_response rendered');
// Test that we can read the JS settings.
$js_settings = $this->getDrupalSettings();
$this->assertSame('azAZ09();.,\\\/-_{}', $js_settings['test-setting']);
// Test drupalGet with a url object.
$url = Url::fromRoute('test_page_test.render_title');
$this->drupalGet($url);
$this->assertSession()->statusCodeEquals(200);
// Test page contains some text.
$this->assertSession()->pageTextContains('Hello Drupal');
// Ensure that \Drupal\Tests\UiHelperTrait::isTestUsingGuzzleClient() works
// as expected.
$this->assertTrue($this->isTestUsingGuzzleClient());
}
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\Tests; namespace Drupal\Tests;
use Behat\Mink\Driver\BrowserKitDriver;
use Behat\Mink\Driver\GoutteDriver; use Behat\Mink\Driver\GoutteDriver;
use Behat\Mink\Element\Element; use Behat\Mink\Element\Element;
use Behat\Mink\Mink; use Behat\Mink\Mink;
...@@ -145,7 +146,7 @@ abstract class BrowserTestBase extends TestCase { ...@@ -145,7 +146,7 @@ abstract class BrowserTestBase extends TestCase {
* *
* @var string * @var string
*/ */
protected $minkDefaultDriverClass = GoutteDriver::class; protected $minkDefaultDriverClass = BrowserKitDriver::class;
/* /*
* Mink default driver params. * Mink default driver params.
...@@ -220,8 +221,11 @@ abstract class BrowserTestBase extends TestCase { ...@@ -220,8 +221,11 @@ abstract class BrowserTestBase extends TestCase {
*/ */
protected function initMink() { protected function initMink() {
$driver = $this->getDefaultDriverInstance(); $driver = $this->getDefaultDriverInstance();
if ($driver instanceof GoutteDriver) { if ($driver instanceof GoutteDriver) {
@trigger_error('Using \Behat\Mink\Driver\GoutteDriver is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. The dependencies behat/mink-goutte-driver and fabpot/goutte will be removed. See https://www.drupal.org/node/3177235', E_USER_DEPRECATED);
}
if ($driver instanceof BrowserKitDriver) {
// Turn off curl timeout. Having a timeout is not a problem in a normal // Turn off curl timeout. Having a timeout is not a problem in a normal
// test running, but it is a problem when debugging. Also, disable SSL // test running, but it is a problem when debugging. Also, disable SSL
// peer verification so that testing under HTTPS always works. // peer verification so that testing under HTTPS always works.
...@@ -306,7 +310,10 @@ protected function getDefaultDriverInstance() { ...@@ -306,7 +310,10 @@ protected function getDefaultDriverInstance() {
} }
} }
if (is_array($this->minkDefaultDriverArgs)) { if ($this->minkDefaultDriverClass === BrowserKitDriver::class) {
$driver = new $this->minkDefaultDriverClass(new DrupalTestBrowser());
}
elseif (is_array($this->minkDefaultDriverArgs)) {
// Use ReflectionClass to instantiate class with received params. // Use ReflectionClass to instantiate class with received params.
$reflector = new \ReflectionClass($this->minkDefaultDriverClass); $reflector = new \ReflectionClass($this->minkDefaultDriverClass);
$driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs); $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
...@@ -539,7 +546,7 @@ protected function getSessionCookies() { ...@@ -539,7 +546,7 @@ protected function getSessionCookies() {
protected function getHttpClient() { protected function getHttpClient() {
/* @var $mink_driver \Behat\Mink\Driver\DriverInterface */ /* @var $mink_driver \Behat\Mink\Driver\DriverInterface */
$mink_driver = $this->getSession()->getDriver(); $mink_driver = $this->getSession()->getDriver();
if ($mink_driver instanceof GoutteDriver) { if ($this->isTestUsingGuzzleClient()) {
return $mink_driver->getClient()->getClient(); return $mink_driver->getClient()->getClient();
} }
throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().'); throw new \RuntimeException('The Mink client type ' . get_class($mink_driver) . ' does not support getHttpClient().');
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
namespace Drupal\Tests\Core\Test; namespace Drupal\Tests\Core\Test;
use Behat\Mink\Driver\GoutteDriver;
use Drupal\Tests\DrupalTestBrowser;
use Drupal\Tests\UnitTestCase; use Drupal\Tests\UnitTestCase;
use Drupal\Tests\BrowserTestBase; use Drupal\Tests\BrowserTestBase;
use Behat\Mink\Driver\GoutteDriver; use Behat\Mink\Driver\BrowserKitDriver;
use Behat\Mink\Session; use Behat\Mink\Session;
use Goutte\Client; use Goutte\Client;
...@@ -19,7 +21,7 @@ protected function mockBrowserTestBaseWithDriver($driver) { ...@@ -19,7 +21,7 @@ protected function mockBrowserTestBaseWithDriver($driver) {
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['getDriver']) ->setMethods(['getDriver'])
->getMock(); ->getMock();
$session->expects($this->once()) $session->expects($this->any())
->method('getDriver') ->method('getDriver')
->willReturn($driver); ->willReturn($driver);
...@@ -27,7 +29,7 @@ protected function mockBrowserTestBaseWithDriver($driver) { ...@@ -27,7 +29,7 @@ protected function mockBrowserTestBaseWithDriver($driver) {
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['getSession']) ->setMethods(['getSession'])
->getMockForAbstractClass(); ->getMockForAbstractClass();
$btb->expects($this->once()) $btb->expects($this->any())
->method('getSession') ->method('getSession')
->willReturn($session); ->willReturn($session);
...@@ -41,21 +43,41 @@ public function testGetHttpClient() { ...@@ -41,21 +43,41 @@ public function testGetHttpClient() {
// Our stand-in for the Guzzle client object. // Our stand-in for the Guzzle client object.
$expected = new \stdClass(); $expected = new \stdClass();
$browserkit_client = $this->getMockBuilder(Client::class) $browserkit_client = $this->getMockBuilder(DrupalTestBrowser::class)
->setMethods(['getClient']) ->setMethods(['getClient'])
->getMockForAbstractClass(); ->getMockForAbstractClass();
$browserkit_client->expects($this->once()) $browserkit_client->expects($this->once())
->method('getClient') ->method('getClient')
->willReturn($expected); ->willReturn($expected);
// Because the driver is a GoutteDriver, we'll get back a client. // Because the driver is a BrowserKitDriver, we'll get back a client.
$driver = $this->getMockBuilder(GoutteDriver::class) $driver = new BrowserKitDriver($browserkit_client);
$btb = $this->mockBrowserTestBaseWithDriver($driver);
$ref_gethttpclient = new \ReflectionMethod($btb, 'getHttpClient');
$ref_gethttpclient->setAccessible(TRUE);
$this->assertSame(get_class($expected), get_class($ref_gethttpclient->invoke($btb)));
}
/**
* @covers ::getHttpClient
*
* @group legacy
*/
public function testGetHttpClientGoutte() {
// Our stand-in for the Guzzle client object.
$expected = new \stdClass();
$browserkit_client = $this->getMockBuilder(Client::class)
->setMethods(['getClient']) ->setMethods(['getClient'])
->getMock(); ->getMockForAbstractClass();
$driver->expects($this->once()) $browserkit_client->expects($this->once())
->method('getClient') ->method('getClient')
->willReturn($browserkit_client); ->willReturn($expected);
// Because the driver is a GoutteDriver, we'll get back a client.
$driver = new GoutteDriver($browserkit_client);
$btb = $this->mockBrowserTestBaseWithDriver($driver); $btb = $this->mockBrowserTestBaseWithDriver($driver);
$ref_gethttpclient = new \ReflectionMethod($btb, 'getHttpClient'); $ref_gethttpclient = new \ReflectionMethod($btb, 'getHttpClient');
...@@ -68,7 +90,7 @@ public function testGetHttpClient() { ...@@ -68,7 +90,7 @@ public function testGetHttpClient() {
* @covers ::getHttpClient * @covers ::getHttpClient
*/ */
public function testGetHttpClientException() { public function testGetHttpClientException() {
// A driver type that isn't GoutteDriver. This should cause a // A driver type that isn't BrowserKitDriver. This should cause a
// RuntimeException. // RuntimeException.
$btb = $this->mockBrowserTestBaseWithDriver(new \stdClass()); $btb = $this->mockBrowserTestBaseWithDriver(new \stdClass());
......
<?php
namespace Drupal\Tests;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\BrowserKit\Response;
/**
* Enables a BrowserKitDriver mink driver to use a Guzzle client.
*
* This code is heavily based on the following projects:
* - https://github.com/FriendsOfPHP/Goutte
* - https://github.com/minkphp/MinkGoutteDriver
*/
class DrupalTestBrowser extends AbstractBrowser {
/**
* The Guzzle client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* Sets the Guzzle client.
*
* @param \GuzzleHttp\ClientInterface $client
* The Guzzle client.
*
* @return $this
*/
public function setClient(ClientInterface $client) {
$this->client = $client;
if ($this->getServerParameter('HTTP_HOST', NULL) !== NULL || $base_uri = $client->getConfig('base_uri') === NULL) {
return $this;
}
$path = $base_uri->getPath();
if ($path !== '' && $path !== '/') {
throw new \InvalidArgumentException('Setting a path in the Guzzle "base_uri" config option is not supported by DrupalTestBrowser.');
}
if ($this->getServerParameter('HTTPS', NULL) === NULL && $base_uri->getScheme() === 'https') {
$this->setServerParameter('HTTPS', 'on');
}
$host = $base_uri->getHost();
if (($port = $base_uri->getPort()) !== NULL) {
$host .= ":$port";
}
$this->setServerParameter('HTTP_HOST', $host);
return $this;
}
/**
* Gets the Guzzle client.
*
* @return \GuzzleHttp\ClientInterface
* The Guzzle client.
*/
public function getClient() {
if (!$this->client) {
$this->client = new Client([
'allow_redirects' => FALSE,
'cookies' => TRUE,
]);
}
return $this->client;
}
/**
* {@inheritdoc}
*/
protected function doRequest($request) {
$headers = [];
foreach ($request->getServer() as $key => $val) {
$key = strtolower(str_replace('_', '-', $key));
$content_headers = [
'content-length' => TRUE,
'content-md5' => TRUE,
'content-type' => TRUE,
];
if (strpos($key, 'http-') === 0) {
$headers[substr($key, 5)] = $val;
}
// CONTENT_* are not prefixed with HTTP_
elseif (isset($content_headers[$key])) {
$headers[$key] = $val;
}
}
$cookies = CookieJar::fromArray(
$this->getCookieJar()->allRawValues($request->getUri()),
parse_url($request->getUri(), PHP_URL_HOST)
);
$request_options = [
'cookies' => $cookies,
'allow_redirects' => FALSE,
];
if (!\in_array($request->getMethod(), ['GET', 'HEAD'], TRUE)) {
if (NULL !== $content = $request->getContent()) {
$request_options['body'] = $content;
}
else {
if ($files = $request->getFiles()) {
$request_options['multipart'] = [];
$this->addPostFields($request->getParameters(), $request_options['multipart']);
$this->addPostFiles($files, $request_options['multipart']);
}
else {
$request_options['form_params'] = $request->getParameters();
}
}
}
if (!empty($headers)) {
$request_options['headers'] = $headers;
}
$method = $request->getMethod();
$uri = $request->getUri();
// Let BrowserKit handle redirects
try {
$response = $this->getClient()->request($method, $uri, $request_options);
}
catch (RequestException $e) {
$response = $e->getResponse();
if (NULL === $response) {
throw $e;
}
}
return $this->createResponse($response);
}
/**
* Adds files to the $multipart array.
*
* @param array $files
* The files.
* @param array $multipart
* A reference to the multipart array to add the files to.
* @param string $array_name
* Internal parameter used by recursive calls.
*/
protected function addPostFiles(array $files, array &$multipart, $array_name = '') {
if (empty($files)) {
return;
}
foreach ($files as $name => $info) {
if (!empty($array_name)) {
$name = $array_name . '[' . $name . ']';
}
$file = [
'name' => $name,
];
if (\is_array($info)) {
if (isset($info['tmp_name'])) {
if ($info['tmp_name'] !== '') {
$file['contents'] = fopen($info['tmp_name'], 'r');
if (isset($info['name'])) {
$file['filename'] = $info['name'];
}
}
else {
continue;
}
}
else {
$this->addPostFiles($info, $multipart, $name);
continue;
}
}
else {
$file['contents'] = fopen($info, 'r');
}