Browser Automation is a new admin-only Tools feature for storing and running browser scripts directly from Tools.
If you want a more focused step-by-step guide specifically for how Playwright is used in this project, see also:
For this Tools integration, Playwright is the better fit because it is easier to call from Laravel/PHP, works well with Chrome/Chromium/Edge, supports persistent browser profiles, and gives clean screenshot/artifact capture.
That makes it a more practical base for login-heavy sites like Facebook than adding a larger standalone TestCafe project structure inside Tools.
The current implementation supports:
tests/e2estorage/app/browser-automation/storage/playwright/browserbot.sh wrapperbrowserbot.sh --recordbrowserbot.sh --openbrowserbot.sh --copy-profile-from ... --profile ...browserbot.sh --export-profile-seed and --copy-profile-from-seedTools admin:
/admin/browser-automation/admin/browser-automation/playwrightFrom there you can:
browserbot.sh command directly from the edit page when you want to launch the same stored script from shellOn the dedicated Playwright E2E page you can also:
tests/e2ebase URL, skip webserver, and one persistent profile name before the run startsThe create/edit page is now also organized as a more guided admin form:
https://example.com, logs the title, saves a screenshot, and returns structured outputThe Playwright runner lives under automation/playwright and needs its Node dependency installed on the server:
cd automation/playwright
npm install
If Chrome / Chromium / Edge is not discoverable automatically on the host, set the matching server environment variable such as BROWSER_AUTOMATION_CHROME_PATH.
For the Laravel-root tests/e2e runner and the current SocialGPT/browserbot stack, use the canonical installer in the project root:
sudo bash bin/install-browserbot-stack.sh
That installer is now the single supported setup entrypoint for this stack. In its current form it handles these steps together:
storage/app/browser-automation/playwright-browsers.browserbot.env with safe defaults for the current stackImportant: this installer is now Chromium-first for the Socdemo runtime. It does not install Google Chrome and it does not write Chrome wrapper binaries as the primary playback target. The supported Socdemo browser chain is:
Ubuntu's snap-style /usr/bin/chromium-browser launcher is explicitly rejected as broken for this flow.
If you want to skip one installer step explicitly, use the installer's environment flags rather than switching back to the older split scripts. For example:
INSTALL_PLAYWRIGHT_BROWSERS=false sudo -E bash bin/install-browserbot-stack.sh
After installation, you can still load the generated environment file before browserbot or socdemo runs:
cd /path/to/tools.tornevall.com
set -a
source .browserbot.env
set +a
For the current Socdemo playback flow, the supported run modes are still:
SOCDEMO_DISPLAY_MODE=xvfb bash play/socdemo-play.sh
SOCDEMO_DISPLAY_MODE=visible bash play/socdemo-play.sh
SOCDEMO_DISPLAY_MODE=headless is intentionally blocked for this SocialGPT playback path. On server hosts, the supported "headless" model for this flow is still a headed browser inside Xvfb.
The current Playwright/Laravel setup also expects a modern Node runtime (Node 18+). An older distro-provided Node such as v12.x can still make the installer fail even if a node binary exists, because npm/npx may be missing or too old for Playwright.
If a remote Linux host still exposes an old/broken Node installation or npm/npx is missing, rerun the unified installer instead of trying to repair one partial step manually:
sudo bash bin/install-browserbot-stack.sh
The installer now also waits for active apt/dpkg locks instead of immediately failing when another package operation is still running.
If a headful SocialGPT/browserbot flow later complains that xvfb-run is missing, do not fall back to a raw apt-get update on hosts with broken third-party PPAs. Rerun the unified installer instead:
sudo bash bin/install-browserbot-stack.sh
That installer still uses only the normal distro/browser vendor apt sources for this stack, so unrelated PPAs such as broken nginx/PHP extras do not stop the Xvfb/browser dependency setup.
The Playwright install step now follows the same rule for Linux browser dependencies: it no longer delegates that step to Playwright's own raw npx playwright install --with-deps ... apt flow. Instead, bin/install-browserbot-stack.sh installs the common Linux browser packages through the same filtered apt wrapper first and only then runs a plain npx playwright install chromium for the browser download itself.
That same dependency step is now also more tolerant of newer Ubuntu package layouts such as Noble's t64 variants and virtual compatibility names. If one classic package name such as libasound2 is only available through a concrete provider like libasound2t64, the installer now resolves that automatically instead of stopping the whole setup on no installation candidate.
Playwright browser downloads are now stored in a project-local cache under storage/app/browser-automation/playwright-browsers by default instead of relying on the current account's ~/.cache/ms-playwright path. That makes browserbot, play/socdemo-play.sh, and the persistent record helper less sensitive to which Unix user happened to install the browsers first.
That same project-local cache is now also verified more aggressively before SocialGPT/browserbot playback starts: the installer checks that the resolved Playwright browser executable actually exists and is executable, and browserbot can now repair execute permissions or reinstall the missing browser into that same cache automatically when the earlier download was incomplete or left behind one non-executable binary.
It also reinstalls the NodeSource apt repo more defensively on remote hosts: stale nodesource.list / .sources files are removed first, the signing key is installed into /etc/apt/keyrings/nodesource.gpg, and the repo is written back with explicit signed-by= metadata. That fixes the common NO_PUBKEY 2F59B5F99B1BE0B4 failure mode that otherwise leaves apt using an old/broken NodeSource entry.
If the current Socdemo or browserbot runtime later fails with a message saying that the project-local Playwright browser cache is missing or not resolvable, rerun the canonical installer:
sudo bash bin/install-browserbot-stack.sh
Repeated cron-driven browserbot runs now condense that same dependency warning after the first full block, so logs keep one detailed repair message and later runs fall back to a shorter reminder instead of repeating the entire Node/npm/Playwright diagnostic every time.
The current stack now has two different browser-resolution behaviors, and that distinction matters:
browserbot.sh is still the general shell wrapper for stored scripts, --open, --record, profile cloning, and profile-seed export/import. In that general wrapper, a request for chromium can still fall back to a verified Chrome binary when Chromium is unavailable.play/socdemo-play.sh is now stricter. It defaults to chromium, rejects snap-style chromium-browser, checks system Chromium first, then the project-local Playwright Chromium cache, and only uses Chrome when you explicitly request it (for example BROWSER=chrome bash play/socdemo-play.sh).If a host only exposes chromium-browser through Snap, that launcher is rejected for this browser automation flow instead of being treated as a usable browser.
The play/socdemo-play.sh operational playback helper is now self-contained for the actual run path: it resolves display handling, extension/submodule presence, script path, runtime profile, profile-seed restore, snap-stub rejection, and Playwright Chromium cache repair itself before it starts the recorded Playwright script directly with node. It no longer uses browserbot.sh as the actual playback transport.
When browserbot.sh --open is run as root on a Linux host, the wrapper still adds the Chromium-family no-sandbox flags automatically for that interactive open session. The socdemo prepare helper also no longer aborts the whole copy/export flow just because one browser-open step failed; if the profile already contained the needed session state, the later clone/export steps can still continue.
When that same --open --with-socialgpt flow starts an unpacked extension through --load-extension, browserbot now also adds Chrome's unpacked-extension debugging flags (--enable-unsafe-extension-debugging and --extensions-on-chrome-urls). In that sideload mode the Chrome Web Store page can still say Installation is not enabled; that is expected, because the browser session is locked to the explicitly loaded unpacked extension. The practical verification point is the target site or the extension popup, not whether the Web Store page offers install.
For the play/prepare-socdemo-profile.sh bootstrap flow, Tools still avoids the temporary sideload trick during the actual profile-preparation steps. Instead, the source profile is opened on chrome://extensions/ so you can use Load unpacked and save SocialGPT into the profile itself, and the later target-profile verification opens without --with-socialgpt so playback is tested against the truly persisted profile state. In that prepare/doctor path, a verified Chrome fallback is still allowed when Chromium is unavailable on that host.
The Laravel-root Playwright setup now expects:
playwright.config.tstests/e2estorage/playwright/profiles/<name>storage/playwright/profiles/default/storage-state.jsonstorage/playwright/For specs that should keep login/session state between runs, import the helper from tests/e2e/support/persistent instead of importing directly from @playwright/test.
The root package.json now also includes a dedicated alias:
npm run test:e2e:codegen
If you want a real Chromium user-data directory for manual login/codegen recording, use:
bash bin/playwright-record-persistent.sh
That helper can also target an external environment:
PLAYWRIGHT_SKIP_WEBSERVER=true PLAYWRIGHT_BASE_URL=https://tools.tornevall.net bash bin/playwright-record-persistent.sh
The stored script body runs inside an async function and receives:
pagecontextbrowserplaywrighthelpersinputUseful helpers include:
helpers.log(...parts)await helpers.screenshot('shot.png')await helpers.saveText('note.txt', '...')await helpers.saveJson('state.json', value)helpers.setOutput(value)await helpers.delay(ms)helpers.getEnv('NAME')Run one stored script directly with Artisan:
php artisan browser-automation:run facebook-post
Or use the repo-root shell wrapper:
bash browserbot.sh --list
bash browserbot.sh --key facebook-post
bash browserbot.sh --name "Facebook post"
If you want to record/codegen the Playwright steps before the stored script exists yet:
bash browserbot.sh --record --key facebook-post --start-url https://www.facebook.com/
For the current fb-socdemo2-stats SocialGPT flow there is also a tiny convenience wrapper that starts that same record path with the expected profile, browser, and start URL already filled in:
bash play/socdemo-record.sh
That recording mode writes the persistent browser profile into the same browser-automation profile storage used by the admin-stored scripts, so you can later save the same profile_directory in the GUI and keep reusing the logged-in session.
If you want the browser by itself, without playback and without Playwright codegen:
bash browserbot.sh --open --browser chrome --profile-directory facebook-admin --start-url https://www.facebook.com/
That mode opens the real browser executable with the same persistent profile storage used by the later Playwright runs.
To avoid the common case where an older saved browser profile reopens with a tiny or off-screen window, browserbot.sh --open now also applies a sane visible default window geometry (1600x1000 at 40,40) unless you explicitly disable it.
If you need to tune or disable that behavior for one host, use:
export BROWSERBOT_WINDOW_SIZE=1920,1080
export BROWSERBOT_WINDOW_POSITION=20,20
# or disable the default geometry completely
export BROWSERBOT_DISABLE_DEFAULT_WINDOW_BOUNDS=1
If you want to prepare several bot/page-account variants without repeating the full login flow, you can now clone one saved profile into another before opening it:
bash browserbot.sh --clone-profile --copy-profile-from default --profile facebook-page-bot
bash browserbot.sh --open --profile facebook-page-bot --start-url https://www.facebook.com/
That workflow is meant for exactly the situation where one reusable base profile already contains the expensive sign-in/extensions/cookie setup, but each later bot/page profile still needs its own final account switch or page-specific login state.
If you want the reusable part of a prepared browser profile to follow your Git checkout, but you do not want the browser cache to come along, export a cache-stripped profile seed:
bash browserbot.sh --export-profile-seed --profile facebook-page-bot --seed-name facebook-page-bot
That writes a reusable seed under storage/app/browser-automation/profile-seeds/<seed-name>. The exported seed is the cache-stripped, commit-friendlier format. Some checkouts may also keep full runtime profiles under storage/app/browser-automation/profiles/<profile> in Git, but the seed is still the leaner format to move between hosts when you do not want the whole browser cache/runtime footprint.
The export keeps the important browser profile data such as cookies, extension state, local browser state, and other login/session material, but skips cache-heavy directories such as Cache, Code Cache, GPUCache, and similar transient browser caches.
Important: the profile seed does not contain the SocialGPT extension source code itself. When you run browserbot.sh --with-socialgpt ..., the extension is still loaded as an unpacked directory from projects/socialgpt-chrome (or from SOCIALGPT_EXTENSION_DIR if you override it explicitly). On server 2 you therefore need both:
projects/socialgpt-chrome (the submodule / extension code)storage/app/browser-automation/profile-seeds/<seed-name> (cookies/session/local state)On a fresh host you can restore the live runtime profile from that committed seed with:
bash browserbot.sh --clone-profile --copy-profile-from-seed facebook-page-bot --profile facebook-page-bot
The SocialGPT helper scripts now understand that workflow too: play/prepare-socdemo-profile.sh exports the cache-stripped seed automatically after you finish preparing the target profile, and play/socdemo-play.sh can restore the live runtime profile from a matching seed automatically when the runtime profile itself is missing.
If you are unsure what a fresh host is missing, you can now run:
bash play/socdemo-doctor.sh
That helper reports separately whether the missing piece is the SocialGPT submodule, the Playwright script, the runtime profile, or the profile seed.
The shorter --profile flag is now just an alias for --profile-directory, so both of these mean the same thing:
bash browserbot.sh --key facebook-post --profile facebook-admin
bash browserbot.sh --key facebook-post --profile-directory facebook-admin
If you want the seed copy to happen immediately before recording or playback, the wrapper now also accepts --copy-profile-from together with the ordinary run/open/record commands. The target profile is only copied when it does not already exist, unless you explicitly add --force-profile-copy.
Persistent saved profiles are now the default behavior for stored scripts. If profile_directory is left blank in the admin form, Tools automatically falls back to the script key as that script's saved profile name. A clean ephemeral run is now the explicit special case and should be requested deliberately, for example:
bash browserbot.sh --key facebook-post --fresh-profile
For first-login or challenge-heavy runs:
bash browserbot.sh --key facebook-post --headed
The wrapper simply forwards to the stored DB-backed script configuration, so browser selection, input JSON, timeout, and the persistent profile name still come from the admin GUI unless you explicitly override them.
Schedule one through DB-backed scheduled jobs:
App\Jobs\Handlers\BrowserAutomationScheduledJobHandlerbrowser_automation.run:facebook-postThe normal Laravel scheduler now also runs jobs:run every minute, so browser automation jobs can be executed automatically from a standard schedule:run cron setup.
If you want to verify that the browser automation stack works before trying Facebook or another interactive site:
/admin/browser-automation/createexample-homechrome or chromiumstart_url (or input.url) to https://example.comawait page.goto(input.url || 'https://example.com', { waitUntil: 'domcontentloaded' });
helpers.log('Loaded page', await page.title());
await helpers.screenshot('example-home.png');
return {
title: await page.title(),
url: page.url(),
};
okexample-home.pngIf you want to automate actions on Facebook or similar sites:
That persistent profile directory is the part that preserves cookies/session state between runs. If the field is empty, Tools now uses the script key automatically as the default saved profile name instead of falling back to an ephemeral run.
There are two different extension workflows, and they are not identical:
If you want to install an extension from the Chrome Web Store, use real Chrome in --open mode first:
bash browserbot.sh --open --browser chrome --profile-directory socialgpt-dev --start-url https://chromewebstore.google.com/
Why:
So the safer workflow is:
browserbot.sh --open --browser chromebrowserbot.sh --record or the stored script with the same profile_directoryIf you already have the extension source locally, you do not need the Chrome Web Store at all. Use the unpacked-extension path instead:
bash browserbot.sh --open --browser chromium --profile-directory socialgpt-dev --extension-dir /path/to/project/projects/socialgpt-chrome --start-url https://www.facebook.com/
That opens the same persistent profile while loading the extension directly from the local folder.
This is the practical route when the extension lives in your repo already and you mainly want Playwright to reuse the same cookies/profile state later.
Initial Google login or Chrome Web Store installation should generally be treated as a manual browser-profile preparation step, not as something you expect playwright codegen itself to do reliably.
Because the later Playwright run uses the same persistent profile path, that profile's installed extensions, cookies, and browser-side session state can follow along after you prepared them in --open mode.
For unpacked extensions, browserbot.sh --open can also accept one or more --extension-dir /path/to/extension values as a best-effort manual-load workflow. This is most useful in headed Chromium/Chrome sessions. Extension behavior in headless runs is generally less reliable, so headed mode is the safer default for extension-heavy flows.
Always make sure your use complies with the target platform's own rules and your own security/operational policy.