Added a tool to browse and screenshot websites using Dusk
This commit is contained in:
parent
0f86f77e4b
commit
e85c771b1a
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
],
|
||||
];
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ abstract class DuskTestCase extends BaseTestCase
|
|||
#[BeforeClass]
|
||||
public static function prepare(): void
|
||||
{
|
||||
if (! static::runningInSail()) {
|
||||
if (!static::runningInSail()) {
|
||||
static::startChromeDriver();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue