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
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,56 @@ Pass `BrowserOptions`, `ImageOptions`, or `PdfOptions` objects through the `brow

### Custom Drivers

Chrome is the built-in driver. To supply your own, implement [`Driver`](src/Contracts/Driver.php) and bind it in your service provider's `register()` method, or pass it to the standalone `Screenshot` constructor. Drivers supporting `close()` should also implement [`ManagesBrowsers`](src/Contracts/ManagesBrowsers.php).
The facade uses `ScreenshotManager` to resolve named drivers. Chrome is included as `chrome` and is the default. Calls such as `Screenshot::html()` are forwarded to the configured default driver:

```php
Screenshot::html('<h1>Hello!</h1>')->capture();
Screenshot::driver('chrome')->html('<h1>Hello!</h1>')->capture();
```

To register another driver, implement [`Driver`](src/Contracts/Driver.php) and call `extend()` in your service provider's `boot()` method:

```php
use App\Screenshots\RemoteDriver;
use DirectoryTree\Screenshot\Facades\Screenshot;
use Illuminate\Contracts\Foundation\Application;

public function boot(): void
{
Screenshot::extend('remote', function (Application $app) {
return $app->make(RemoteDriver::class);
});
}
```

Select it for an individual capture:

```php
Screenshot::driver('remote')->url('https://example.com')->pdf()->capture();
```

Set `SCREENSHOT_DRIVER=remote` or `screenshot.default` to use it by default. Drivers are created on first use and cached by name. Every driver uses the same fluent builders and configured capture, image, and PDF defaults; custom drivers only implement the capture operation.

Inject `DirectoryTree\Screenshot\Contracts\Factory` when using the container directly. Both the manager and its fake implement this contract:

```php
use DirectoryTree\Screenshot\Contracts\Factory;
use DirectoryTree\Screenshot\ImageResult;

class InvoiceScreenshot
{
public function __construct(
protected Factory $screenshots,
) {}

public function capture(string $html): ImageResult
{
return $this->screenshots->driver()->html($html)->capture();
}
}
```

Outside Laravel, pass a driver to the standalone `Screenshot` constructor. Drivers supporting `close()` should also implement [`ManagesBrowsers`](src/Contracts/ManagesBrowsers.php).

Drivers receive `SourceType::Html` or `SourceType::Url` and handle every output through `capture(string $source, SourceType $type, CaptureOptions $options)`. Blade views are rendered to HTML first. `CaptureOptions` contains:

Expand Down Expand Up @@ -393,6 +442,10 @@ Screenshot::assertCaptured(function (string $source, SourceType $type, CaptureOp
});
```

`Screenshot::fake()` intercepts both the default driver and explicit `driver()` selections without creating real drivers. Assertions collect captures and shutdown requests across all driver names.

`Screenshot::fake()` also replaces the `Contracts\Factory` container binding. Services resolved after faking receive the same recorder as the facade. Use this contract for constructor injection; the fake does not extend the concrete `ScreenshotManager` class.

`assertCaptured()` and `assertCapturedCount()` include PNG, JPEG, and PDF captures. Use `assertNothingCaptured()` when no captures should occur. Assert browser shutdown requests with `assertClosed($key)`, `assertNotClosed($key)`, and `assertNothingClosed()`.

Check the output format in the callback when it matters:
Expand Down
12 changes: 12 additions & 0 deletions config/screenshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

