Best for
- When automating browser interactions with headless Chrome
- When writing end-to-end tests using Puppeteer
- When building web scraping or data extraction scripts
PramodDutta/qaskills/seed-skills/puppeteer-testing/SKILL.md
Comprehensive Puppeteer browser automation and testing skill for headless Chrome scripting, web scraping, PDF generation, network interception, and end-to-end test workflows in JavaScript and TypeScript.
Decision brief
You are an expert QA engineer specializing in Puppeteer browser automation and testing. When the user asks you to write, review, debug, or set up Puppeteer-related scripts, tests, or configurations, follow these detailed instructions.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Declared | Source record | Install path and trigger |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Declared | Source record | Install path and trigger |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/PramodDutta/qaskills --skill "seed-skills/puppeteer-testing"Inspect the Agent Skill "Puppeteer Testing" from https://github.com/PramodDutta/qaskills/blob/c924c5f7fee5fa410f267031061e492eb051757a/seed-skills/puppeteer-testing/SKILL.md at commit c924c5f7fee5fa410f267031061e492eb051757a. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Review the “Setup” section in the pinned source before continuing.
1. Headless by Default -- Run Puppeteer in headless mode for CI/CD and production scripts. Only switch to headed mode (headless: false) for local debugging. 2. Wait for Conditions, Not Time -- Never use page.waitForTimeout() in production code. Use page.waitForSelector(), page.w…
When automating browser interactions with headless Chrome
Review the “Project Structure” section in the pinned source before continuing.
Review the “Configuration” section in the pinned source before continuing.
Permission review
The documentation includes network, browsing, or remote request actions.
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';The documentation includes network, browsing, or remote request actions.
if (request.url().includes('/api/products')) {The documentation asks the agent to run terminal commands or scripts.
npx jest --config jest.puppeteer.config.tsThe documentation asks the agent to run terminal commands or scripts.
npx jest tests/e2e/auth.test.tsEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 195 | Source | Repository attention, not individual Skill quality |
| Compatibility | 3 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
You are an expert QA engineer specializing in Puppeteer browser automation and testing. When the user asks you to write, review, debug, or set up Puppeteer-related scripts, tests, or configurations, follow these detailed instructions.
headless: false) for local debugging.page.waitForTimeout() in production code. Use page.waitForSelector(), page.waitForNavigation(), page.waitForFunction(), or page.waitForNetworkIdle() to synchronize with actual page state.finally blocks or teardown hooks to prevent memory leaks and zombie Chrome processes.page.goto(), page.$(), page.evaluate(), or Puppeteer launch optionsproject-root/
├── puppeteer.config.ts # Shared Puppeteer configuration
├── tests/
│ ├── e2e/ # End-to-end test specs
│ │ ├── auth.test.ts
│ │ ├── checkout.test.ts
│ │ └── navigation.test.ts
│ ├── pages/ # Page Object classes
│ │ ├── base.page.ts
│ │ ├── login.page.ts
│ │ └── dashboard.page.ts
│ ├── helpers/ # Utility functions
│ │ ├── browser-factory.ts
│ │ ├── screenshot-helper.ts
│ │ └── network-mock.ts
│ └── fixtures/ # Test data
│ └── test-users.json
├── scripts/
│ ├── generate-pdf.ts # PDF generation scripts
│ └── scrape-data.ts # Data extraction scripts
├── screenshots/ # Captured screenshots
├── reports/ # Test reports
└── package.json
import puppeteer, { Browser, Page, LaunchOptions } from 'puppeteer';
const defaultOptions: LaunchOptions = {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--window-size=1920,1080',
],
defaultViewport: {
width: 1920,
height: 1080,
},
};
export async function createBrowser(options?: Partial<LaunchOptions>): Promise<Browser> {
return puppeteer.launch({ ...defaultOptions, ...options });
}
export async function createPage(browser: Browser): Promise<Page> {
const page = await browser.newPage();
page.setDefaultTimeout(30000);
page.setDefaultNavigationTimeout(30000);
// Log console messages for debugging
page.on('console', (msg) => {
if (msg.type() === 'error') {
console.error(`[Browser Console] ${msg.text()}`);
}
});
// Log page errors
page.on('pageerror', (err) => {
console.error(`[Page Error] ${err.message}`);
});
return page;
}
import { Page } from 'puppeteer';
export class BasePage {
constructor(protected page: Page) {}
async navigate(path: string): Promise<void> {
const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
await this.page.goto(`${baseUrl}${path}`, { waitUntil: 'networkidle0' });
}
async waitForSelector(selector: string, timeout = 10000): Promise<void> {
await this.page.waitForSelector(selector, { visible: true, timeout });
}
async getText(selector: string): Promise<string> {
await this.waitForSelector(selector);
return this.page.$eval(selector, (el) => el.textContent?.trim() || '');
}
async click(selector: string): Promise<void> {
await this.waitForSelector(selector);
await this.page.click(selector);
}
async type(selector: string, text: string): Promise<void> {
await this.waitForSelector(selector);
await this.page.click(selector, { clickCount: 3 }); // Select all existing text
await this.page.type(selector, text);
}
async screenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `screenshots/${name}.png`, fullPage: true });
}
async waitForNavigation(): Promise<void> {
await this.page.waitForNavigation({ waitUntil: 'networkidle0' });
}
}
import { BasePage } from './base.page';
import { Page } from 'puppeteer';
export class LoginPage extends BasePage {
private selectors = {
usernameInput: '[data-testid="username-input"]',
passwordInput: '[data-testid="password-input"]',
submitButton: '[data-testid="login-submit"]',
errorMessage: '[data-testid="login-error"]',
successRedirect: '[data-testid="dashboard"]',
};
constructor(page: Page) {
super(page);
}
async login(username: string, password: string): Promise<void> {
await this.type(this.selectors.usernameInput, username);
await this.type(this.selectors.passwordInput, password);
await Promise.all([
this.page.waitForNavigation({ waitUntil: 'networkidle0' }),
this.click(this.selectors.submitButton),
]);
}
async getError(): Promise<string> {
return this.getText(this.selectors.errorMessage);
}
async open(): Promise<void> {
await this.navigate('/login');
}
}
import { Browser, Page } from 'puppeteer';
import { createBrowser, createPage } from '../helpers/browser-factory';
import { LoginPage } from '../pages/login.page';
describe('Authentication Flow', () => {
let browser: Browser;
let page: Page;
let loginPage: LoginPage;
beforeAll(async () => {
browser = await createBrowser();
});
beforeEach(async () => {
page = await createPage(browser);
loginPage = new LoginPage(page);
await loginPage.open();
});
afterEach(async () => {
await page.close();
});
afterAll(async () => {
await browser.close();
});
it('should login with valid credentials', async () => {
await loginPage.login('[email protected]', 'SecurePass123!');
const url = page.url();
expect(url).toContain('/dashboard');
});
it('should display error for invalid credentials', async () => {
await loginPage.login('[email protected]', 'wrongpassword');
const error = await loginPage.getError();
expect(error).toBe('Invalid email or password');
});
it('should persist session after login', async () => {
await loginPage.login('[email protected]', 'SecurePass123!');
await page.goto(`${process.env.BASE_URL}/profile`);
const profileName = await page.$eval('[data-testid="profile-name"]', (el) =>
el.textContent?.trim()
);
expect(profileName).toBeTruthy();
});
});
describe('Network Interception', () => {
it('should mock API responses for controlled testing', async () => {
await page.setRequestInterception(true);
page.on('request', (request) => {
if (request.url().includes('/api/products')) {
request.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
products: [
{ id: 1, name: 'Test Product', price: 29.99 },
{ id: 2, name: 'Another Product', price: 49.99 },
],
}),
});
} else {
request.continue();
}
});
await page.goto(`${process.env.BASE_URL}/products`);
const productCount = await page.$$eval('[data-testid="product-card"]', (els) => els.length);
expect(productCount).toBe(2);
});
it('should block unnecessary resources for faster tests', async () => {
await page.setRequestInterception(true);
const blockedTypes = new Set(['image', 'stylesheet', 'font', 'media']);
page.on('request', (request) => {
if (blockedTypes.has(request.resourceType())) {
request.abort();
} else {
request.continue();
}
});
await page.goto(`${process.env.BASE_URL}/heavy-page`);
// Page loads faster without images, CSS, fonts
});
it('should test error handling with failed API calls', async () => {
await page.setRequestInterception(true);
page.on('request', (request) => {
if (request.url().includes('/api/data')) {
request.respond({ status: 500, body: 'Internal Server Error' });
} else {
request.continue();
}
});
await page.goto(`${process.env.BASE_URL}/data-view`);
const errorBanner = await page.$eval('[data-testid="error-banner"]', (el) =>
el.textContent?.trim()
);
expect(errorBanner).toContain('Something went wrong');
});
});
describe('PDF Generation', () => {
it('should generate a PDF from a web page', async () => {
await page.goto(`${process.env.BASE_URL}/invoice/12345`, {
waitUntil: 'networkidle0',
});
const pdf = await page.pdf({
path: 'reports/invoice-12345.pdf',
format: 'A4',
printBackground: true,
margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:10px;text-align:center;width:100%;">Invoice</div>',
footerTemplate:
'<div style="font-size:8px;text-align:center;width:100%;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>',
});
expect(pdf.byteLength).toBeGreaterThan(0);
});
});
describe('Session Management', () => {
it('should reuse authentication state across pages', async () => {
// Login once
await page.goto(`${process.env.BASE_URL}/login`);
await page.type('[data-testid="username-input"]', '[email protected]');
await page.type('[data-testid="password-input"]', 'AdminPass123!');
await Promise.all([
page.waitForNavigation(),
page.click('[data-testid="login-submit"]'),
]);
// Save cookies for reuse
const cookies = await page.cookies();
// Open a new page and set saved cookies
const newPage = await browser.newPage();
await newPage.setCookie(...cookies);
await newPage.goto(`${process.env.BASE_URL}/admin`);
const isLoggedIn = await newPage.$('[data-testid="admin-panel"]');
expect(isLoggedIn).not.toBeNull();
await newPage.close();
});
});
import { KnownDevices } from 'puppeteer';
describe('Mobile Responsive Testing', () => {
it('should render correctly on iPhone 14', async () => {
const iPhone14 = KnownDevices['iPhone 14'];
await page.emulate(iPhone14);
await page.goto(`${process.env.BASE_URL}/`);
const mobileMenu = await page.$('[data-testid="mobile-hamburger"]');
expect(mobileMenu).not.toBeNull();
const desktopNav = await page.$('[data-testid="desktop-nav"]');
const isHidden = await page.$eval('[data-testid="desktop-nav"]', (el) => {
return window.getComputedStyle(el).display === 'none';
});
expect(isHidden).toBe(true);
});
});
afterAll or finally blocks. Leaked Chrome processes consume memory and crash CI runners.page.waitForSelector() with { visible: true } to ensure elements are actually visible before interacting with them, not just present in the DOM.Promise.all([page.waitForNavigation(), page.click()]) to avoid race conditions.page.setViewport({ width: 1920, height: 1080 }).page.evaluate() for complex DOM queries that are easier to express as browser-side JavaScript rather than chaining Puppeteer methods.defaultTimeout and defaultNavigationTimeout at the page level rather than passing timeouts to every individual call.page.waitForNetworkIdle() after dynamic content loads to ensure all API calls have completed before making assertions.page.waitForTimeout() -- Static delays make tests slow and unreliable. Wait for specific conditions instead.page.on('pageerror') -- Uncaught JavaScript errors on the page often indicate real bugs. Log and optionally fail tests on page errors.page.click() without waiting -- Clicking elements before they are visible or clickable causes intermittent failures.page.goto() without timeout -- Pages that never fully load can hang tests indefinitely. Always set waitUntil and use timeouts.# Run Puppeteer tests with Jest
npx jest --config jest.puppeteer.config.ts
# Run specific test file
npx jest tests/e2e/auth.test.ts
# Run in headed mode for debugging
HEADLESS=false npx jest tests/e2e/auth.test.ts
# Run with verbose output
npx jest --verbose tests/e2e/
# Generate coverage report
npx jest --coverage tests/
# Install Puppeteer (includes bundled Chromium)
npm install --save-dev puppeteer
# For lighter installs (bring your own Chrome)
npm install --save-dev puppeteer-core
# With Jest integration
npm install --save-dev jest ts-jest @types/jest puppeteer
# For TypeScript support
npm install --save-dev typescript ts-node @types/node
Alternatives
alirezarezvani/claude-skills
Use when planning, running, or learning from chaos engineering experiments. Triggers on "chaos experiment", "fault injection", "gameday", "resilience test", "blast radius", "steady state", "abort criteria", "Chaos Toolkit", "Chaos Mesh", "Litmus", "Gremlin", "AWS FIS", or any deliberate failure-injection question. Ships experiment designer, blast-radius calculator, and postmortem generator (all stdlib Python), 4 references on chaos principles + experiment design + attack taxonomy + tooling lands
PramodDutta/qaskills
Simulate aggressive user behavior patterns including rapid clicking, random navigation, form abuse, tab spamming, and unexpected interaction sequences to find UI resilience issues
PramodDutta/qaskills
Comprehensive WebDriverIO (WDIO) test automation skill for generating reliable end-to-end browser tests in JavaScript and TypeScript with Page Object Model, custom commands, and advanced synchronization strategies.
PramodDutta/qaskills
Verify loading indicators, skeleton screens, and progress bars appear correctly during async operations and disappear on completion or error.