UNPKG

claude-playwright

Version:

Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing

334 lines 13.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BrowserProfileManager = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); /** * Browser Profile Manager for managing multiple browser profiles with different user roles */ class BrowserProfileManager { constructor(workingDir) { this.profilesDir = './browser-profiles'; this.authStatesDir = './playwright-auth'; if (workingDir) { this.profilesDir = path_1.default.join(workingDir, 'browser-profiles'); this.authStatesDir = path_1.default.join(workingDir, 'playwright-auth'); } } /** * Create a new browser profile for a specific role */ async createProfile(profileData) { await fs_extra_1.default.ensureDir(this.profilesDir); const profilePath = path_1.default.join(this.profilesDir, `${profileData.name}.json`); // Check if profile already exists if (await fs_extra_1.default.pathExists(profilePath)) { throw new Error(`Profile already exists: ${profileData.name}`); } const profile = { name: profileData.name, description: profileData.description || `Browser profile for ${profileData.role}`, role: profileData.role, createdAt: Date.now(), lastUsed: Date.now(), settings: { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', viewport: { width: 1920, height: 1080 }, locale: 'en-US', timezone: 'America/New_York', permissions: [], cookies: [], localStorage: {}, sessionStorage: {}, ...profileData.settings }, metadata: { tags: [], environment: 'dev', ...profileData.metadata } }; await fs_extra_1.default.writeJSON(profilePath, profile, { spaces: 2 }); // Create profile-specific directories const profileDataDir = path_1.default.join(this.profilesDir, profileData.name); await fs_extra_1.default.ensureDir(profileDataDir); await fs_extra_1.default.ensureDir(path_1.default.join(profileDataDir, 'downloads')); await fs_extra_1.default.ensureDir(path_1.default.join(profileDataDir, 'cache')); if (!profileData.silent) { console.log(chalk_1.default.green('✓') + ` Browser profile created: ${profileData.name}`); } return profilePath; } /** * Load a browser profile */ async loadProfile(profileName) { const profilePath = path_1.default.join(this.profilesDir, `${profileName}.json`); if (!await fs_extra_1.default.pathExists(profilePath)) { throw new Error(`Profile not found: ${profileName}`); } const profile = await fs_extra_1.default.readJSON(profilePath); // Update last used timestamp profile.lastUsed = Date.now(); await fs_extra_1.default.writeJSON(profilePath, profile, { spaces: 2 }); return profile; } /** * Update an existing profile */ async updateProfile(profileName, updates) { try { const profile = await this.loadProfile(profileName); // Merge updates const updatedProfile = { ...profile, ...updates, settings: { ...profile.settings, ...updates.settings }, metadata: { ...profile.metadata, ...updates.metadata }, lastUsed: Date.now() }; const profilePath = path_1.default.join(this.profilesDir, `${profileName}.json`); await fs_extra_1.default.writeJSON(profilePath, updatedProfile, { spaces: 2 }); console.log(chalk_1.default.green('✓') + ` Profile updated: ${profileName}`); return true; } catch (error) { console.error(chalk_1.default.red('Failed to update profile:'), error.message); return false; } } /** * List all browser profiles */ async listProfiles() { await fs_extra_1.default.ensureDir(this.profilesDir); const files = await fs_extra_1.default.readdir(this.profilesDir); const profiles = []; for (const file of files) { if (file.endsWith('.json')) { try { const profile = await fs_extra_1.default.readJSON(path_1.default.join(this.profilesDir, file)); profiles.push({ name: profile.name, role: profile.role, lastUsed: new Date(profile.lastUsed).toLocaleString(), environment: profile.metadata?.environment, description: profile.description, metadata: profile.metadata, configuration: { viewport: profile.settings?.viewport, userAgent: profile.settings?.userAgent, hasTouch: profile.settings?.hasTouch || false, isMobile: profile.settings?.isMobile || false, deviceScaleFactor: profile.settings?.deviceScaleFactor || 1, locale: profile.settings?.locale, timezone: profile.settings?.timezone } }); } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not read profile file ${file}`)); } } } // Sort by last used (most recent first) return profiles.sort((a, b) => new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime()); } /** * Delete a browser profile */ async deleteProfile(profileName, silent = false) { const profilePath = path_1.default.join(this.profilesDir, `${profileName}.json`); const profileDataDir = path_1.default.join(this.profilesDir, profileName); if (!await fs_extra_1.default.pathExists(profilePath)) { return false; } // Remove profile file await fs_extra_1.default.remove(profilePath); // Remove profile data directory if it exists if (await fs_extra_1.default.pathExists(profileDataDir)) { await fs_extra_1.default.remove(profileDataDir); } // Remove associated auth state if it exists const authStatePath = path_1.default.join(this.authStatesDir, `${profileName}-auth.json`); if (await fs_extra_1.default.pathExists(authStatePath)) { await fs_extra_1.default.remove(authStatePath); } if (!silent) { console.log(chalk_1.default.green('✓') + ` Profile deleted: ${profileName}`); } return true; } /** * Create predefined profiles for common roles */ async setupDefaultProfiles() { const defaultProfiles = [ { name: 'admin', role: 'administrator', description: 'Administrator user with full permissions', settings: { permissions: ['geolocation', 'camera', 'microphone', 'notifications'], viewport: { width: 1920, height: 1080 } }, metadata: { tags: ['admin', 'elevated'], environment: 'dev' } }, { name: 'user', role: 'regular_user', description: 'Standard user with basic permissions', settings: { permissions: ['geolocation'], viewport: { width: 1366, height: 768 } }, metadata: { tags: ['user', 'standard'], environment: 'dev' } }, { name: 'guest', role: 'guest', description: 'Guest user with minimal permissions', settings: { permissions: [], viewport: { width: 1280, height: 720 } }, metadata: { tags: ['guest', 'anonymous'], environment: 'dev' } }, { name: 'mobile', role: 'mobile_user', description: 'Mobile user simulation', settings: { userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', viewport: { width: 375, height: 667 }, permissions: ['geolocation'] }, metadata: { tags: ['mobile', 'ios'], environment: 'dev' } } ]; for (const profileData of defaultProfiles) { try { await this.createProfile(profileData); } catch (error) { if (error.message.includes('already exists')) { console.log(chalk_1.default.yellow(`⚠ Profile '${profileData.name}' already exists, skipping`)); } else { console.error(chalk_1.default.red(`Failed to create profile '${profileData.name}':`, error.message)); } } } console.log(chalk_1.default.green('✓') + ' Default profiles setup completed'); } /** * Get profile configuration for MCP integration */ async getProfileForSession(browserProfile) { try { if (!browserProfile) { // Try to find a default profile const profiles = await this.listProfiles(); const defaultProfile = profiles.find(p => p.name === 'default' || p.name === 'user'); if (defaultProfile) { return await this.loadProfile(defaultProfile.name); } return null; } return await this.loadProfile(browserProfile); } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not load profile '${browserProfile}':`, error.message)); return null; } } /** * Associate a session with a browser profile */ async associateSessionWithProfile(profileName, sessionName) { try { const profile = await this.loadProfile(profileName); // Update profile metadata to track associated sessions if (!profile.metadata) { profile.metadata = {}; } if (!profile.metadata.associatedSessions) { profile.metadata.associatedSessions = []; } const sessions = profile.metadata.associatedSessions || []; if (!sessions.includes(sessionName)) { sessions.push(sessionName); // Keep only the last 10 associated sessions if (sessions.length > 10) { sessions.splice(0, sessions.length - 10); } // Update the metadata profile.metadata.associatedSessions = sessions; } return await this.updateProfile(profileName, profile); } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not associate session '${sessionName}' with profile '${profileName}':`, error.message)); return false; } } /** * Get sessions associated with a profile */ async getAssociatedSessions(profileName) { try { const profile = await this.loadProfile(profileName); return profile.metadata?.associatedSessions || []; } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not get associated sessions for profile '${profileName}':`, error.message)); return []; } } /** * Find the best profile for auto-loading with a session */ async findBestProfileForAutoLoad() { try { const profiles = await this.listProfiles(); if (profiles.length === 0) { return null; } // Prioritize profiles by usage and type const prioritizedProfiles = profiles.sort((a, b) => { // First, prioritize by role (user > admin > guest > mobile) const roleOrder = { user: 4, admin: 3, guest: 2, mobile: 1 }; const aRoleScore = roleOrder[a.role] || 0; const bRoleScore = roleOrder[b.role] || 0; if (aRoleScore !== bRoleScore) { return bRoleScore - aRoleScore; } // Then by last used time (more recent first) return new Date(b.lastUsed).getTime() - new Date(a.lastUsed).getTime(); }); return prioritizedProfiles[0].name; } catch (error) { console.warn(chalk_1.default.yellow('Warning: Could not find best profile for auto-load:', error.message)); return null; } } } exports.BrowserProfileManager = BrowserProfileManager; //# sourceMappingURL=browser-profile.js.map