return [

/*
|--------------------------------------------------------------------------
| Default Driver
|--------------------------------------------------------------------------
|
| Captures use this driver unless one is selected explicitly. Chrome is
| included by default. Register custom drivers using Screenshot::extend().
|
*/

'default' => env('SCREENSHOT_DRIVER', 'chrome'),

/*
|--------------------------------------------------------------------------
| Chrome / Chromium
Expand Down
16 changes: 16 additions & 0 deletions src/Contracts/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace DirectoryTree\Screenshot\Contracts;

use DirectoryTree\Screenshot\Screenshot;

/**
* @mixin Screenshot
*/
interface Factory
{
/**
* Get a screenshot factory for the given driver.
*/
public function driver($driver = null): Screenshot;
}
19 changes: 10 additions & 9 deletions src/Facades/Screenshot.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

namespace DirectoryTree\Screenshot\Facades;

use Closure;
use DirectoryTree\Screenshot\ImageResult;
use DirectoryTree\Screenshot\PdfResult;
use DirectoryTree\Screenshot\PendingSource;
use DirectoryTree\Screenshot\Screenshot as Factory;
use DirectoryTree\Screenshot\Testing\ScreenshotFake;
use DirectoryTree\Screenshot\ScreenshotManager;
use DirectoryTree\Screenshot\Testing\ScreenshotManagerFake;
use Illuminate\Support\Facades\Facade;

/**
* @method static Factory driver(?string $driver = null)
* @method static ScreenshotManager extend(string $driver, Closure $callback)
* @method static PendingSource html(string $html)
* @method static PendingSource url(string $url)
* @method static PendingSource view(string $view, array $data = [])
Expand All @@ -21,19 +25,16 @@
* @method static void assertNotClosed(string $key)
* @method static void assertNothingClosed()
*
* @see Factory
* @see ScreenshotManager
*/
class Screenshot extends Facade
{
/**
* Replace the screenshot factory with a recording fake.
* Replace the screenshot manager with a recording fake.
*/
public static function fake(ImageResult|PdfResult|null $result = null): ScreenshotFake
public static function fake(ImageResult|PdfResult|null $result = null): ScreenshotManagerFake
{
static::swap($fake = ScreenshotFake::from(
factory: static::getFacadeRoot(),
result: $result,
));
static::swap($fake = static::getFacadeRoot()->fake($result));

return $fake;
}
Expand All @@ -43,6 +44,6 @@ public static function fake(ImageResult|PdfResult|null $result = null): Screensh
*/
protected static function getFacadeAccessor(): string
{
return Factory::class;
return ScreenshotManager::class;
}
}
106 changes: 106 additions & 0 deletions src/ScreenshotManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace DirectoryTree\Screenshot;

use DirectoryTree\Screenshot\Contracts\Driver;
use DirectoryTree\Screenshot\Contracts\Factory;
use DirectoryTree\Screenshot\Drivers\ChromeDriver;
use DirectoryTree\Screenshot\Enums\OutputFormat;
use DirectoryTree\Screenshot\Enums\PaperSize;
use DirectoryTree\Screenshot\Support\ChromeInstaller;
use DirectoryTree\Screenshot\Testing\ScreenshotFake;
use DirectoryTree\Screenshot\Testing\ScreenshotManagerFake;
use Illuminate\Support\Manager;

/**
* @mixin Screenshot
*/
class ScreenshotManager extends Manager implements Factory
{
/**
* Create a recording manager using the configured capture defaults.
*/
public function fake(ImageResult|PdfResult|null $result = null): ScreenshotManagerFake
{
return new ScreenshotManagerFake(ScreenshotFake::from(
$this->createScreenshot(new ScreenshotFake),
$result,
));
}

/**
* Get the default driver name.
*/
public function getDefaultDriver(): string
{
return $this->config->get('screenshot.default');
}

/**
* Get a screenshot factory for the given driver.
*/
public function driver($driver = null): Screenshot
{
return parent::driver($driver);
}

/**
* Wrap the resolved capture driver in a configured screenshot factory.
*/
protected function createDriver($driver): Screenshot
{
return $this->createScreenshot(parent::createDriver($driver));
}

/**
* Create the Chrome capture driver.
*/
protected function createChromeDriver(): ChromeDriver
{
return new ChromeDriver(
$this->config->get('screenshot.browser.binary') ?? $this->container->make(ChromeInstaller::class)->path(),
$this->config->get('screenshot.browser.options', []),
$this->config->get('screenshot.sockets.directory'),
$this->config->get('screenshot.sockets.lock_timeout', 60000),
);
}

/**
* Create a screenshot factory with the application's capture defaults.
*/
protected function createScreenshot(Driver $driver): Screenshot
{
$capture = $this->config->get('screenshot.capture');
$image = $this->config->get('screenshot.image');
$pdf = $this->config->get('screenshot.pdf');

[$width, $height] = PaperSize::from($pdf['paper'])->dimensions();

return new Screenshot(
driver: $driver,
browser: (new BrowserOptions)
->setSize($capture['width'], $capture['height'])
->setTimeouts($capture['load_timeout'], $capture['browser_timeout'])
->setJavascript($capture['javascript'])
->setFailOnTimeout($capture['fail_on_timeout'])
->setSocket($capture['socket']),
image: (new ImageOptions)
->setFormat(OutputFormat::from($image['format']), $image['quality'])
->setMaxSize($image['max_width'], $image['max_height'])
->setFullPage($image['full_page']),
pdf: (new PdfOptions)
->setLayout(
width: $width,
height: $height,
landscape: $pdf['landscape'],
top: $pdf['margins']['top'],
right: $pdf['margins']['right'],
bottom: $pdf['margins']['bottom'],
left: $pdf['margins']['left'],
)
->setBackground($pdf['background'])
->setPreferCssPageSize($pdf['prefer_css_page_size']),
views: $this->container->make('view'),
);
}
}
50 changes: 3 additions & 47 deletions src/ScreenshotServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
namespace DirectoryTree\Screenshot;

use DirectoryTree\Screenshot\Commands\InstallCommand;
use DirectoryTree\Screenshot\Contracts\Driver;
use DirectoryTree\Screenshot\Drivers\ChromeDriver;
use DirectoryTree\Screenshot\Enums\OutputFormat;
use DirectoryTree\Screenshot\Enums\PaperSize;
use DirectoryTree\Screenshot\Contracts\Factory;
use DirectoryTree\Screenshot\Support\ChromeInstaller;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
Expand All @@ -24,49 +21,8 @@ public function register(): void
$app['config']->get('screenshot.browser.directory'),
));

