npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apna-browser

v1.1.1

Published

Complete Browser Automation Suite built on Puppeteer with stealth mode, SSL handling, auto-detection, dialogs, console and network monitoring, drag & drop, iframes, mobile emulation, performance metrics, geolocation, downloads, experimental WebMCP tool su

Readme

Apna Browser

All-in-one Puppeteer-based browser automation manager with 30+ battle-tested utilities: stealth browsing, smart tab tracking, dialogs, clipboard, console + network monitoring, downloads, performance metrics, mobile emulation, geolocation, automatic retries, and experimental WebMCP tool support.

Installation

npm install apna-browser

Requires Node.js 22.12.0 or newer.

Quick Start

import ApnaBrowser from 'apna-browser';

const browser = new ApnaBrowser();

await browser.launchBrowser({
  stealth: true,
  ignoreSSLErrors: true,
});

await browser.openNewTab('https://www.google.com');
await browser.typeText('textarea[name="q"]', 'Apna Browser automation');
await browser.click('input[name="btnK"]');
await browser.wait(5000);
await browser.takeScreenshot('google-search.png');

await browser.closeBrowser();

Highlights

  • Stealth-ready launch options that bypass common bot detections
  • Automatic tab tracking and tab management helpers
  • Rich interaction helpers (typing, clicking, drag-and-drop, hover, iframes, clipboard)
  • Framework-safe form filling — pasteIntoField dispatches real input/change events so React, Vue, and Angular controlled inputs update correctly
  • Built-in console log and network request capture, with automatic memory caps
  • Request interception/blocking that safely coexists with other interceptors (isInterceptResolutionHandled() guard)
  • Automatic retry logic for flaky clicks and navigation (withRetry)
  • Persistent browser profiles via userDataDir
  • Custom Chrome path via executablePath or channel (system Chrome, Canary, Edge, etc.)
  • Connect to an already running browser via connectBrowser()
  • Modern Navigation Timing API for performance metrics (no deprecated APIs)
  • Experimental WebMCP support — discover and invoke tools that a page registers for AI agents
  • Performance, device emulation, geolocation and download helpers
  • Fully English console output and inline documentation

Table of Contents

Configuration

ApnaBrowser accepts a config object in its constructor to tune defaults:

const browser = new ApnaBrowser({
  defaultTimeout: 30000,      // default timeout for waitForSelector etc.
  maxLogs: 1000,               // console log cap (prevents memory leaks)
  maxNetworkRequests: 1000,    // network request cap
  tabSyncIntervalMs: 2000,     // how often auto-detection syncs tabs
  defaultRetries: 2,           // retry count for withRetry-wrapped actions
  defaultRetryDelay: 500,      // ms between retries
  logging: false,              // true = show library debug logs on errors/retries
});

Custom Chrome & Connect

Use your own Chrome (custom path)

Pass executablePath to point at a specific Chrome/Chromium binary, or use channel if Chrome is installed in a standard location:

// Windows — full path to chrome.exe
await browser.launchBrowser({
  stealth: true,
  executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
});

// macOS
await browser.launchBrowser({
  executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
});

// Linux
await browser.launchBrowser({
  executablePath: '/usr/bin/google-chrome',
});

// Or use an installed channel (no path needed)
await browser.launchBrowser({
  channel: 'chrome',        // stable Chrome
  // channel: 'chrome-beta',
  // channel: 'chrome-canary',
  // channel: 'msedge',
  enableWebMCP: true,       // useful with system Chrome 150+
});

Use executablePath or channel — not both at once.

Connect to a running browser

Launch Chrome separately with remote debugging, then attach Apna Browser:

# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222

# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

# Linux
google-chrome --remote-debugging-port=9222

Copy the WebSocket URL from the Chrome output, then:

await browser.connectBrowser({
  browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/...',
});

await browser.openNewTab('https://example.com');
await browser.click('button');

// Disconnect only — Chrome keeps running
await browser.closeBrowser();

// Or fully close the connected browser
await browser.closeBrowser({ closeRunningBrowser: true });

See Puppeteer browser management for more on launch vs connect.

Advanced Usage

Launch, Tabs & Navigation

import ApnaBrowser from 'apna-browser';

const browser = new ApnaBrowser();

await browser.launchBrowser({
  stealth: true,
  ignoreSSLErrors: true,
  autoDetect: true,
  userDataDir: './profile', // optional: persist cookies/login across runs
});

await browser.openNewTab('https://news.ycombinator.com');
await browser.openNewTab('https://github.com/alsocoders');

await browser.switchToTab(0);
await browser.reload();
await browser.nextTab();
await browser.navigateTo('https://alsocoder.com');

Dialogs, Console & Network Monitoring

browser.enableAutoDialogHandler({
  alert: 'accept',
  confirm: 'accept',
  prompt: 'accept',
  promptText: 'Namaste!',
});

await browser.enableNetworkMonitoring();
browser.setupConsoleCapture(browser.page);

await browser.click('#trigger-dialog');

const consoleErrors = browser.getConsoleErrors();
const recentRequests = browser.getNetworkRequests({ limit: 5 });

Interactions, Clipboard & Elements

await browser.typeText('#email', '[email protected]');
await browser.typeText('#password', 'super-secret');
await browser.click('#login');
await browser.waitForNavigation();

await browser.selectText('.profile-name');
const copiedName = await browser.copySelectedText();

const linkCount = await browser.countElements('a');
const isVisible = await browser.isElementVisible('#dashboard');

Performance, Screenshots & Downloads

await browser.getPerformanceMetrics();
browser.printPerformanceMetrics();

await browser.takeScreenshot('dashboard.png');
await browser.generatePDF('dashboard.pdf');

await browser.enableDownloads('./downloads');
await browser.click('#download-report');
await browser.waitForDownload(5000);

Mobile Emulation, Geolocation & Devices

await browser.setViewport(390, 844);
await browser.emulateMobile('iPhone 13 Pro');

await browser.grantGeolocationPermission();
await browser.setGeolocation(28.6139, 77.2090); // New Delhi
await browser.reload();

Iframes, Drag & Drop, Keyboard Shortcuts

await browser.clickInFrame('#ad-frame', 'button.close');
await browser.dragAndDrop('#item-1', '#dropzone', { steps: 15, holdDelay: 100 });

await browser.pressKeyCombination(['Control', 'Shift', 'KeyT']);
await browser.ctrlF();
await browser.escape();

Request Blocking (safe with other interceptors)

// Blocks images/fonts across every current AND future tab
await browser.enableRequestBlocking(['image', 'font']);

// ...later, to stop blocking
await browser.disableRequestBlocking();

Cleanup

await browser.closeAllTabs();
await browser.closeBrowser();

WebMCP (Experimental)

WebMCP lets a web page register "tools" (name + description + input schema) that an external agent can discover and invoke directly — instead of guessing CSS selectors and simulating clicks. It requires Chrome 150+ and must be explicitly enabled at launch.

await browser.launchBrowser({ stealth: true, enableWebMCP: true });
await browser.openNewTab('https://example-webmcp-site.com');

// Discover tools registered on the page
const tools = browser.getWebMCPTools();
console.log(tools.map(t => t.name));

// Call a tool directly
const result = await browser.executeWebMCPTool('search_products', { query: 'laptop' });

// React to tools appearing/disappearing dynamically
browser.onWebMCPToolsAdded(tools => console.log('New tools:', tools));
browser.onWebMCPToolsRemoved(tools => console.log('Removed tools:', tools));

// Watch every invocation for debugging
browser.onWebMCPToolInvoked(call => console.log('Invoked:', call.tool.name, call.input));
browser.onWebMCPToolResponded(response => console.log('Responded:', response.status));

// Register your own tool directly on a page
await browser.registerWebMCPTool({
  name: 'get_page_title',
  description: 'Returns the current document title',
  inputSchema: { type: 'object', properties: {} },
  execute: async () => ({ title: document.title }),
});

All WebMCP methods throw a clear error if enableWebMCP wasn't set at launch, or if the connected browser doesn't support the WebMCP CDP domain.

Complete Usage Reference

Each snippet assumes you created an instance via:

import ApnaBrowser from 'apna-browser';
const browser = new ApnaBrowser();

Browser Lifecycle

await browser.launchBrowser({ headless: false, stealth: true, userDataDir: './profile' });
await browser.enableAutoDetection();
await browser.refreshTabs();
await browser.closeBrowser();

// Custom Chrome path or channel
await browser.launchBrowser({
  executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
});
await browser.launchBrowser({ channel: 'chrome-canary' });

// Connect to a browser already running with --remote-debugging-port=9222
await browser.connectBrowser({ browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/...' });
await browser.closeBrowser(); // disconnect only
await browser.closeBrowser({ closeRunningBrowser: true }); // fully close Chrome

Tabs & Contexts

await browser.openNewTab('https://example.com');
await browser.switchToTab(0);
await browser.duplicateCurrentTab();
await browser.closeCurrentTab();
await browser.closeAllTabs();

const tabs = await browser.getAllTabs();

const { page: incognitoPage, context } = await browser.openIncognitoTab('https://alsocoder.com');
await context.close();

Navigation & Waiting

await browser.navigateTo('https://alsocoder.com/blog'); // auto-retried on failure
await browser.waitForNavigation();
await browser.goBack();
await browser.goForward();
await browser.reload();
await browser.wait(1500);
await browser.waitForSelector('#hero');

Dialog Handling

await browser.handleAlert(true);
await browser.handleConfirm(false);
await browser.handlePrompt('Namaste', true);

browser.enableAutoDialogHandler({
  alert: 'accept',
  confirm: 'dismiss',
  prompt: 'accept',
  promptText: 'Namaste!',
});

browser.disableAutoDialogHandler();

Interaction Helpers

await browser.typeText('#search', 'Apna Browser rocks!');
await browser.click('.submit'); // auto-retried on failure
await browser.doubleClick('.card');
await browser.rightClick('.card');
await browser.hover('.menu');
await browser.hoverAndClick('.menu', '.menu-item:last-child');
await browser.dragAndDrop('#item-1', '#drop-target');

Clipboard & Form Utilities

await browser.selectText('article h1');
const headline = await browser.copySelectedText();
await browser.selectAll();
await browser.copy();
await browser.cut();
await browser.paste();

// Works correctly on React/Vue/Angular controlled inputs
await browser.pasteIntoField('#notes', 'Copied content');

await browser.selectOption('#country', 'IN');
await browser.setCheckbox('#terms', true);
await browser.uploadFile('input[type="file"]', './assets/resume.pdf');
await browser.focus('#comments');

Element Info & Queries

const exists = await browser.elementExists('.ticket');
const titleText = await browser.getText('.title');
const href = await browser.getAttribute('.cta', 'href');
const count = await browser.countElements('a');
const allHeadings = await browser.getAllText('h2');
const visible = await browser.isElementVisible('#status');

Evaluation & Scrolling

await browser.evaluate(() => window.dispatchEvent(new Event('resize')));
await browser.scrollTo(0, 500);
await browser.scrollToBottom();
await browser.scrollToElement('#footer');

Screenshots & PDFs

await browser.takeScreenshot('full-page.png');
await browser.takeScreenshot('viewport.png', { fullPage: false });
await browser.generatePDF('report.pdf', { format: 'A4', printBackground: true });

Console & Network Monitoring

browser.setupConsoleCapture(browser.page);
await browser.enableNetworkMonitoring();
await browser.enableRequestBlocking(['image', 'font']);
await browser.disableRequestBlocking();

browser.printConsoleLogs();
const errors = browser.getConsoleErrors();
const warnings = browser.getConsoleWarnings();

const requests = browser.getNetworkRequests({ type: 'xhr', limit: 10 });
browser.clearConsoleLogs();
browser.clearNetworkRequests();

Performance Metrics

const metrics = await browser.getPerformanceMetrics(); // uses modern Navigation Timing API
console.log(metrics.pageLoadTime);
browser.printPerformanceMetrics();

Storage & Cookies

const cookies = await browser.getCookies();
await browser.setCookie({ name: 'user', value: 'also-coder' });
await browser.clearCookies();

const localStorage = await browser.getLocalStorage();
await browser.clearLocalStorage();

Downloads & File Handling

await browser.enableDownloads('./tmp/downloads');
await browser.click('#download');
await browser.waitForDownload(10000);

Viewport, Devices & Geolocation

await browser.setViewport(1280, 800);
await browser.setUserAgent('Mozilla/5.0 (...custom UA...)');

await browser.emulateMobile('iPhone 13 Pro');
const availableDevices = browser.getAvailableDevices();

await browser.grantGeolocationPermission();
await browser.setGeolocation(12.9716, 77.5946); // Bengaluru

Keyboard Shortcuts

await browser.pressKey('Enter');
await browser.pressKeyCombination(['Control', 'Shift', 'KeyT']);
await browser.ctrlS();
await browser.ctrlF();
await browser.ctrlW();
await browser.ctrlT();
await browser.ctrlShiftT();
await browser.escape();
await browser.enter();
await browser.tab();

Frames & Multi-Context

await browser.switchToFrame('iframe#payment');
await browser.currentFrame.type('#card', '4242424242424242');
await browser.switchToMainFrame();

await browser.clickInFrame('iframe#ad', 'button.close');

Metrics & Utilities Combo

const dimensions = await browser.getPageDimensions();
console.log(dimensions.width, dimensions.height);

const url = await browser.getURL();
const title = await browser.getTitle();

Retry Utility

// Wrap any custom async action with automatic retry
await browser.withRetry(
  () => browser.page.click('.flaky-button'),
  { retries: 3, delay: 1000, label: 'flaky button click' }
);

API Overview

| Category | Key Methods | | --- | --- | | Browser | launchBrowser, connectBrowser, closeBrowser, enableAutoDetection, refreshTabs | | Tabs | openNewTab, switchToTab, nextTab, closeTab, duplicateCurrentTab | | Navigation | navigateTo, reload, goBack, goForward, waitForNavigation | | Interactions | typeText, click, doubleClick, hover, dragAndDrop, pressKeyCombination | | Clipboard | selectText, copySelectedText, pasteIntoField | | Media | takeScreenshot, generatePDF | | Monitoring | setupConsoleCapture, getConsoleLogs, enableNetworkMonitoring | | Networking | enableRequestBlocking, disableRequestBlocking | | Reliability | withRetry, defaultTimeout, defaultRetries | | WebMCP | getWebMCPTools, executeWebMCPTool, registerWebMCPTool, onWebMCPToolInvoked | | Utilities | getPerformanceMetrics, setViewport, emulateMobile, setGeolocation, enableDownloads |

See lib/ApnaBrowser.js for the full API documentation via inline comments.

Example Script

The following script demonstrates a full-featured flow you can copy into your project:

import ApnaBrowser from 'apna-browser';

async function exampleDemo() {
  const manager = new ApnaBrowser();

  try {
    await manager.launchBrowser({ stealth: true, ignoreSSLErrors: true });

    manager.enableAutoDialogHandler({ alert: 'accept', confirm: 'accept' });

    await manager.openNewTab('https://www.google.com');
    await manager.wait(2000);

    await manager.openNewTab('https://www.github.com');
    await manager.wait(2000);

    await manager.openNewTab('https://www.youtube.com');
    await manager.wait(2000);

    await manager.switchToTab(0);
    await manager.typeText('textarea[name="q"]', 'Browser automation', { delay: 100 });
    await manager.wait(1000);
    await manager.click('input[name="btnK"]');
    await manager.wait(5000);

    try {
      await manager.selectText('h3');
      const copiedText = await manager.copySelectedText();
      console.log(`Copied: ${copiedText.substring(0, 50)}...`);
    } catch {
      console.log('Copy demo skipped');
    }

    await manager.getPerformanceMetrics();
    manager.printPerformanceMetrics();

    const linkCount = await manager.countElements('a');
    console.log(`Total links: ${linkCount}`);

    await manager.takeScreenshot('demo-screenshot.png');
  } catch (error) {
    console.error('Error:', error.message);
    await manager.takeScreenshot('error.png');
  } finally {
    await manager.closeBrowser();
  }
}

exampleDemo();

Changelog

1.1.1

  • Fixed SPA sites (WhatsApp Web, etc.) breaking on openNewTab / navigateTo
  • Navigation default changed from networkidle2 to load (configurable via defaultWaitUntil)
  • Browser now uses real window size (defaultViewport: null) instead of forced 1280×720
  • Stealth mode no longer uses command-line flags that trigger Chrome warnings on Windows
  • Stealth page patches simplified — removed fake plugins and extra HTTP headers that blocked JS/CSS loading

1.1.0

  • Requires Node.js 22.12.0 or newer
  • Updated to Puppeteer ^25.3.0
  • Added connectBrowser() to attach to a running Chrome via browserWSEndpoint
  • Added explicit executablePath and channel support in launchBrowser()
  • closeBrowser() disconnects without killing Chrome when attached via connectBrowser(); pass { closeRunningBrowser: true } to close the process
  • Added logging option (default false) to control library status logs
  • Fixed blank tab reuse for chrome://new-tab-page/ when connecting to real Chrome
  • Fixed deprecated page.target().createCDPSession()page.createCDPSession()
  • Fixed deprecated createIncognitoBrowserContext()createBrowserContext() (with fallback for older versions)
  • Fixed request-blocking "Request is already handled!" errors via isInterceptResolutionHandled()
  • pasteIntoField now dispatches native input/change events for React/Vue/Angular compatibility
  • dragAndDrop now uses intermediate mouse-move steps for JS-based drag libraries
  • Performance metrics migrated from deprecated performance.timing to Navigation Timing Level 2
  • Added automatic retry logic (withRetry) for click and navigateTo
  • Added memory caps for console logs and network request history
  • Added userDataDir support for persistent browser profiles
  • Added experimental WebMCP support (getWebMCPTools, executeWebMCPTool, registerWebMCPTool, and related event listeners)
  • Added disableRequestBlocking()
  • All console output, errors, and inline comments are now in English

1.0.0

  • Initial release

Support & Feedback

License

MIT © Also Coder · https://alsocoder.com
Use the package freely in your projects, but do not redistribute modified versions under the same name.