diff --git a/README.md b/README.md index db571d8..170bd69 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,15 @@ A beautiful dashboard for your Dusk test suites.  + +## Improvements +* Upgrade to PHP 8.0 +* Upgraded some packages +* Applied community improvements from other forks + +## Next Step +* Generate PDF report + ## Installation You can install the package via composer: diff --git a/composer.json b/composer.json index cde97f8..e55cfec 100644 --- a/composer.json +++ b/composer.json @@ -16,18 +16,23 @@ } ], "require": { - "php": "^7.2", + "php": "^7.2|^8.0", + "barryvdh/laravel-snappy": "^1.0", "cboden/ratchet": "^0.4.1", - "clue/buzz-react": "^2.5", "guzzlehttp/guzzle": "^7.0.1", + "h4cc/wkhtmltoimage-amd64": "0.12.x", + "h4cc/wkhtmltoimage-i386": "0.12.x", + "h4cc/wkhtmltopdf-amd64": "0.12.x", + "h4cc/wkhtmltopdf-i386": "0.12.x", "illuminate/console": "5.6.*|5.7.*|5.8.*|6.*|7.*|8.*", "illuminate/support": "5.6.*|5.7.*|5.8.*|6.*|7.*|8.*", "laravel/dusk": "^4.0|^5.0|^6.0", - "yosymfony/resource-watcher": "^2.0" + "react/http": "^1.4", + "yosymfony/resource-watcher": "^3.0" }, "require-dev": { "larapack/dd": "^1.0", - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "autoload": { "psr-4": { diff --git a/src/BrowserReportCollector.php b/src/BrowserReportCollector.php new file mode 100644 index 0000000..17a514a --- /dev/null +++ b/src/BrowserReportCollector.php @@ -0,0 +1,135 @@ +testName = $testName; + + $this->pdf = App::make('snappy.pdf.wrapper'); + $this->pdf->setOption('disable-javascript', false); + $this->pdf->setOption('javascript-delay', 10000); + $this->pdf->setOption('no-stop-slow-scripts', true); + $this->pdf->setOption('enable-smart-shrinking', true); + $this->pdf->setOption('margin-bottom', 0); + $this->pdf->setOption('margin-top', 0); + + $file_name = Str::slug($this->testName) . "_" . Carbon::now()->format('YmdHis'); + $outPath = storage_path("app/dusk-reports/"); + if (!file_exists($outPath)) { + mkdir($outPath, 0777, true); + } + + + $data_html = '
You owe me money, dude.
'; + + $this->pdf->loadHtml($data_html); + $this->pdf->save($outPath . $file_name . '.pdf'); + } + + public function collect(string $action, array $arguments, Browser $browser, string $previousHtml = null) + { + $path = parse_url($browser->driver->getCurrentURL(), PHP_URL_PATH) ?? ''; + + $action = new Action($action, $arguments, $browser->getCurrentPageSource(), $path); + + $action->setPreviousHtml($previousHtml); + + $this->pushAction('dusk-event', [ + 'test' => $this->testName, + 'path' => $action->getPath(), + 'name' => $action->getName(), + 'arguments' => $action->getArguments(), + 'before' => $action->getPreviousHtml(), + 'html' => $action->getHtml(), + ]); + + $this->processPerformanceLog($browser); + } + + protected function processPerformanceLog(Browser $browser) + { + $logs = collect([]); + + try { + $logs = collect($browser->driver->manage()->getLog('performance')); + } catch (\Exception $e) { + // performance logging might be disabled. + } + + $allowedMethods = [ + 'Network.requestWillBeSent', + 'Network.responseReceived', + ]; + + $logs + ->map(function ($log) { + return json_decode($log['message']); + }) + ->filter(function ($log) use ($allowedMethods) { + $method = data_get($log, 'message.method'); + + $type = data_get($log, 'message.params.type'); + + return in_array($method, $allowedMethods) && $type === 'XHR'; + })->groupBy(function ($log) { + if (data_get($log, 'message.method') === 'Network.requestWillBeSent') { + return data_get($log, 'message.requestId'); + } + + return data_get($log, 'params.requestId'); + })->map(function ($log) use ($browser) { + $this->pushPerformanceLog($log->toArray(), $browser); + }); + } + + protected function pushAction(string $name, array $payload) + { + /* + try { + $this->client->post('http://127.0.0.1:'.StartDashboardCommand::PORT.'/events', [ + RequestOptions::JSON => [ + 'channel' => 'dusk-dashboard', + 'name' => $name, + 'data' => $payload, + ], + ]); + } catch (\Exception $e) { + // Dusk-Dashboard Server might be turned off. No need to panic! + } + */ + } + + protected function pushPerformanceLog(array $log, Browser $browser) + { + $request = $log[0]; + $response = $log[1]; + + $url = parse_url(data_get($request, 'message.params.request.url')); + + $this->pushAction('dusk-event', [ + 'test' => $this->testName, + 'name' => 'XHR', + 'arguments' => [ + data_get($request, 'message.params.request.method').' '. + $url['path'].' '. + data_get($response, 'message.params.response.status').' '. + data_get($response, 'message.params.response.statusText'), + ], + 'html' => $browser->getCurrentPageSource(), + 'logs' => $log, + ]); + } +} diff --git a/src/Console/StartDashboardCommand.php b/src/Console/StartDashboardCommand.php index 37e1f57..50679df 100644 --- a/src/Console/StartDashboardCommand.php +++ b/src/Console/StartDashboardCommand.php @@ -8,17 +8,17 @@ use BeyondCode\DuskDashboard\Ratchet\Server\App; use BeyondCode\DuskDashboard\Ratchet\Socket; use BeyondCode\DuskDashboard\Watcher; -use Clue\React\Buzz\Browser; +use React\Http\Browser; use Illuminate\Console\Command; use Ratchet\WebSocket\WsServer; -use React\EventLoop\Factory as LoopFactory; +use React\EventLoop\Loop as LoopFactory; use React\EventLoop\LoopInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Routing\Route; class StartDashboardCommand extends Command { - const PORT = 9773; + public const PORT = 9773; protected $signature = 'dusk:dashboard'; @@ -46,7 +46,7 @@ public function handle() { $url = parse_url(config('dusk-dashboard.host', config('app.url'))); - $this->loop = LoopFactory::create(); + $this->loop = LoopFactory::get(); $this->loop->futureTick(function () use ($url) { $dashboardUrl = 'http://'.$url['host'].':'.self::PORT.'/dashboard'; @@ -78,7 +78,7 @@ protected function addRoutes() protected function createTestWatcher() { - $finder = (new Finder) + $finder = (new Finder()) ->name('*.php') ->files() ->in($this->getTestSuitePath()); @@ -86,9 +86,12 @@ protected function createTestWatcher() (new Watcher($finder, $this->loop))->startWatching(function () { $client = new Browser($this->loop); - $client->post('http://127.0.0.1:'.self::PORT.'/events', [ + $client->post( + 'http://127.0.0.1:'.self::PORT.'/events', + [ 'Content-Type' => 'application/json', - ], json_encode([ + ], + json_encode([ 'channel' => 'dusk-dashboard', 'name' => 'dusk-reset', 'data' => [], @@ -109,7 +112,7 @@ protected function getTestSuitePath() $xml = simplexml_load_file(base_path('phpunit.dusk.xml')); foreach ($xml->testsuites->testsuite as $testsuite) { - $directories[] = (string) $testsuite->directory; + $directories[] = trim((string) $testsuite->directory); } } else { $directories[] = base_path('tests/Browser'); @@ -136,5 +139,9 @@ protected function tryToOpenInBrowser($url) if (PHP_OS === 'Darwin') { exec('open '.$url); } + + if (PHP_OS === 'Linux') { + exec('xdg-open '.$url); + } } } diff --git a/src/Dusk/Browser.php b/src/Dusk/Browser.php index 549cd70..7bdef1f 100644 --- a/src/Dusk/Browser.php +++ b/src/Dusk/Browser.php @@ -3,26 +3,47 @@ namespace BeyondCode\DuskDashboard\Dusk; use BeyondCode\DuskDashboard\BrowserActionCollector; +use BeyondCode\DuskDashboard\BrowserReportCollector; class Browser extends \Laravel\Dusk\Browser { - use Concerns\InteractsWithAuthentication, - Concerns\InteractsWithCookies, - Concerns\InteractsWithElements, - Concerns\InteractsWithJavascript, - Concerns\InteractsWithMouse, - Concerns\MakesAssertions, - Concerns\MakesUrlAssertions, - Concerns\WaitsForElements; + use Concerns\InteractsWithAuthentication; + use Concerns\InteractsWithCookies; + use Concerns\InteractsWithElements; + use Concerns\InteractsWithJavascript; + use Concerns\InteractsWithMouse; + use Concerns\MakesAssertions; + use Concerns\MakesUrlAssertions; + use Concerns\WaitsForElements; /** @var BrowserActionCollector */ protected $actionCollector; + /** @var BrowserReportCollector */ + protected $reportCollector; + + /** + * Create a browser instance. + * + * @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver + * @param \Laravel\Dusk\ElementResolver|null $resolver + * @return void + */ + public function __construct($driver, $resolver = null) + { + parent::__construct($driver, $resolver); + } + public function setActionCollector(BrowserActionCollector $collector) { $this->actionCollector = $collector; } + public function setReportCollector(BrowserReportCollector $reportCollector) + { + $this->reportCollector = $reportCollector; + } + /** * @return BrowserActionCollector|null */ diff --git a/src/Ratchet/Http/DashboardController.php b/src/Ratchet/Http/DashboardController.php index bb7bc00..8757ff9 100644 --- a/src/Ratchet/Http/DashboardController.php +++ b/src/Ratchet/Http/DashboardController.php @@ -3,7 +3,7 @@ namespace BeyondCode\DuskDashboard\Ratchet\Http; use GuzzleHttp\Psr7\Response; -use function GuzzleHttp\Psr7\str; +use GuzzleHttp\Psr7\Message; use Psr\Http\Message\RequestInterface; use Ratchet\ConnectionInterface; @@ -12,7 +12,7 @@ class DashboardController extends Controller public function onOpen(ConnectionInterface $connection, RequestInterface $request = null) { $connection->send( - str(new Response( + Message::toString(new Response( 200, ['Content-Type' => 'text/html'], file_get_contents(__DIR__.'/../../../resources/views/index.html') diff --git a/src/Ratchet/Http/EventController.php b/src/Ratchet/Http/EventController.php index 62671e1..0da55ac 100644 --- a/src/Ratchet/Http/EventController.php +++ b/src/Ratchet/Http/EventController.php @@ -5,7 +5,7 @@ use BeyondCode\DuskDashboard\Ratchet\Socket; use Exception; use GuzzleHttp\Psr7\Response; -use function GuzzleHttp\Psr7\str; +use GuzzleHttp\Psr7\Message; use Psr\Http\Message\RequestInterface; use Ratchet\ConnectionInterface; @@ -23,9 +23,9 @@ public function onOpen(ConnectionInterface $conn, RequestInterface $request = nu $connection->send($request->getBody()); } - $conn->send(str(new Response(200))); + $conn->send(Message::toString(new Response(200))); } catch (Exception $e) { - $conn->send(str(new Response(500, [], $e->getMessage()))); + $conn->send(Message::toString(new Response(500, [], $e->getMessage()))); } $conn->close(); diff --git a/src/Ratchet/Server/App.php b/src/Ratchet/Server/App.php index ec122d4..e02dd26 100644 --- a/src/Ratchet/Server/App.php +++ b/src/Ratchet/Server/App.php @@ -19,9 +19,9 @@ public function __construct($httpHost, $port, $address, LoopInterface $loop) $socket = new Reactor($address.':'.$port, $loop); - $this->routes = new RouteCollection; + $this->routes = new RouteCollection(); - $urlMatcher = new UrlMatcher($this->routes, new RequestContext); + $urlMatcher = new UrlMatcher($this->routes, new RequestContext()); $router = new Router($urlMatcher); diff --git a/src/Testing/TestCase.php b/src/Testing/TestCase.php index e2eeae8..0a21c37 100644 --- a/src/Testing/TestCase.php +++ b/src/Testing/TestCase.php @@ -2,16 +2,17 @@ namespace BeyondCode\DuskDashboard\Testing; -use BeyondCode\DuskDashboard\BrowserActionCollector; -use BeyondCode\DuskDashboard\Console\StartDashboardCommand; -use BeyondCode\DuskDashboard\Dusk\Browser; use Closure; -use Facebook\WebDriver\Chrome\ChromeOptions; -use Facebook\WebDriver\Remote\DesiredCapabilities; +use Throwable; use GuzzleHttp\Client; use GuzzleHttp\RequestOptions; +use BeyondCode\DuskDashboard\Dusk\Browser; use Laravel\Dusk\TestCase as BaseTestCase; -use Throwable; +use Facebook\WebDriver\Chrome\ChromeOptions; +use Facebook\WebDriver\Remote\DesiredCapabilities; +use BeyondCode\DuskDashboard\BrowserActionCollector; +use BeyondCode\DuskDashboard\BrowserReportCollector; +use BeyondCode\DuskDashboard\Console\StartDashboardCommand; abstract class TestCase extends BaseTestCase { @@ -39,6 +40,7 @@ protected function createBrowsersFor(Closure $callback) foreach ($browsers as $browser) { $browser->setActionCollector(new BrowserActionCollector($this->getTestName())); + $browser->setReportCollector(new BrowserReportCollector($this->getTestName())); } static::$browsers = $browsers;