$this->app->singleton(Driver::class, function (Application $app): Driver {
return new ChromeDriver(
$app['config']->get('screenshot.browser.binary') ?? $app->make(ChromeInstaller::class)->path(),
$app['config']->get('screenshot.browser.options', []),
$app['config']->get('screenshot.sockets.directory'),
$app['config']->get('screenshot.sockets.lock_timeout', 60000),
);
});

$this->app->singleton(Screenshot::class, function (Application $app): Screenshot {
$capture = $app['config']->get('screenshot.capture');
$image = $app['config']->get('screenshot.image');
$pdf = $app['config']->get('screenshot.pdf');

[$width, $height] = PaperSize::from($pdf['paper'])->dimensions();

return new Screenshot(
driver: $app->make(Driver::class),
browser: (new BrowserOptions)
->setSize($capture['width'], $capture['height'])
->setTimeouts($capture['load_timeout'], $capture['browser_timeout'])
->setJavascript($capture['javascript'])
->setFailOnTimeout($capture['fail_on_timeout'])
->setSocket($capture['socket']),
image: (new ImageOptions)
->setFormat(OutputFormat::from($image['format']), $image['quality'])
->setMaxSize($image['max_width'], $image['max_height'])
->setFullPage($image['full_page']),
pdf: (new PdfOptions)
->setLayout(
width: $width,
height: $height,
landscape: $pdf['landscape'],
top: $pdf['margins']['top'],
right: $pdf['margins']['right'],
bottom: $pdf['margins']['bottom'],
left: $pdf['margins']['left'],
)
->setBackground($pdf['background'])
->setPreferCssPageSize($pdf['prefer_css_page_size']),
views: $app['view'],
);
});
$this->app->singleton(ScreenshotManager::class);
$this->app->alias(ScreenshotManager::class, Factory::class);
}

/**
Expand Down
48 changes: 48 additions & 0 deletions src/Testing/ScreenshotManagerFake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace DirectoryTree\Screenshot\Testing;

use DirectoryTree\Screenshot\Contracts\Factory;
use DirectoryTree\Screenshot\ImageResult;
use DirectoryTree\Screenshot\PdfResult;
use Illuminate\Support\Testing\Fakes\Fake;
use Illuminate\Support\Traits\ForwardsCalls;

/**
* @mixin ScreenshotFake
*/
class ScreenshotManagerFake implements Factory, Fake
{
use ForwardsCalls;

/**
* Create a manager that records captures without resolving real drivers.
*/
public function __construct(
protected ScreenshotFake $fake,
) {}

/**
* Resolve every driver to the same configured recording fake.
*/
public function driver($driver = null): ScreenshotFake
{
return $this->fake;
}

/**
* Create a fresh recording manager using the same capture defaults.
*/
public function fake(ImageResult|PdfResult|null $result = null): static
{
return new static(ScreenshotFake::from($this->fake, $result));
}

/**
* Forward capture methods and assertions to the shared recorder.
*/
public function __call(string $method, array $parameters): mixed
{
return $this->forwardCallTo($this->fake, $method, $parameters);
}
}
Loading
Loading