Skip to content

Instantly share code, notes, and snippets.

@p32929
Last active June 25, 2025 19:24
Show Gist options
  • Save p32929/dbb594f4cc8a3cdc1cc8a0b9dfbf7918 to your computer and use it in GitHub Desktop.
Save p32929/dbb594f4cc8a3cdc1cc8a0b9dfbf7918 to your computer and use it in GitHub Desktop.
PuppeteerHelper.ts
// v4
import { connect } from "puppeteer-real-browser";
import fs from 'fs';
import path from 'path';
// Use the exact types from puppeteer-real-browser
type ConnectResult = Awaited<ReturnType<typeof connect>>;
type PuppeteerBrowser = ConnectResult['browser'];
type PuppeteerPage = ConnectResult['page'];
class PuppeteerConstants {
static defaultTimeout = 1000 * 60 * 5;
static defaultMaxWait = 1000 * 5;
static defaultMinWait = 1000;
static defaultShortWait = 2500;
static defaultDownloadWait = 1000 * 10;
static defaultButtonClickTimeout = 1000 * 15;
static defaultUploadWait = 1000 * 30;
static maxGotoRetries = 5;
}
interface PuppeteerHelperOptions {
headless?: boolean;
turnstile?: boolean;
args?: string[];
customConfig?: { userDataDir?: string; chromePath?: string; skipDefaultArgs?: boolean; [key: string]: any };
connectOption?: { defaultViewport?: any; args?: string[]; [key: string]: any };
disableXvfb?: boolean;
ignoreAllFlags?: boolean;
proxy?: { host: string; port: number; username?: string; password?: string };
plugins?: any[];
executablePath?: string;
}
export class PuppeteerHelper {
private browser: PuppeteerBrowser | null = null;
private page: PuppeteerPage | null = null;
private isInitialized = false;
private sessionPath?: string;
constructor(private options: PuppeteerHelperOptions = {
headless: false,
turnstile: true,
args: [
"--start-maximized"
],
customConfig: {},
connectOption: { defaultViewport: null },
disableXvfb: false,
ignoreAllFlags: false,
plugins: [require("puppeteer-extra-plugin-click-and-wait")()]
}, sessionPath?: string) {
this.sessionPath = sessionPath;
}
// Enhanced session management (cookies + localStorage + sessionStorage)
private async saveSession() {
if (!this.sessionPath || !this.page) return;
try {
// Save cookies
const cookies = await this.page.cookies();
// Save localStorage and sessionStorage
const storage = await this.page.evaluate(() => {
const localStorage = Object.keys(window.localStorage || {}).reduce((acc, key) => {
acc[key] = window.localStorage.getItem(key);
return acc;
}, {} as Record<string, string | null>);
const sessionStorage = Object.keys(window.sessionStorage || {}).reduce((acc, key) => {
acc[key] = window.sessionStorage.getItem(key);
return acc;
}, {} as Record<string, string | null>);
return { localStorage, sessionStorage };
});
const sessionData = {
cookies,
storage,
timestamp: Date.now(),
url: await this.page.url()
};
const sessionFile = path.join(this.sessionPath, 'session-data.json');
fs.mkdirSync(this.sessionPath, { recursive: true });
fs.writeFileSync(sessionFile, JSON.stringify(sessionData, null, 2));
console.log(`πŸ’Ύ Session saved: ${cookies.length} cookies + storage data`);
} catch (error) {
console.warn("⚠️ Could not save session:", error);
}
}
private async loadSession() {
if (!this.sessionPath || !this.page) return;
try {
const sessionFile = path.join(this.sessionPath, 'session-data.json');
if (fs.existsSync(sessionFile)) {
const sessionData = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
// Check session expiration
const sessionMaxAge = (this as any).sessionMaxAge || (72 * 60 * 60 * 1000); // Default 72 hours
const sessionAge = Date.now() - (sessionData.timestamp || 0);
if (sessionAge > sessionMaxAge) {
console.log(`⏰ Session expired (${Math.round(sessionAge / (1000 * 60 * 60))} hours old), starting fresh`);
await PuppeteerHelper.deleteSession(path.basename(this.sessionPath));
return;
}
// Load cookies first
if (sessionData.cookies && sessionData.cookies.length > 0) {
await this.page.setCookie(...sessionData.cookies);
}
// Store storage data for later loading (after navigation)
(this as any).pendingStorage = sessionData.storage;
console.log(`πŸ”„ Session loaded: ${sessionData.cookies?.length || 0} cookies ready + storage pending`);
// Log session age
if (sessionData.timestamp) {
const ageHours = Math.round((Date.now() - sessionData.timestamp) / (1000 * 60 * 60));
console.log(`πŸ“… Session age: ${ageHours} hours`);
}
} else {
console.log("πŸ“ No existing session found, starting fresh");
}
} catch (error) {
console.warn("⚠️ Could not load session:", error);
}
}
// New method to restore storage after navigation
private async restoreStorageAfterNavigation() {
const pendingStorage = (this as any).pendingStorage;
if (!pendingStorage || !this.page) return;
try {
await this.page.evaluate((storage: {
localStorage?: Record<string, string | null>;
sessionStorage?: Record<string, string | null>;
}) => {
try {
// Restore localStorage
if (storage.localStorage && window.localStorage) {
Object.entries(storage.localStorage).forEach(([key, value]) => {
if (value !== null && typeof value === 'string') {
window.localStorage.setItem(key, value);
}
});
}
// Restore sessionStorage
if (storage.sessionStorage && window.sessionStorage) {
Object.entries(storage.sessionStorage).forEach(([key, value]) => {
if (value !== null && typeof value === 'string') {
window.sessionStorage.setItem(key, value);
}
});
}
} catch (e) {
// Silently fail if storage is not accessible
}
}, pendingStorage);
console.log(`πŸ”„ Storage restored after navigation`);
// Clear pending storage
delete (this as any).pendingStorage;
} catch (error) {
console.warn("⚠️ Could not restore storage after navigation:", error);
}
}
// Enhanced configuration builder with all features
private static buildOptions(options?: {
headless?: boolean;
proxy?: { host: string; port: string; username?: string; password?: string };
userAgent?: string;
plugins?: any[];
windowSize?: { width: number; height: number };
executablePath?: string;
}): PuppeteerHelperOptions {
const args = ["--start-maximized"];
// Add proxy args if provided
if (options?.proxy) {
args.push(`--proxy-server=${options.proxy.host}:${options.proxy.port}`);
}
// Add user agent if provided
if (options?.userAgent) {
args.push(`--user-agent=${options.userAgent}`);
}
// Add window size if provided
if (options?.windowSize) {
args.push(`--window-size=${options.windowSize.width},${options.windowSize.height}`);
}
const baseOptions: PuppeteerHelperOptions = {
headless: options?.headless ?? false,
turnstile: true,
args,
customConfig: {}, // Deliberately NOT setting userDataDir to avoid ECONNREFUSED
connectOption: {
defaultViewport: options?.windowSize ? {
width: options.windowSize.width,
height: options.windowSize.height
} : null,
},
disableXvfb: false,
ignoreAllFlags: false,
plugins: options?.plugins ?? [require("puppeteer-extra-plugin-click-and-wait")()],
executablePath: options?.executablePath
};
// Add proxy configuration
if (options?.proxy) {
baseOptions.proxy = {
host: options.proxy.host,
port: parseInt(options.proxy.port),
username: options.proxy.username,
password: options.proxy.password
};
}
return baseOptions;
}
// Session management methods
static createWithSession(sessionName: string, options?: {
headless?: boolean;
proxy?: { host: string; port: string; username?: string; password?: string };
userAgent?: string;
plugins?: any[];
windowSize?: { width: number; height: number };
executablePath?: string;
sessionMaxAge?: number; // Session expiration in hours (default: 72 hours)
}) {
const sessionPath = `./data/sessions/${sessionName}`;
// Auto-create the sessions directory structure
try {
fs.mkdirSync(sessionPath, { recursive: true });
console.log(`πŸ“ Session directory created: ${sessionPath}`);
} catch (error) {
// Directory might already exist, which is fine
}
const sessionOptions = PuppeteerHelper.buildOptions(options);
const helper = new PuppeteerHelper(sessionOptions, sessionPath);
// Set session max age (default 72 hours)
(helper as any).sessionMaxAge = (options?.sessionMaxAge || 72) * 60 * 60 * 1000;
return helper;
}
// Create a temporary instance without session persistence
static createTemporary(options?: {
headless?: boolean;
proxy?: { host: string; port: string; username?: string; password?: string };
userAgent?: string;
plugins?: any[];
windowSize?: { width: number; height: number };
executablePath?: string;
}) {
console.log(`πŸš€ Creating temporary browser instance (no session persistence)`);
const tempOptions = PuppeteerHelper.buildOptions(options);
const helper = new PuppeteerHelper(tempOptions);
return helper;
}
async init(retries = 3) {
if (this.isInitialized) return { browser: this.browser, page: this.page };
console.log("πŸš€ Initializing puppeteer-real-browser...");
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const { browser, page } = await connect(this.options);
this.browser = browser;
this.page = page;
this.isInitialized = true;
// Load session if this is a session browser
if (this.sessionPath) {
await this.loadSession();
}
console.log("βœ… Browser initialized successfully!");
return { browser, page };
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Browser initialization attempt ${attempt}/${retries} failed: ${errorMsg}`);
if (attempt === retries) {
throw new Error(`Failed to initialize browser after ${retries} attempts: ${errorMsg}`);
}
// Wait before retry
console.log(`⏳ Waiting 2 seconds before retry...`);
await PuppeteerHelper.delay(2000);
}
}
throw new Error("Unexpected error in browser initialization");
}
private async ensureInitialized() {
if (!this.isInitialized) {
await this.init();
}
}
async getPage(): Promise<PuppeteerPage> {
await this.ensureInitialized();
if (!this.page) {
throw new Error("Failed to initialize page");
}
return this.page;
}
async getBrowser() {
await this.ensureInitialized();
return this.browser;
}
async newPage() {
const browser = await this.getBrowser();
return await browser?.newPage();
}
async destroy() {
try {
// Save session if this is a session browser
if (this.sessionPath) {
await this.saveSession();
}
if (this.browser) {
await this.browser.close();
console.log("πŸ‘‹ Browser closed successfully!");
}
} catch (e) {
console.error("Error closing browser:", e);
}
this.isInitialized = false;
this.browser = null;
this.page = null;
}
// Static helper methods that work with any page
static getRandomInt(min = 0, max = Number.MAX_VALUE) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
static delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
static async wait(page: PuppeteerPage, milliseconds?: number) {
const timeout = milliseconds ?? PuppeteerHelper.getRandomInt(
PuppeteerConstants.defaultMinWait,
PuppeteerConstants.defaultMaxWait
);
console.log(`⏳ Waiting ${timeout}ms...`);
await PuppeteerHelper.delay(timeout);
}
static async navigateTo(page: PuppeteerPage, url: string, retries = PuppeteerConstants.maxGotoRetries) {
console.log(`🌐 Navigating to: ${url}`);
for (let i = 0; i < retries; i++) {
try {
const response = await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 90000
});
if (response && response.ok()) {
console.log(`βœ… Successfully loaded: ${url}`);
await PuppeteerHelper.wait(page);
return true;
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Attempt ${i + 1} failed: ${errorMsg}`);
if (i < retries - 1) {
await PuppeteerHelper.wait(page);
}
}
}
console.log(`❌ Failed to load ${url} after ${retries} attempts`);
return false;
}
static async clickElement(page: PuppeteerPage, selector: string, retries = 3) {
console.log(`πŸ–±οΈ Clicking: ${selector}`);
for (let i = 0; i < retries; i++) {
try {
await page.waitForSelector(selector, { timeout: PuppeteerConstants.defaultButtonClickTimeout });
await page.click(selector);
await PuppeteerHelper.wait(page);
console.log(`βœ… Successfully clicked: ${selector}`);
return true;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Click attempt ${i + 1} failed: ${errorMsg}`);
if (i < retries - 1) {
await PuppeteerHelper.wait(page, 1000);
}
}
}
console.log(`❌ Failed to click ${selector} after ${retries} attempts`);
return false;
}
static async clickAndWaitForNavigation(page: any, selector: string, options?: { timeout?: number }) {
console.log(`πŸ–±οΈ Click and wait for navigation: ${selector}`);
try {
if (page.clickAndWaitForNavigation) {
// Use the plugin method if available
await page.clickAndWaitForNavigation(selector, options);
console.log(`βœ… Successfully clicked and waited for navigation: ${selector}`);
return true;
} else {
// Fallback to manual implementation
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: options?.timeout || 30000 }),
page.click(selector)
]);
console.log(`βœ… Successfully clicked and waited for navigation (fallback): ${selector}`);
return true;
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to click and wait for navigation: ${errorMsg}`);
return false;
}
}
static async typeText(page: PuppeteerPage, selector: string, text: string, clearFirst = true) {
console.log(`⌨️ Typing: ${text}`);
try {
await page.waitForSelector(selector, { timeout: PuppeteerConstants.defaultButtonClickTimeout });
await page.click(selector);
if (clearFirst) {
await page.evaluate((sel: string) => {
const element = document.querySelector(sel) as HTMLInputElement;
if (element) {
element.value = '';
element.dispatchEvent(new Event('input', { bubbles: true }));
}
}, selector);
}
await page.type(selector, text, { delay: 50 });
await PuppeteerHelper.wait(page, 500);
console.log(`βœ… Successfully typed text`);
return true;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to type text: ${errorMsg}`);
return false;
}
}
static async scroll(page: PuppeteerPage, direction: 'up' | 'down' = 'down', times = 1) {
console.log(`πŸ“œ Scrolling ${direction} ${times} times...`);
for (let i = 0; i < times; i++) {
await page.evaluate((dir) => {
if (dir === 'up') {
window.scrollBy({ top: -500, behavior: 'smooth' });
} else {
window.scrollBy({ top: 500, behavior: 'smooth' });
}
}, direction);
await PuppeteerHelper.delay(800);
}
console.log(`βœ… Finished scrolling ${direction}`);
}
static async scrollToTop(page: PuppeteerPage) {
console.log(`πŸ“œ Scrolling to top...`);
await page.evaluate(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
await PuppeteerHelper.wait(page);
}
static async scrollToBottom(page: PuppeteerPage) {
console.log(`πŸ“œ Scrolling to bottom...`);
await page.evaluate(() => {
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
});
await PuppeteerHelper.wait(page);
}
static async getCurrentUrl(page: PuppeteerPage) {
const url = await page.url();
console.log(`πŸ”— Current URL: ${url}`);
return url;
}
static async takeScreenshot(page: PuppeteerPage, filepath = `./screenshots/screenshot-${Date.now()}.png`) {
try {
// Ensure directory exists
const dir = path.dirname(filepath);
await fs.promises.mkdir(dir, { recursive: true });
await page.screenshot({ path: filepath, fullPage: true });
console.log(`πŸ“Έ Screenshot saved: ${filepath}`);
return filepath;
} catch (e: any) {
console.log(`❌ Failed to take screenshot:`, e?.message || e);
return null;
}
}
static async getPageTitle(page: PuppeteerPage) {
const title = await page.title();
console.log(`πŸ“„ Page title: ${title}`);
return title;
}
static async waitForElement(page: PuppeteerPage, selector: string, timeout = 30000) {
console.log(`⏳ Waiting for element: ${selector}`);
try {
await page.waitForSelector(selector, { timeout });
console.log(`βœ… Element found: ${selector}`);
return true;
} catch (e) {
console.log(`❌ Element not found: ${selector}`);
return false;
}
}
static async isElementVisible(page: PuppeteerPage, selector: string) {
try {
const element = await page.$(selector);
if (!element) return false;
const isVisible = await page.evaluate((el: any) => {
const style = window.getComputedStyle(el);
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
}, element);
return isVisible;
} catch (e) {
return false;
}
}
static async getText(page: any, selector: string) {
try {
const text = await page.$eval(selector, (el: any) => el.textContent?.trim());
console.log(`πŸ“ Text from ${selector}: ${text}`);
return text;
} catch (e) {
console.log(`❌ Failed to get text from ${selector}`);
return null;
}
}
static async getAllTexts(page: any, selector: string) {
try {
const texts = await page.$$eval(selector, (elements: any[]) =>
elements.map((el: any) => el.textContent?.trim()).filter((text: any) => text)
);
console.log(`πŸ“ Found ${texts.length} text elements for ${selector}`);
return texts;
} catch (e) {
console.log(`❌ Failed to get texts from ${selector}`);
return [];
}
}
static async uploadFile(page: any, inputSelector: string, filePath: string) {
console.log(`πŸ“€ Uploading file: ${filePath} to ${inputSelector}`);
try {
const input = await page.$(inputSelector);
if (!input) {
console.log(`❌ File input not found: ${inputSelector}`);
return false;
}
await input.uploadFile(filePath);
await PuppeteerHelper.wait(page, PuppeteerConstants.defaultUploadWait);
console.log(`βœ… File uploaded successfully`);
return true;
} catch (e: any) {
console.log(`❌ Failed to upload file:`, e?.message || e);
return false;
}
}
static async downloadFile(page: any, url: string, downloadPath: string) {
console.log(`⬇️ Downloading file from: ${url}`);
try {
const response = await page.goto(url);
const buffer = await response.buffer();
// Ensure directory exists
const dir = path.dirname(downloadPath);
await fs.promises.mkdir(dir, { recursive: true });
await fs.promises.writeFile(downloadPath, buffer);
console.log(`βœ… File downloaded: ${downloadPath}`);
return true;
} catch (e: any) {
console.log(`❌ Failed to download file:`, e?.message || e);
return false;
}
}
static async setViewport(page: any, width: number, height: number) {
console.log(`πŸ“± Setting viewport: ${width}x${height}`);
try {
await page.setViewport({ width, height });
return true;
} catch (e: any) {
console.log(`❌ Failed to set viewport:`, e?.message || e);
return false;
}
}
static async setMobileViewport(page: any) {
return await PuppeteerHelper.setViewport(page, 390, 844);
}
static async setDesktopViewport(page: any) {
return await PuppeteerHelper.setViewport(page, 1920, 1080);
}
static async evaluateScript(page: any, script: string, ...args: any[]) {
try {
const result = await page.evaluate(script, ...args);
console.log(`βœ… Script executed successfully`);
return result;
} catch (e: any) {
console.log(`❌ Script execution failed:`, e?.message || e);
return null;
}
}
static async addStylesheet(page: any, css: string) {
try {
await page.addStyleTag({ content: css });
console.log(`βœ… CSS added successfully`);
return true;
} catch (e: any) {
console.log(`❌ Failed to add CSS:`, e?.message || e);
return false;
}
}
static async blockResources(page: PuppeteerPage, resourceTypes = ['image', 'media', 'font']) {
console.log(`🚫 Blocking resources: ${resourceTypes.join(', ')}`);
await page.setRequestInterception(true);
page.on('request', (req: any) => {
if (resourceTypes.includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
}
// Simple utility functions
static async clearInput(page: PuppeteerPage, selector: string) {
console.log(`🧹 Clearing input`);
try {
await page.click(selector, { clickCount: 3 });
await page.keyboard.press('Backspace');
console.log(`βœ… Input cleared`);
return true;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to clear input: ${errorMsg}`);
return false;
}
}
static async selectDropdownOption(page: PuppeteerPage, selector: string, value: string) {
console.log(`🎯 Selecting option: ${value}`);
try {
await page.select(selector, value);
console.log(`βœ… Option selected`);
return true;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to select option: ${errorMsg}`);
return false;
}
}
static async hoverElement(page: PuppeteerPage, selector: string) {
console.log(`πŸ–±οΈ Hovering over element`);
try {
await page.hover(selector);
await PuppeteerHelper.wait(page, 500);
console.log(`βœ… Hovered successfully`);
return true;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to hover: ${errorMsg}`);
return false;
}
}
static async refresh(page: PuppeteerPage) {
console.log(`πŸ”„ Refreshing page`);
try {
await page.reload({ waitUntil: 'domcontentloaded' });
await PuppeteerHelper.wait(page);
console.log(`βœ… Page refreshed`);
return true;
} catch (e: any) {
console.log(`❌ Failed to refresh`);
return false;
}
}
static async goBack(page: PuppeteerPage) {
console.log(`⬅️ Going back`);
try {
await page.goBack({ waitUntil: 'domcontentloaded' });
await PuppeteerHelper.wait(page);
console.log(`βœ… Went back successfully`);
return true;
} catch (e: any) {
console.log(`❌ Failed to go back`);
return false;
}
}
static async pressKey(page: PuppeteerPage, key: string) {
console.log(`⌨️ Pressing: ${key}`);
try {
await page.keyboard.press(key as any);
await PuppeteerHelper.wait(page, 200);
console.log(`βœ… Key pressed`);
return true;
} catch (e: any) {
console.log(`❌ Failed to press key`);
return false;
}
}
static async waitForText(page: PuppeteerPage, text: string, timeout = 10000) {
console.log(`⏳ Waiting for text: "${text}"`);
try {
await page.waitForFunction(
(txt) => document.body.innerText.includes(txt),
{ timeout },
text
);
console.log(`βœ… Text found`);
return true;
} catch (e: any) {
console.log(`❌ Text not found`);
return false;
}
}
static async getElementCount(page: PuppeteerPage, selector: string) {
try {
const elements = await page.$$(selector);
console.log(`πŸ”’ Found ${elements.length} elements`);
return elements.length;
} catch (e: any) {
console.log(`❌ Failed to count elements`);
return 0;
}
}
// Session utility methods
static async listSessions() {
try {
const sessionsDir = './data/sessions';
if (!fs.existsSync(sessionsDir)) {
console.log(`πŸ“ No sessions directory found`);
return [];
}
const sessions = fs.readdirSync(sessionsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
console.log(`πŸ“ Found ${sessions.length} sessions: ${sessions.join(', ')}`);
return sessions;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to list sessions: ${errorMsg}`);
return [];
}
}
static async deleteSession(sessionName: string) {
try {
const sessionPath = `./data/sessions/${sessionName}`;
if (fs.existsSync(sessionPath)) {
await fs.promises.rm(sessionPath, { recursive: true, force: true });
console.log(`πŸ—‘οΈ Session deleted: ${sessionName}`);
return true;
} else {
console.log(`⚠️ Session not found: ${sessionName}`);
return false;
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to delete session: ${errorMsg}`);
return false;
}
}
static async sessionExists(sessionName: string) {
const sessionPath = `./data/sessions/${sessionName}`;
return fs.existsSync(sessionPath);
}
// Get current session name (if any) - improved implementation
getSessionName() {
if (this.sessionPath) {
return path.basename(this.sessionPath);
}
return null;
}
// Instance methods for convenience (enhanced with storage restoration)
async navigate(url: string) {
const page = await this.getPage();
if (!page) throw new Error("Page not initialized");
const result = await PuppeteerHelper.navigateTo(page, url);
// Restore storage after navigation if we have pending storage
await this.restoreStorageAfterNavigation();
return result;
}
async click(selector: string, retries = 3) {
const page = await this.getPage();
return await PuppeteerHelper.clickElement(page, selector, retries);
}
async type(selector: string, text: string, clearFirst = true) {
const page = await this.getPage();
return await PuppeteerHelper.typeText(page, selector, text, clearFirst);
}
async wait(milliseconds?: number) {
const page = await this.getPage();
return await PuppeteerHelper.wait(page, milliseconds);
}
async screenshot(filepath?: string) {
const page = await this.getPage();
return await PuppeteerHelper.takeScreenshot(page, filepath);
}
async scroll(direction: 'up' | 'down' = 'down', times = 1) {
const page = await this.getPage();
return await PuppeteerHelper.scroll(page, direction, times);
}
async getNewPage(): Promise<PuppeteerPage> {
await this.ensureInitialized();
if (!this.browser) {
throw new Error("Browser not initialized");
}
console.log('πŸ“„ Creating new page...');
const newPage = await this.browser.newPage();
console.log('βœ… New page created successfully');
return newPage as PuppeteerPage;
}
static async cleanupExpiredSessions(maxAgeHours = 72) {
try {
const sessionsDir = './data/sessions';
if (!fs.existsSync(sessionsDir)) {
return [];
}
const sessions = fs.readdirSync(sessionsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
const maxAge = maxAgeHours * 60 * 60 * 1000;
const cleanedSessions: string[] = [];
for (const sessionName of sessions) {
const sessionFile = path.join(sessionsDir, sessionName, 'session-data.json');
if (fs.existsSync(sessionFile)) {
try {
const sessionData = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
const sessionAge = Date.now() - (sessionData.timestamp || 0);
if (sessionAge > maxAge) {
await PuppeteerHelper.deleteSession(sessionName);
cleanedSessions.push(sessionName);
}
} catch (error) {
// If session file is corrupted, delete it
console.log(`πŸ—‘οΈ Removing corrupted session: ${sessionName}`);
await PuppeteerHelper.deleteSession(sessionName);
cleanedSessions.push(sessionName);
}
}
}
if (cleanedSessions.length > 0) {
console.log(`🧹 Cleaned up ${cleanedSessions.length} expired sessions: ${cleanedSessions.join(', ')}`);
}
return cleanedSessions;
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to cleanup sessions: ${errorMsg}`);
return [];
}
}
static async getSessionInfo(sessionName: string) {
try {
const sessionPath = `./data/sessions/${sessionName}`;
const sessionFile = path.join(sessionPath, 'session-data.json');
if (!fs.existsSync(sessionFile)) {
return null;
}
const sessionData = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
const sessionAge = Date.now() - (sessionData.timestamp || 0);
const ageHours = Math.round(sessionAge / (1000 * 60 * 60));
return {
name: sessionName,
ageHours,
cookieCount: sessionData.cookies?.length || 0,
hasStorage: !!(sessionData.storage?.localStorage || sessionData.storage?.sessionStorage),
lastUrl: sessionData.url,
created: new Date(sessionData.timestamp || 0).toISOString()
};
} catch (error) {
return null;
}
}
// Enhanced session listing with detailed info
static async listSessionsDetailed() {
try {
const sessions = await PuppeteerHelper.listSessions();
const sessionDetails = await Promise.all(
sessions.map(sessionName => PuppeteerHelper.getSessionInfo(sessionName))
);
return sessionDetails.filter(Boolean);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.log(`❌ Failed to list sessions: ${errorMsg}`);
return [];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment