1
0
Fork 0

Added a tool to browse and screenshot websites using Dusk

This commit is contained in:
Richard Dern 2024-04-17 14:27:30 +02:00
parent 0f86f77e4b
commit e85c771b1a
4 changed files with 192 additions and 3 deletions

166
app/Services/Browser.php Normal file
View File

@ -0,0 +1,166 @@
<?php
namespace App\Services;
use Exception;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
use Facebook\WebDriver\Exception\UnknownErrorException;
use Facebook\WebDriver\Exception\UnrecognizedExceptionException;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Str;
use Laravel\Dusk\Browser as DuskBrowser;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
class Browser
{
use ProvidesBrowser;
use SupportsChrome;
private $driver;
private $tries = 0;
private $screenshot = null;
private $title = null;
private $description = null;
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
if (!isset($this->driver)) {
$options = $this->getDriverOptions();
$this->driver = RemoteWebDriver::create(
$_ENV['DUSK_DRIVER_URL'],
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
return $this->driver;
}
/**
* Return driver options
*/
protected function getDriverOptions()
{
$options = (new ChromeOptions)
->addArguments(
collect(config(sprintf('browser.%s', $this->optionsSet)))
->all()
);
return $options;
}
public function name()
{
return Str::slug($this->url);
}
public function dataname()
{
return Str::slug('data_' . $this->url);
}
/**
* Return screenshot content as a PNG resource
*/
public function getScreenshot()
{
return $this->screenshot;
}
public function getTitle()
{
return $this->title;
}
public function getDescription()
{
return $this->description;
}
public function __construct(public string $url, public ?string $optionsSet = 'normal')
{
}
/**
* Navigate to specified URL
*/
public function go()
{
$shouldRetry = false;
DuskBrowser::$storeScreenshotsAt = storage_path('app/screenshots');
DuskBrowser::$storeConsoleLogAt = storage_path('logs/dusk');
DuskBrowser::$storeSourceAt = storage_path('app/sources');
$this->closeAll();
try {
$this->browse(fn (DuskBrowser $browser) => $this->handleNavigation($browser));
} catch (UnknownErrorException $ex) {
// This could happen when SSL certificat is expired
} catch (UnexpectedResponseException $ex) {
dump($ex->getMessage());
} catch (UnrecognizedExceptionException $ex) {
// This could happen on - some - YouTube links. Using the "no-gpu"
// preset fixes the problem
$shouldRetry = true;
$this->optionsSet = 'no-gpu';
} finally {
$this->closeAll();
}
$this->tries++;
if ($shouldRetry) {
if ($this->tries === 3) {
throw new Exception(sprintf('Maximum tries reach for url %s', $this->url));
}
unset($this->driver);
return $this->go();
}
}
private function handleNavigation(DuskBrowser $browser)
{
$browser->resize(1920, 1080)
->visit($this->url)
->pause(1000);
$this->screenshot = $browser->driver->takeScreenshot();
$this->title = $browser->driver->getTitle();
$this->description = $this->findDescription($browser);
$browser->quit();
}
private function findDescription(DuskBrowser $browser)
{
$description = null;
try {
$descriptionMeta = $browser->driver->findElement(WebDriverBy::xpath("//meta[@name='description']"));
$description = $descriptionMeta->getAttribute('content');
} finally {
}
return $description;
}
}

24
config/browser.php Normal file
View File

@ -0,0 +1,24 @@
<?php
return [
'normal' => [
'--start-maximized',
'--force-dark-mode',
'--no-sandbox',
'--disable-dev-shm-usage',
'--ignore-certificate-errors',
'--allow-insecure-localhost',
'--window-size=1920,1080',
],
'no-gpu' => [
'--disable-gpu',
'--headless',
'--start-maximized',
'--force-dark-mode',
'--no-sandbox',
'--disable-dev-shm-usage',
'--ignore-certificate-errors',
'--allow-insecure-localhost',
'--window-size=1920,1080',
],
];

View File

@ -2,7 +2,6 @@
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
@ -15,7 +14,7 @@ public function testBasicExample(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
->assertSee('Laravel');
});
}
}

View File

@ -17,7 +17,7 @@ abstract class DuskTestCase extends BaseTestCase
#[BeforeClass]
public static function prepare(): void
{
if (! static::runningInSail()) {
if (!static::runningInSail()) {
static::startChromeDriver();
}
}