claude-playwright
Version:
Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing
334 lines • 13.9 kB
JavaScript
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
;