Sort of working beta
This commit is contained in:
272
app/Browser/BrowserJob.php
Normal file
272
app/Browser/BrowserJob.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser;
|
||||
|
||||
use App\Browser\JobArtifacts\JobRunArtifact;
|
||||
use App\Exception\JobException;
|
||||
use App\Models\JobArtifact;
|
||||
use App\Models\JobRun;
|
||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\Remote\RemoteWebElement;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Laravel\Dusk\TestCase as BaseTestCase;
|
||||
use Closure;
|
||||
use PHPUnit\Framework\Attributes\BeforeClass;
|
||||
use Exception;
|
||||
use Laravel\Dusk\Chrome\SupportsChrome;
|
||||
use Laravel\Dusk\Concerns\ProvidesBrowser;
|
||||
use Laravel\Dusk;
|
||||
use Throwable;
|
||||
|
||||
abstract class BrowserJob
|
||||
{
|
||||
use SupportsChrome, ProvidesBrowser;
|
||||
|
||||
public int $jobId;
|
||||
|
||||
public function __construct(int $jobId)
|
||||
{
|
||||
$this->jobId = $jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback in a browser
|
||||
* @param Closure $callback function with a Browser as parameter
|
||||
* @return void
|
||||
*/
|
||||
private function executeInBrowser(Closure $callback): ?JobRun
|
||||
{
|
||||
|
||||
$this->prepare();
|
||||
$this->setUp();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($callback, &$log) {
|
||||
try {
|
||||
$log = $callback($browser);
|
||||
// } catch (Exception $e) {
|
||||
// $browser->screenshot("failure-{$this->jobId}");
|
||||
// dump($e);
|
||||
// throw $e;
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$browser->screenshot("failure-{$this->jobId}");
|
||||
dump($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
$browser->quit();
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job
|
||||
* @return void
|
||||
*/
|
||||
public function execute(): ?JobRun {
|
||||
return $this->executeInBrowser(function (Browser $browser): ?JobRun {
|
||||
return $this->run($browser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job test
|
||||
* @return void
|
||||
*/
|
||||
public function executeTest(): ?JobRun {
|
||||
return $this->executeInBrowser(function (Browser $browser): ?JobRun {
|
||||
return $this->runTest($browser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps that run in the browser
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
abstract public function run(Browser $browser): ?JobRun;
|
||||
|
||||
abstract public function runTest(Browser $browser): ?JobRun;
|
||||
|
||||
/**
|
||||
* Prepare for Dusk test execution.
|
||||
* @unused
|
||||
*/
|
||||
#[BeforeClass]
|
||||
public static function prepare(): void
|
||||
{
|
||||
if (config("dusk.driver.url") == null && !(isset($_ENV['LARAVEL_SAIL']) && $_ENV['LARAVEL_SAIL'] == '1')) {
|
||||
static::startChromeDriver(['--port=9515']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the base URL with Dusk.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
Browser::$baseUrl = "https://pdftools.matthiasg.dev/";
|
||||
|
||||
Browser::$storeScreenshotsAt = base_path('app/Browser/screenshots');
|
||||
|
||||
Browser::$storeConsoleLogAt = base_path('app/Browser/console');
|
||||
|
||||
Browser::$storeSourceAt = base_path('app/Browser/source');
|
||||
|
||||
/*Browser::$userResolver = function () {
|
||||
return $this->user();
|
||||
}; */
|
||||
}
|
||||
|
||||
protected function makeSimpleJobRun(bool $success, string $name, string $content): JobRun {
|
||||
$artifact = new JobRun([
|
||||
"job_id" => $this->jobId,
|
||||
"success" => $success
|
||||
]);
|
||||
$artifact->save();
|
||||
$artifact->artifacts()->save(new JobArtifact([
|
||||
"name" => $name,
|
||||
"content" => $content,
|
||||
]));
|
||||
return $artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the RemoteWebDriver instance.
|
||||
*/
|
||||
protected function driver(): RemoteWebDriver
|
||||
{
|
||||
$options = (new ChromeOptions)->addArguments(collect([
|
||||
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1360,1020',
|
||||
'--disable-search-engine-choice-screen',
|
||||
'--disable-gpu',
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--whitelisted-ips=""',
|
||||
'--disable-dev-shm-usage',
|
||||
'--user-data-dir=/home/seluser/profile/',
|
||||
])->all());
|
||||
|
||||
return RemoteWebDriver::create(
|
||||
config("dusk.driver.url", 'http://localhost:9515'),
|
||||
DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY,
|
||||
$options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function terminate() {
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->quit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Dusk command has disabled headless mode.
|
||||
*/
|
||||
protected function hasHeadlessDisabled(): bool
|
||||
{
|
||||
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
|
||||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the browser window should start maximized.
|
||||
*/
|
||||
protected function shouldStartMaximized(): bool
|
||||
{
|
||||
return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
|
||||
isset($_ENV['DUSK_START_MAXIMIZED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an "after class" tear down callback.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function afterClass(Closure $callback)
|
||||
{
|
||||
static::$afterClassCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public static function name()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
public static function dataName()
|
||||
{
|
||||
return "dataTest";
|
||||
}
|
||||
|
||||
|
||||
// BROWSER MACROS
|
||||
protected function waitForAndClickText(Browser $browser, string $text, int $timeout = 30, bool $ignoreCase = true) {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
$this->findElementContainingText($browser, $text, $ignoreCase)?->click();
|
||||
}
|
||||
|
||||
protected function waitForElementContainingTextAndGetIt(Browser $browser, string $text, int $timeout = 30, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
return $this->findElementContainingText($browser, $text, $ignoreCase);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function findElementContainingText(Browser $browser, string $text, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
if ($ignoreCase) {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("//*[{$this->xpathContainsIgnoreCase($text)}]"));
|
||||
} else {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("//*[contains(text(), \"{$text}\")]"));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function xpathContainsIgnoreCase(string $needle, string $haystack = "text()") {
|
||||
$needle = strtolower($needle);
|
||||
return "contains(translate({$haystack}, \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", \"abcdefghijklmnopqrstuvwxyz\"), \"{$needle}\")";
|
||||
}
|
||||
|
||||
protected function waitForAndClickElementContainingText(Browser $browser, string $elementXpath, string $text, int $timeout = 30, bool $ignoreCase = true) {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
$this->findElementContainingElementWithText($browser, $elementXpath, $text, $ignoreCase)?->click();
|
||||
}
|
||||
|
||||
protected function waitForElementContainingElementWithTextAndGetIt(Browser $browser, string $elementXpath, string $text, int $timeout = 30, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
sleep(2);
|
||||
return $this->findElementContainingElementWithText($browser, $elementXpath, $text, $ignoreCase);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function findElementContainingElementWithText(Browser $browser, string $elementXpath, string $text, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
if ($ignoreCase) {
|
||||
dump("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]");
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]"));
|
||||
} else {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[contains(text(), \"{$text}\")]]"));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user