claude-playwright
Version:
Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing
377 lines (356 loc) โข 14.4 kB
JavaScript
"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