UNPKG

claude-playwright

Version:

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

377 lines (356 loc) โ€ข 14.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.initProject = initProject; exports.initProjectInteractive = initProjectInteractive; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); const init_interactive_1 = require("./init-interactive"); const template_selector_1 = require("./template-selector"); const template_generator_1 = require("./template-generator"); const mcp_setup_1 = require("./mcp-setup"); async function initProject(options) { const projectPath = path_1.default.join(process.cwd(), options.name); // Create project directory await fs_extra_1.default.ensureDir(projectPath); // Copy templates await copyTemplates(projectPath, options); // Generate MCP config await generateMCPConfig(projectPath); // Generate CLAUDE.md await generateClaudeMD(projectPath, options.name); console.log(chalk_1.default.green('โœ“') + ' Project structure created'); console.log(chalk_1.default.green('โœ“') + ' MCP configuration generated'); console.log(chalk_1.default.green('โœ“') + ' CLAUDE.md created'); } /** * Interactive project initialization */ async function initProjectInteractive() { console.log(chalk_1.default.blue('๐Ÿš€ Claude-Playwright Interactive Setup')); try { // Get user preferences through interactive prompts const options = await (0, init_interactive_1.promptForOptions)(); // Validate project name and directory const isValid = await (0, init_interactive_1.validateProjectName)(options.name); if (!isValid) { console.log(chalk_1.default.yellow('โš  Initialization cancelled')); return; } const projectPath = path_1.default.resolve(options.name); // Check Claude installation if MCP is requested if (options.configureMCP) { const claudeInstalled = await (0, mcp_setup_1.checkClaudeInstallation)(); if (!claudeInstalled) { console.log(chalk_1.default.red('โŒ Please install Claude Code first')); process.exit(1); } } // Select and load template const templateMetadata = await (0, template_selector_1.selectTemplate)(options.template); // Generate project from template const generator = new template_generator_1.TemplateGenerator(projectPath, { PROJECT_NAME: options.name, TEMPLATE_TYPE: options.template }); await generator.generateFromTemplate(options.template, templateMetadata); // Generate claude-playwright.config.js await generateProjectConfig(projectPath); // Setup MCP configuration if requested if (options.configureMCP) { await (0, mcp_setup_1.setupBrowserProfiles)(projectPath); const mcpSuccess = await (0, mcp_setup_1.setupMCPForClaude)(projectPath); if (!mcpSuccess) { console.log(chalk_1.default.yellow('โš  MCP configuration failed, but project was created')); } } // Install dependencies if requested if (options.installDeps) { await installDependencies(projectPath); } // Show completion message showCompletionMessage(options); } catch (error) { console.error(chalk_1.default.red('โŒ Initialization failed:'), error); process.exit(1); } } /** * Install project dependencies */ async function installDependencies(projectPath) { console.log(chalk_1.default.blue('\n๐Ÿ“ฆ Installing dependencies...')); const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process'))); return new Promise((resolve, reject) => { const npm = spawn('npm', ['install'], { cwd: projectPath, stdio: 'inherit', shell: true }); npm.on('close', (code) => { if (code === 0) { console.log(chalk_1.default.green('โœ“ Dependencies installed successfully')); resolve(); } else { console.log(chalk_1.default.yellow('โš  Failed to install dependencies automatically')); console.log(chalk_1.default.gray(' You can install them manually with: npm install')); resolve(); // Don't fail the whole process } }); npm.on('error', (err) => { console.log(chalk_1.default.yellow('โš  Failed to install dependencies automatically')); console.log(chalk_1.default.gray(' You can install them manually with: npm install')); resolve(); // Don't fail the whole process }); }); } /** * Show completion message with next steps */ function showCompletionMessage(options) { console.log(chalk_1.default.green('\n๐ŸŽ‰ Project initialized successfully!')); console.log(chalk_1.default.blue('\n๐Ÿ“‹ Next steps:')); console.log(chalk_1.default.white(`1. cd ${options.name}`)); if (!options.installDeps) { console.log(chalk_1.default.white('2. npm install')); console.log(chalk_1.default.white('3. npx playwright install')); } else { console.log(chalk_1.default.white('2. npx playwright install')); } if (options.configureMCP) { console.log(chalk_1.default.white('3. Restart Claude Code to load MCP configuration')); console.log(chalk_1.default.white('4. Open your project in Claude Code')); } console.log(chalk_1.default.white('5. Start writing tests with: npm test')); console.log(chalk_1.default.blue('\n๐Ÿงช Available Commands:')); console.log(chalk_1.default.gray(' npm test - Run all tests')); console.log(chalk_1.default.gray(' npm run test:ui - Run tests with UI mode')); console.log(chalk_1.default.gray(' npm run test:debug - Debug tests')); console.log(chalk_1.default.gray(' npm run codegen - Generate test code')); if (options.template === 'enterprise') { console.log(chalk_1.default.gray(' npm run test:docker - Run tests in Docker')); console.log(chalk_1.default.gray(' npm run test:visual - Visual regression tests')); } console.log(chalk_1.default.blue('\n๐ŸŽฏ You can now use Playwright with Claude Code integration!')); } async function copyTemplates(projectPath, options) { const templatePath = path_1.default.join(__dirname, '../../templates/minimal'); // Create directories const dirs = [ 'src/pages/base', 'src/fixtures', 'src/tests', 'src/utils', 'playwright-auth', 'browser-profiles' ]; for (const dir of dirs) { await fs_extra_1.default.ensureDir(path_1.default.join(projectPath, dir)); } // Copy package.json const packageJson = { name: options.name, version: '1.0.0', scripts: { 'test': 'playwright test', 'test:ui': 'playwright test --ui', 'test:debug': 'playwright test --debug' }, devDependencies: { '@playwright/test': '^1.40.0', 'typescript': '^5.0.0' } }; await fs_extra_1.default.writeJSON(path_1.default.join(projectPath, 'package.json'), packageJson, { spaces: 2 }); // Copy playwright.config.ts await fs_extra_1.default.writeFile(path_1.default.join(projectPath, 'playwright.config.ts'), PLAYWRIGHT_CONFIG_TEMPLATE); // Copy BasePage await fs_extra_1.default.writeFile(path_1.default.join(projectPath, 'src/pages/base/BasePage.ts'), BASE_PAGE_TEMPLATE); // Create example test await fs_extra_1.default.writeFile(path_1.default.join(projectPath, 'src/tests/example.spec.ts'), EXAMPLE_TEST_TEMPLATE); } async function generateMCPConfig(projectPath) { const config = { mcpServers: { playwright: { command: 'npx', args: [ '@playwright/mcp' ] } } }; await fs_extra_1.default.writeJSON(path_1.default.join(projectPath, 'mcp.config.json'), config, { spaces: 2 }); } async function generateProjectConfig(projectPath) { // Ask user for base URL const readline = await Promise.resolve().then(() => __importStar(require('readline'))); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const baseURL = await new Promise((resolve) => { rl.question(chalk_1.default.blue('๐ŸŒ Enter your application base URL (default: http://localhost:3000): '), (url) => { rl.close(); resolve(url.trim() || 'http://localhost:3000'); }); }); const configSource = path_1.default.join(__dirname, '../../templates/claude-playwright.config.js'); const configDest = path_1.default.join(projectPath, 'claude-playwright.config.js'); // Read template config let configContent = await fs_extra_1.default.readFile(configSource, 'utf8'); // Replace base URL configContent = configContent.replace("baseURL: process.env.BASE_URL || 'http://localhost:3000'", `baseURL: process.env.BASE_URL || '${baseURL}'`); await fs_extra_1.default.writeFile(configDest, configContent); console.log(chalk_1.default.green(`โœ“ Project config created with base URL: ${baseURL}`)); } async function generateClaudeMD(projectPath, projectName) { const claudeMd = `# ${projectName} - Claude Code Integration ## ๐Ÿšจ IMPORTANT RULES 1. **ALWAYS** check existing implementations before writing new code 2. **NEVER** rewrite existing functionality 3. **ALWAYS** extend BasePage for new Page Objects ## Project Structure - @src/pages/base/BasePage.ts - Base class for ALL Page Objects - @src/tests/ - Test files - @src/fixtures/ - Reusable fixtures - @playwright.config.ts - Playwright configuration ## Test Pattern \`\`\`typescript import { test, expect } from '@playwright/test'; import { BasePage } from '../pages/base/BasePage'; test('example test', async ({ page }) => { const basePage = new BasePage(page); await basePage.navigateTo('/'); await expect(page).toHaveTitle(/Example/); }); \`\`\` ## MCP Integration - Server runs on port 8931 - Browser profiles in ./browser-profiles/ - Auth states in ./playwright-auth/ ## Commands - npm test - Run tests - npm run test:ui - UI mode - npm run test:debug - Debug mode `; await fs_extra_1.default.writeFile(path_1.default.join(projectPath, 'CLAUDE.md'), claudeMd); } // Template-Strings const PLAYWRIGHT_CONFIG_TEMPLATE = `import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './src/tests', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }); `; const BASE_PAGE_TEMPLATE = `import { Page, expect } from '@playwright/test'; export class BasePage { readonly page: Page; readonly baseUrl: string; constructor(page: Page) { this.page = page; this.baseUrl = process.env.BASE_URL || 'http://localhost:3000'; } async navigateTo(path: string = '') { await this.page.goto(\`\${this.baseUrl}\${path}\`); await this.page.waitForLoadState('networkidle'); } async takeScreenshot(name: string) { await this.page.screenshot({ path: \`screenshots/\${name}.png\`, fullPage: true }); } async expectPageTitle(title: string | RegExp) { await expect(this.page).toHaveTitle(title); } async expectURL(url: string | RegExp) { await expect(this.page).toHaveURL(url); } async clickAndWait(selector: string) { await this.page.click(selector); await this.page.waitForLoadState('networkidle'); } async fillForm(fields: Record<string, string>) { for (const [selector, value] of Object.entries(fields)) { await this.page.fill(selector, value); } } } `; const EXAMPLE_TEST_TEMPLATE = `import { test, expect } from '@playwright/test'; import { BasePage } from '../pages/base/BasePage'; test.describe('Example Tests', () => { test('homepage should load', async ({ page }) => { const basePage = new BasePage(page); await basePage.navigateTo('/'); // Add your assertions here await expect(page).toHaveTitle(/Home/); }); test('should take screenshot', async ({ page }) => { const basePage = new BasePage(page); await basePage.navigateTo('/'); await basePage.takeScreenshot('homepage'); }); }); `; //# sourceMappingURL=init.js.map