@push.rocks/smartpuppeteer
Simplified access to Puppeteer with environment-aware browser startup helpers.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
Install @push.rocks/smartpuppeteer with pnpm:
pnpm add @push.rocks/smartpuppeteer
Puppeteer 25 requires Node.js 22.12.0 or newer.
Usage
@push.rocks/smartpuppeteer simplifies interaction with Puppeteer, providing easier ways to launch Puppeteer instances considering environment constraints, such as running in a CI pipeline or as root, which necessitates certain flags for Chrome.
Here, we give a comprehensive guide to using @push.rocks/smartpuppeteer in various scenarios, using ESM syntax and TypeScript.
Basic Setup
Firstly, let’s set up the basic environment for using @push.rocks/smartpuppeteer:
import { getEnvAwareBrowserInstance, IncognitoBrowser, puppeteer } from '@push.rocks/smartpuppeteer';
// Usually, you would initialize the browser instance at the start of your script or application logic
const initializeBrowser = async () => {
const browser = await getEnvAwareBrowserInstance({
launchOptions: {
headless: true,
defaultViewport: { width: 1280, height: 720 },
args: ['--lang=en-US'],
},
});
return browser;
};
getEnvAwareBrowserInstance() checks google-chrome, chromium, and chromium-browser in that order. Missing candidates are skipped safely. When none resolve, Puppeteer chooses its default executable.
Caller launchOptions are passed to Puppeteer. Caller arguments are retained when the environment requires --no-sandbox and --disable-setuid-sandbox, and the required arguments are added without duplication. This no-sandbox behavior remains limited to root users, CI environments, or callers that explicitly set forceNoSandbox; disabling Chromium's sandbox reduces process isolation. The existing pipe transport remains enabled by default; set usePipe: false to request Puppeteer's WebSocket transport. Executable discovery is skipped when launchOptions.browser, launchOptions.channel, or launchOptions.executablePath is present.
Set requireSandbox: true when a caller must fail closed instead of accepting the environment-aware no-sandbox behavior. This rejects forceNoSandbox, known sandbox-disabling Chromium arguments, and Linux launches as root. It also prevents CI from adding no-sandbox arguments. This option prevents SmartPuppeteer from disabling Chromium's sandbox; a successful process launch is still not an independent verification of Chromium's internal sandbox state.
Use resolveBrowserExecutablePath() directly when you need to inspect the selected executable or provide your own ordered candidate list:
import { resolveBrowserExecutablePath } from '@push.rocks/smartpuppeteer';
const executablePath: string | undefined = resolveBrowserExecutablePath([
'chromium',
'chromium-browser',
]);
Opening a Page and Navigating
After obtaining a browser instance, you commonly want to open a page and navigate to a URL:
const openPage = async (browser: puppeteer.Browser) => {
const page = await browser.newPage();
await page.goto('https://www.example.com');
const pageTitle = await page.title();
console.log(`Page title: ${pageTitle}`);
// Always close the browser after you are done to free resources
await browser.close();
};
// Utilize the async function
initializeBrowser()
.then(openPage)
.catch(console.error);
Using Incognito Mode for Isolated Sessions
@push.rocks/smartpuppeteer offers easy management of incognito sessions, allowing isolated environments within the same browser instance:
const useIncognitoBrowser = async () => {
const incognitoBrowser = new IncognitoBrowser();
await incognitoBrowser.start(); // Initializes a new incognito browser instance
const context = await incognitoBrowser.getNewIncognitoContext();
const page = await context.newPage();
await page.goto('https://www.privacyfocusedsite.com');
// Perform actions in the isolated session
// Tidy up
await incognitoBrowser.stop(); // Stops the incognito browser and closes all its pages and contexts
};
useIncognitoBrowser()
.then(() => console.log('Incognito session used successfully'))
.catch(console.error);
Advanced Configuration
@push.rocks/smartpuppeteer allows further customization for launching the Puppeteer browser, such as disabling the sandbox environment (not recommended for production).
Live Browser Sessions
LiveBrowserSession provides a transport-neutral runtime for remote browser and agent adapters. It owns one Chromium process and one default-context profile. All tabs and popups therefore share cookies, local storage, cache, and other profile state. Puppeteer creates an ephemeral profile when neither launchOptions.userDataDir nor a --user-data-dir argument is supplied. Either explicit form persists potentially sensitive authentication and browsing data, must not be shared concurrently between Chromium processes, and must be protected by the caller.
import {
LiveBrowserSession,
type ILiveBrowserFrame,
} from '@push.rocks/smartpuppeteer';
const session = new LiveBrowserSession({
viewport: {
width: 1280,
height: 720,
deviceScaleFactor: 1,
},
screencast: {
format: 'jpeg',
quality: 80,
maxWidth: 1280,
maxHeight: 720,
everyNthFrame: 1,
maxOutstandingFrames: 3,
firstFrameTimeoutMs: 5000,
},
launchOptions: {
headless: true,
// userDataDir: '/explicit/profile/path',
},
});
const unsubscribe = session.onEvent((event) => {
if (event.type !== 'frame') {
return;
}
const frame: ILiveBrowserFrame = event.frame;
console.log(frame.mimeType, frame.data.byteLength);
void session.acknowledgeFrame({
tabId: frame.tabId,
sequence: frame.sequence,
generation: frame.generation,
viewportRevision: frame.viewportRevision,
});
});
await session.start();
try {
const state = session.getState();
const tabId = state.activeTabId!;
await session.navigate({
tabId,
url: 'data:text/html,<title>Live session</title><button>Continue</button>',
});
const observation = await session.observe({ tabId });
console.log(observation.text);
const currentState = session.getState();
const currentTab = currentState.tabs.find((tab) => tab.id === tabId)!;
await session.click({
tabId,
generation: currentTab.generation,
viewportRevision: currentState.viewportRevision,
selector: 'button',
});
} finally {
unsubscribe();
await session.stop();
}
Only the active tab is streamed. Frames carry a session-monotonic sequence, tab/CDP generation, viewport revision, viewport, MIME type, encoded dimensions, screencast metadata, and binary Uint8Array data. Every delivered frame must be acknowledged with all four identity fields. A delivered frame remains pending until it is acknowledged, dropped, or retired by the runtime; mismatched, duplicate, stale, retired, or operationally failed acknowledgements return { accepted: false }. Operational acknowledgement failures also emit an error event whose code is frame_acknowledgement_failed. screencast.maxOutstandingFrames is an integer from 1 through liveBrowserMaxOutstandingFrames (64) and defaults to liveBrowserDefaultMaxOutstandingFrames (3). At capacity, SmartPuppeteer retires and CDP-acknowledges the oldest pending frame before publishing the next one. Tab switches, navigation, resize, page cleanup, disconnect, and shutdown also retire pending frames and await their single CDP acknowledgement attempt before detaching the screencast session.
The SmartPuppeteer outstanding-frame bound covers only the private frame-to-Page.screencastFrameAck lifecycle. It is not a binary transport window, and acknowledgeFrame() is not an application transport acknowledgement. A higher-level runtime must maintain and bound its application frame window separately. refreshScreencast() retires the active stream and resolves with an ILiveBrowserFrameIdentity for the exact first validated frame from a new generation. Its tabId, sequence, generation, and viewportRevision fields can be passed directly to acknowledgeFrame(). screencast.firstFrameTimeoutMs bounds both the CDP restart and first-frame arrival, accepts integers from 100 through 60,000, and defaults to 5,000; timeout or another refresh failure emits fatal screencast_refresh_failed and shuts the session down. Screencast format accepts jpeg or png, quality accepts integers from 0 through 100, maxWidth and maxHeight accept integers from 1 through 4096 subject to an 8,294,400-pixel combined ceiling, and everyNthFrame accepts integers from 1 through 100.
The live API includes:
- Lifecycle and state:
start(),stop(),terminate(),refreshScreencast(),getProcessState(),onEvent(), andgetState() - Tabs and navigation:
createTab(),activateTab(),closeTab(),navigate(),back(),forward(), andreload() - Viewport and raw input:
setViewport(),dispatchMouse(),dispatchWheel(),dispatchKey(), andinsertText() - Agent-oriented actions:
click(),fill(), andpress()with bounded selectors and timeouts - Capture and observation:
captureSnapshot()returns viewport-only JPEG or PNG bytes;observe()returns bounded URL, title, tab state, and textual accessibility content without image bytes - Optional evaluation:
evaluate()returns bounded JSON values when the session explicitly setsallowEvaluation: true
Coordinate, keyboard, text, and semantic input messages include tabId, generation, and viewportRevision. This rejects input derived from an old stream generation, resize, or tab state. Snapshot, observation, and semantic operations are serialized with lifecycle mutations; inactive tabs receive the current session viewport before use. viewport takes precedence over launchOptions.defaultViewport; null falls back to 800x600. The runtime canonicalizes every page to a desktop, non-touch viewport because mobile emulation flags are outside the public viewport contract. Viewport dimensions, device scale factor, and physical pixel area are bounded, and full-page snapshots are intentionally unsupported. CDP sessions and CDP frame identifiers remain private implementation details. LiveBrowserSession owns launch cancellation, so callers cannot supply launchOptions.signal. It supports only Chromium over CDP and rejects Firefox or WebDriver BiDi launch selections.
start(), refreshScreencast(), tab and navigation methods, setViewport(), captureSnapshot(), observe(), semantic actions, and evaluate() accept a trailing { signal } operation argument. A pre-aborted operation is never admitted. An operation aborted while queued is removed immediately. An active operation receives cancellation when its Puppeteer or CDP primitive supports it; otherwise its promise rejects only after the underlying work settles, and it continues to occupy the serialized queue until then. An active refreshScreencast() completes the restorative restart before settling caller cancellation, unless session shutdown or page invalidation revokes that restart. Cancellation therefore does not promise that an already-started browser side effect did not occur. stop(), frame acknowledgement, event/state access, and direct raw input are intentionally not caller-cancellable.
Optional browser guards can be enabled when composing a higher-level runtime:
const guardedSession = new LiveBrowserSession({
requireSandbox: true,
launchOptions: {
args: [
'--proxy-server=http://127.0.0.1:8080',
'--proxy-bypass-list=<-loopback>',
],
},
security: {
denyDownloads: true,
denyFileChoosers: true,
denyPermissions: true,
httpNavigationOnly: true,
proxyCredentials: {
username: 'proxy-user',
password: 'proxy-password',
},
},
});
denyDownloads installs a default-context download denial at launch. denyPermissions applies an empty browser-wide permission grant before the first page is exposed, causing unlisted permissions to be denied. denyFileChoosers installs persistent CDP cancellation on each registered page. httpNavigationOnly limits URLs passed to createTab() and navigate() to http: and https:; it does not inspect or rewrite renderer-initiated navigation.
proxyCredentials handles authenticated-proxy challenges for existing and future page, dedicated-worker, and service-worker traffic. SmartPuppeteer supplies credentials only when CDP identifies the challenge source as Proxy; origin-server and unknown challenges are cancelled without credentials, and a repeated challenge for the same request is cancelled. Page and dedicated-worker setup is queued from Puppeteer's public CDP session-attachment event. Service workers use an independent, filtered browser-target attachment scope that enables Fetch and Network before resuming each worker. A hidden lifecycle target releases stopped workers and redundant workers after their tracked requests drain so Chromium can destroy them normally; later restarts are paused and protected again before execution. Setup failure rejects start() and closes the browser. During a running session, an unexpected browser security-session detach, a live target without a protected replacement session, or a proxy protocol failure is fatal and requests termination; Linux termination is confirmed only when terminate() resolves. The caller still owns proxy selection, Chromium proxy arguments, bypass rules, DNS behavior, egress policy, and proxy trust. Credentials remain in process memory for the session lifetime.
On Linux, Puppeteer launches Chromium as a dedicated process-group and session leader. getProcessState() reports the generation, root PID, process-group ID, and Node.js exit state. terminate({ gracefulTimeoutMs, forceTimeoutMs }) is idempotent for concurrent callers, first requests normal shutdown, then freezes and kills the owned process group if the graceful deadline expires. It resolves with confirmedDead: true only after /proc contains no member of that owned process group, the Node.js child exit is observed, and session shutdown settles. PID reuse, permission failures, surviving members, or an unsettled shutdown reject confirmation and keep restart blocked. Confirmed process-group termination is Linux-only; use stop() for portable best-effort lifecycle cleanup.
Evaluation is disabled by default. Once enabled, it accepts a JavaScript expression, runs it in a dedicated main-frame isolated world, awaits its result, and returns only JSON-compatible values:
const evaluationSession = new LiveBrowserSession({ allowEvaluation: true });
await evaluationSession.start();
const result = await evaluationSession.evaluate(
`({ title: document.title, links: document.links.length })`,
{ timeoutMs: 2000, maxOutputBytes: 65536 },
);
The default evaluation limits are a 5-second timeout, 256 KiB transferred output, depth 16, 10,000 total nodes, 64 KiB per string or key, 1,000 array entries, and 1,000 object keys. Hard ceilings are 30 seconds, 1 MiB output, depth 32, 50,000 nodes, 256 KiB per string or key, and 10,000 array entries or object keys. Expressions are limited to 256 KiB of UTF-8 source. Results reject non-finite numbers, undefined, bigint, symbols, functions, sparse or extended arrays, accessors, non-plain objects, cycles, and repeated object references. Output is normalized and measured inside the renderer before bounded JSON text is transferred, then measured again before host parsing.
evaluate() is trusted-caller code execution, not a JavaScript sandbox. An expression can mutate or navigate the page, initiate network activity, consume renderer resources, or crash the renderer. User-started asynchronous work can outlive a returned result, timeout, or caller cancellation; a higher-level runtime that requires strict quiescence must stop or quarantine the browser. Keep evaluation disabled unless a higher-level policy explicitly authorizes it.
LiveBrowserSession remains a browser runtime, not a complete security policy layer. The optional guards do not authenticate callers, authorize actions, enforce network egress, own profile-directory cleanup, isolate operating-system resources, or contain a compromised Chromium process. Adapters must apply those controls before invoking it. stop() rejects active and queued operations and aborts Chromium independently of Puppeteer operation timeouts. Closing the final usable tab also stops the session. Browser-wide loss stops the runtime without automatic relaunch. Page or CDP loss is tab-scoped: another usable tab becomes active when possible, otherwise the runtime stops. Popup registration queue saturation emits popup_registration_capacity_exceeded and stops the session rather than leaving an untracked page.
Handling Browser Events
It's important to handle browser events, such as disconnections, which might occur due to various reasons:
const browserWithEventHandling = async () => {
const browser = await getEnvAwareBrowserInstance();
browser.on('disconnected', () => {
console.log('Browser disconnected. Handling reconnection...');
// Implement reconnection logic here
});
// Utilize the browser for tasks
};
browserWithEventHandling()
.then(() => console.log('Handled browser events successfully'))
.catch(console.error);
Rotation of Browsers and Pages
In scenarios such as web scraping or automated testing, you might want to rotate between browser instances or pages to manage memory usage or simulate new sessions:
const rotateBrowserInstances = async (incognitoBrowser: IncognitoBrowser) => {
// Assuming incognitoBrowser is already initialized and started
await incognitoBrowser.rotateBrowser(); // Closes the current browser and starts a new instance
// Now you have a fresh browser instance
};
// Example usage
const incognitoBrowser = new IncognitoBrowser();
incognitoBrowser.start()
.then(() => rotateBrowserInstances(incognitoBrowser))
.catch(console.error);
@push.rocks/smartpuppeteer with its encapsulated features and simplified API provides an efficient way to harness the power of Puppeteer without getting bogged down by its complexities. Whether you are handling web scraping, automated testing, or any task requiring browser automation, @push.rocks/smartpuppeteer streamlines the process, making it more accessible and manageable even for those new to Puppeteer.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.