Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Puphpeteer Laravel Package

nesk/puphpeteer

PuPHPeteer is a PHP bridge to Puppeteer that lets you drive headless Chrome from PHP. Scrape pages, generate screenshots and PDFs, run browser tests, and automate JavaScript-heavy sites with Puppeteer's full API, powered by Node.js via Rialto.

Deep Wiki
Context7

Getting Started

PuPHPeteer is a two-part install: a Composer package for PHP and a Node package for the bridge, plus a local Chrome/Chromium (or system Chrome via executablePath).

composer require nesk/puphpeteer
npm install @nesk/puphpeteer      # the Node side of the bridge

Minimal script:

use Nesk\Puphpeteer\Puppeteer;

$puppeteer = new Puppeteer;
$browser = $puppeteer->launch(['args' => ['--no-sandbox']]);

try {
    $page = $browser->newPage();
    $page->setViewport(['width' => 1280, 'height' => 800, 'deviceScaleFactor' => 2]);
    $page->goto('https://example.com', ['waitUntil' => 'networkidle0']);
    $page->screenshot(['path' => storage_path('app/example.png'), 'fullPage' => true]);
} finally {
    $browser->close();
}

Where to look first:

  • Nesk\Puphpeteer\Puppeteer — the entry point (constructor options, launch()).
  • Nesk\Puphpeteer\Resources\* — Browser, Page, ElementHandle, Request, Cookie. These are thin wrappers whose methods mirror Puppeteer's JS API 1:1, so the official Puppeteer docs are your primary reference; the PHP wrapper just changes the syntax.
  • nesk/rialto — the underlying IPC bridge (Symfony Process ↔ Node). This is where timeouts, debugging, and serialization behaviour live.

First use case: a screenshot/PDF endpoint. It's the shortest path to a working bridge and exercises the parts you'll rely on later (navigation, viewport, waiting, file output). Swap in $page->pdf(['format' => 'A4', 'printBackground' => true]) for PDFs (headless only).

Implementation Patterns

Treat the bridge as a synchronous RPC client. Every method call is a round trip to a Node process that blocks until the promise resolves. Write code accordingly.

Service provider registration (Laravel):

// AppServiceProvider
$this->app->singleton(Puppeteer::class, fn () => new Puppeteer([
    'executable_path' => '/usr/bin/node',
    // 'node_modules_path' => base_path('node_modules'),
]));

Keep the Puppeteer instance as a singleton, but never a singleton Browser. Launch a browser per job and close it in finally — long-lived browsers leak memory and zombie processes.

Queue job pattern:

class CapturePage implements ShouldQueue
{
    public $timeout = 120;
    public $tries = 3;

    public function handle(Puppeteer $puppeteer): void
    {
        $browser = $puppeteer->launch([
            'executablePath' => config('services.chrome.path'),
            'args' => ['--no-sandbox', '--disable-dev-shm-usage'],
        ]);

        try {
            $page = $browser->newPage();
            $page->setDefaultNavigationTimeout(60000);
            $page->goto($this->url, ['waitUntil' => 'networkidle0']);
            $page->waitForSelector('#app .ready', ['timeout' => 15000]);
            $page->screenshot(['path' => $this->targetPath, 'fullPage' => true]);
        } finally {
            $browser->close();
        }
    }
}

Data extraction — always pass arguments, never string-interpolate:

$html = $page->evaluate('() => document.documentElement.outerHTML');

$rows = $page->evaluate(
    '(selector) => [...document.querySelectorAll(selector)].map(el => ({
        title: el.querySelector("h2")?.innerText,
        href:  el.querySelector("a")?.href,
    }))',
    ['article.card']
);   // PHP array back — anything JSON-serializable survives the bridge

evaluate() takes a JS function as a string plus an array of JSON-serializable arguments. You cannot pass PHP closures. When you need an element, don't round-trip the whole node — grab data in one evaluate call.

Element handles for interactions:

$input = $page->querySelector('input[name="email"]');
$input->click();
$input->type('[email protected]', ['delay' => 30]);

$btn = $page->querySelector('button[type="submit"]');
$btn->click();

Login / cookie reuse:

$page->authenticate(['username' => $u, 'password' => $p]);
$page->setExtraHTTPHeaders(['Accept-Language' => 'en-US,en;q=0.9']);

// persist between jobs
$cookies = $page->cookies();
// later
foreach ($cookies as $cookie) { $page->setCookie($cookie); }

Speed up blocking assets with request interception:

$page->setRequestInterception(true);
$page->on('request', function ($request) {
    $skip = ['image', 'font', 'media'];
    in_array($request->resourceType(), $skip, true)
        ? $request->abort()
        : $request->continue();
});

Event listeners are plain PHP closures and are dispatched from Node synchronously:

$page->on('console', fn ($msg) => logger()->debug('browser: '.$msg->text()));
$page->on('pageerror', fn ($e) => logger()->error('page: '.$e->message()));

Gotchas and Tips

This package is archived. Pin the version in composer.json, and expect friction on PHP 8.x and modern symfony/process / nesk/rialto constraints. If Composer refuses, check the exact constraint before reaching for --ignore-platform-reqs. For new work, weigh this against Spatie's Browsershot (same Puppeteer engine, much smaller surface, actively maintained) or a plain HTTP client if you only need HTML.

Navigation waits are racy. In JS you'd do Promise.all([page.waitForNavigation(), page.click()]). The synchronous facade can't express that, so click() followed by waitForNavigation() may miss the event. Prefer waiting on content (waitForSelector, waitForFunction) or polling $page->url() after the click. Same trap applies to waitForNavigation on SPA route changes.

Chromium on servers: running as root requires --no-sandbox --disable-setuid-sandbox; containers need --disable-dev-shm-usage. Set executablePath (or PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 on npm install) to reuse a system Chromium instead of downloading one per deploy. Missing shared libs (libnss3, libatk, libgbm, etc.) surface as opaque "Failed to launch browser" errors — install Puppeteer's documented dependency list.

Errors come from the Node side. A null selector, a JS syntax error in your evaluate string, or a crashed page all arrive as Rialto/PuPHPeteer exceptions (e.g. InstructionException, Node\FatalException). Wrap calls in try/catch and log the message verbatim — it usually contains the JS stack you need.

Debugging the bridge. If calls hang, the IPC is the suspect, not your code. Raise read_timeout/idle_timeout on the Puppeteer constructor for slow pages (default read timeouts are aggressive for networkidle0 on heavy sites). Rialto's process class is the place to enable raw stderr passthrough — Node's stdout/stderr is otherwise swallowed.

Batching beats chit-chat. Ten evaluate() calls cost ten process round trips. One evaluate() returning a structured array is often 5–10× faster. Same for querySelectorAll + evaluate versus looping over handles in PHP.

Serialization boundaries. Only JSON-safe values cross: no PHP objects, resources, or closures in return values or arguments. Dates become strings, undefined becomes null, and huge DOM dumps are slow — prefer $page->content() over evaluate('() => document.documentElement.outerHTML') when you want full markup.

Always close the browser in finally, and guard against orphaned Chrome processes in long-running workers (--single-process is tempting but unstable). Add a health check or pkill sweep in your supervisor config if you run many jobs.

Extension points are shallow: subclass Nesk\Puphpeteer\Resources\Page and register it on the Puppeteer instance if you want helpers like waitForText() or clickAndWait() — that's the cleanest way to encode the navigation-wait workaround once and reuse it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console