Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 104 additions & 147 deletions README.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions src/Browser.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ public function __construct(DriverInterface $driver)
$this->mink = new Mink([self::SESSION => new Session($driver)]);
}

/**
* @return static
*/
public static function create(callable $factory): self
{
$browser = $factory();

if (!$browser instanceof self) {
throw new \RuntimeException(\sprintf('The factory callable must return an instance of "%s".', self::class));
}

return $browser;
}

/**
* @return static
*/
Expand Down
27 changes: 0 additions & 27 deletions src/Browser/Extension/ContainerAware.php

This file was deleted.

20 changes: 13 additions & 7 deletions src/Browser/HttpBrowser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,44 @@
namespace Zenstruck\Browser;

use Symfony\Component\BrowserKit\HttpBrowser as SymfonyHttpBrowser;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpKernel\Profiler\Profile;
use Zenstruck\Browser\Extension\ContainerAware;
use Symfony\Component\HttpKernel\Profiler\Profiler;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*
* @method SymfonyHttpBrowser inner()
*/
class HttpBrowser extends BrowserKitBrowser implements ContainerAwareInterface, ProfileAware
class HttpBrowser extends BrowserKitBrowser implements ProfileAware
{
use ContainerAware;
private ?Profiler $profiler = null;

final public function __construct(SymfonyHttpBrowser $inner)
{
parent::__construct($inner);
}

final public function setProfiler(Profiler $profiler): self
{
$this->profiler = $profiler;

return $this;
}

/**
* Profile collection must be enabled globally for this feature.
*/
final public function profile(): Profile
{
if (!$this->container()->has('profiler')) {
throw new \RuntimeException('Profiling is not enabled.');
if (!$this->profiler) {
throw new \RuntimeException('The profiler has not been set. Is profiling enabled?');
}

if (!$token = $this->inner()->getInternalResponse()->getHeader('x-debug-token')) {
throw new \RuntimeException('Profiling is not enabled for this request. You must enable profile collection globally when using the HttpBrowser.');
}

if (!$profile = $this->container()->get('profiler')->loadProfile($token)) {
if (!$profile = $this->profiler->loadProfile($token)) {
throw new \RuntimeException('Could not find profile for this request.');
}

Expand Down
157 changes: 135 additions & 22 deletions src/Browser/Test/HasBrowser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,160 @@
namespace Zenstruck\Browser\Test;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Zenstruck\Browser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\HttpBrowser as HttpBrowserClient;
use Symfony\Component\Panther\Client as PantherClient;
use Symfony\Component\Panther\PantherTestCase;
use Zenstruck\Browser\HttpBrowser;
use Zenstruck\Browser\KernelBrowser;
use Zenstruck\Browser\PantherBrowser;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
trait HasBrowser
{
final protected function browser(): Browser
/** @var HttpBrowserClient[] */
private static array $httpBrowserClients = [];
private static ?PantherClient $primaryPantherClient = null;

/**
* @internal
* @after
*/
final public static function _resetBrowserClients(): void
{
$browser = $this->createBrowser()
self::$httpBrowserClients = [];
self::$primaryPantherClient = null;
}

protected function pantherBrowser(): PantherBrowser
{
$browser = PantherBrowser::create(function() {
if (!$this instanceof PantherTestCase) {
throw new \RuntimeException(\sprintf('The "%s" method can only be used on TestCases that extend "%s".', __METHOD__, PantherTestCase::class));
}

$class = $_SERVER['PANTHER_BROWSER_CLASS'] ?? PantherBrowser::class;

if (!\is_a($class, PantherBrowser::class, true)) {
throw new \RuntimeException(\sprintf('"PANTHER_BROWSER_CLASS" env variable must reference a class that extends %s.', PantherBrowser::class));
}

if (self::$primaryPantherClient) {
return new $class(static::createAdditionalPantherClient());
}

self::$primaryPantherClient = static::createPantherClient(
['browser' => $_SERVER['PANTHER_BROWSER'] ?? static::CHROME]
);

return new $class(self::$primaryPantherClient);
});

BrowserExtension::registerBrowser($browser);

return $browser
->setSourceDir($_SERVER['BROWSER_SOURCE_DIR'] ?? './var/browser/source')
->setScreenshotDir($_SERVER['BROWSER_SCREENSHOT_DIR'] ?? './var/browser/screenshots')
->setConsoleLogDir($_SERVER['BROWSER_CONSOLE_LOG_DIR'] ?? './var/browser/console-logs')
;
}

BrowserExtension::registerBrowser($browser);
protected function httpBrowser(): HttpBrowser
{
$browser = HttpBrowser::create(function() {
$class = $_SERVER['HTTP_BROWSER_CLASS'] ?? HttpBrowser::class;

if (!$this instanceof KernelTestCase) {
$this->configureBrowser($browser);
if (!\is_a($class, HttpBrowser::class, true)) {
throw new \RuntimeException(\sprintf('"HTTP_BROWSER_CLASS" env variable must reference a class that extends %s.', HttpBrowser::class));
}

$baseUri = $_SERVER['HTTP_BROWSER_URI'] ?? null;

if (!$baseUri && !$this instanceof PantherTestCase) {
throw new \RuntimeException(\sprintf('If not using "HTTP_BROWSER_URI", your TestCase must extend "%s".', PantherTestCase::class));
}

if (!$baseUri) {
self::startWebServer();

$baseUri = self::$baseUri;
}

// copied from PantherTestCaseTrait::createHttpBrowserClient()
$client = new HttpBrowserClient();
$urlComponents = \parse_url($baseUri);
$host = $urlComponents['host'];

if (isset($urlComponents['port'])) {
$host .= ":{$urlComponents['port']}";
}

$client->setServerParameter('HTTP_HOST', $host);

if ('https' === ($urlComponents['scheme'] ?? 'http')) {
$client->setServerParameter('HTTPS', 'true');
}

/** @var HttpBrowser $browser */
$browser = new $class(self::$httpBrowserClients[] = $client);

if (!$this instanceof KernelTestCase) {
return $browser;
}

if (!static::$booted) {
static::bootKernel();
}

if (static::$container->has('profiler')) {
$browser->setProfiler(static::$container->get('profiler'));
}

return $browser;
});

BrowserExtension::registerBrowser($browser);

return $browser
->setSourceDir($_SERVER['BROWSER_SOURCE_DIR'] ?? './var/browser/source')
;
}

protected function kernelBrowser(): KernelBrowser
{
if (!$this instanceof KernelTestCase) {
throw new \RuntimeException(\sprintf('The "%s" method can only be used on TestCases that extend "%s".', __METHOD__, KernelTestCase::class));
}

if (!static::$booted) {
$browser = KernelBrowser::create(function() {
$class = $_SERVER['KERNEL_BROWSER_CLASS'] ?? KernelBrowser::class;

if (!\is_a($class, KernelBrowser::class, true)) {
throw new \RuntimeException(\sprintf('"KERNEL_BROWSER_CLASS" env variable must reference a class that extends %s.', KernelBrowser::class));
}

if ($this instanceof WebTestCase) {
static::ensureKernelShutdown();

return new $class(static::createClient());
}

// reboot kernel before starting browser
static::bootKernel();
}

if ($browser instanceof ContainerAwareInterface) {
$browser->setContainer(static::$container);
}
if (!static::$container->has('test.client')) {
throw new \RuntimeException('The Symfony test client is not enabled.');
}

$this->configureBrowser($browser);
return new $class(static::$container->get('test.client'));
});

return $browser;
}
BrowserExtension::registerBrowser($browser);

/**
* Override to configure the Browser's initial state/options.
*/
protected function configureBrowser(Browser $browser): void
{
return $browser
->setSourceDir($_SERVER['BROWSER_SOURCE_DIR'] ?? './var/browser/source')
;
}

abstract protected function createBrowser(): Browser;
}
72 changes: 0 additions & 72 deletions src/Browser/Test/HasHttpBrowser.php

This file was deleted.

Loading