claude-playwright
Version:
Seamless integration between Claude Code and Playwright MCP for efficient browser automation and testing
209 lines (201 loc) ⢠7.67 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scaffoldPage = scaffoldPage;
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const chalk_1 = __importDefault(require("chalk"));
const readline_1 = __importDefault(require("readline"));
/**
* Main function to scaffold a new Page Object
*/
async function scaffoldPage(name, options = {}) {
const className = toClassName(name);
const fileName = toFileName(name);
const targetDir = options.path || 'src/pages';
const filePath = path_1.default.join(targetDir, `${className}.ts`);
console.log(chalk_1.default.blue(`šļø Scaffolding ${className}...`));
// Check if file exists
if (await fs_extra_1.default.pathExists(filePath)) {
const overwrite = await askConfirmation(`${filePath} already exists. Overwrite?`);
if (!overwrite) {
console.log(chalk_1.default.yellow('ā Scaffolding cancelled'));
return false;
}
}
// Interactive mode for collecting page elements
let elements = [];
if (options.interactive !== false) {
console.log(chalk_1.default.cyan('\nš Let\'s define the page elements...'));
elements = await collectPageElements();
}
// Generate page content
const pageContent = generatePageContent(className, fileName, elements, options.url);
// Ensure directory exists and write file
await fs_extra_1.default.ensureDir(targetDir);
await fs_extra_1.default.writeFile(filePath, pageContent);
// Success feedback
console.log(chalk_1.default.green('ā
Page scaffolded successfully!'));
console.log(chalk_1.default.gray(`š Location: ${filePath}`));
console.log(chalk_1.default.gray(`š¦ Import with: import { ${className} } from './${className}';`));
// Generate corresponding test file suggestion
const testPath = filePath.replace('/pages/', '/tests/').replace('.ts', '.spec.ts');
console.log(chalk_1.default.blue(`\nš” Suggestion: Generate test with:`));
console.log(chalk_1.default.gray(` claude-playwright scaffold test ${className} --path ${path_1.default.dirname(testPath)}`));
return true;
}
/**
* Interactive collection of page elements
*/
async function collectPageElements() {
const elements = [];
const rl = readline_1.default.createInterface({
input: process.stdin,
output: process.stdout
});
const askQuestion = (question) => {
return new Promise((resolve) => {
rl.question(question, resolve);
});
};
try {
while (true) {
console.log(chalk_1.default.cyan('\nš Add a page element (or press Enter to finish):'));
const elementName = await askQuestion('Element name (e.g., loginButton, emailField): ');
if (!elementName.trim())
break;
const selector = await askQuestion('CSS selector (e.g., #login-btn, [data-testid="email"]): ');
if (!selector.trim())
continue;
const type = await askQuestion('Type (locator/action/assertion) [locator]: ') || 'locator';
elements.push({
name: toCamelCase(elementName),
selector,
type: type
});
console.log(chalk_1.default.green(`ā Added ${elementName} -> ${selector}`));
}
}
finally {
rl.close();
}
return elements;
}
/**
* Generate the complete page class content
*/
function generatePageContent(className, fileName, elements, url, browserProfile) {
const locators = elements.filter(e => e.type === 'locator' || !e.type);
const actionElements = elements.filter(e => e.type === 'action');
const assertionElements = elements.filter(e => e.type === 'assertion');
return `import { Page, expect, Locator } from '@playwright/test';
import { BasePage } from '../pages/base/BasePage';
/**
* ${className} - Page Object Model
* Generated by claude-playwright scaffold
*/
export class ${className} extends BasePage {
constructor(page: Page) {
super(page);
}
// Page URL
private readonly pageUrl = '${url || `/${fileName}`}';${browserProfile ? `\n // Browser Profile\n private readonly browserProfile = '${browserProfile}';` : ''}
// Locators${generateLocators(locators)}
// Navigation
async goto(): Promise<void> {
await this.navigateTo(this.pageUrl);
await this.waitForNetworkIdle();
}
async waitForLoad(): Promise<void> {
${locators.length > 0 ? `await this.waitForElement('${locators[0].selector}');` : 'await this.waitForNetworkIdle();'}
}
// Actions${generateActions(actionElements, locators)}
// Assertions${generateAssertions(assertionElements, locators)}
// Helper Methods
async isLoaded(): Promise<boolean> {
${locators.length > 0 ? `return await this.isElementVisible('${locators[0].selector}');` : 'return true;'}
}
}
`;
}
/**
* Generate locator definitions
*/
function generateLocators(locators) {
if (locators.length === 0) {
return `
private readonly mainContent = '[data-testid="main-content"], main, .content';`;
}
return locators.map(el => `\n private readonly ${el.name} = '${el.selector}';`).join('');
}
/**
* Generate action methods
*/
function generateActions(actionElements, allElements) {
const actions = actionElements.length > 0 ? actionElements : allElements.slice(0, 2);
if (actions.length === 0) {
return `
async clickMainContent(): Promise<void> {
await this.clickAndWait(this.mainContent);
}`;
}
return actions.map(el => {
const methodName = el.name.startsWith('click') ? el.name : `click${capitalize(el.name)}`;
return `\n async ${methodName}(): Promise<void> {
await this.clickAndWait(this.${el.name});
}`;
}).join('');
}
/**
* Generate assertion methods
*/
function generateAssertions(assertionElements, allElements) {
const assertions = assertionElements.length > 0 ? assertionElements : allElements.slice(0, 2);
if (assertions.length === 0) {
return `
async expectPageLoaded(): Promise<void> {
await this.expectElementVisible(this.mainContent);
}`;
}
return assertions.map(el => {
const methodName = el.name.startsWith('expect') ? el.name : `expect${capitalize(el.name)}Visible`;
return `\n async ${methodName}(): Promise<void> {
await this.expectElementVisible(this.${el.name});
}`;
}).join('');
}
/**
* Ask for user confirmation
*/
async function askConfirmation(question) {
const rl = readline_1.default.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
rl.question(`${question} (y/N): `, (answer) => {
rl.close();
resolve(answer.toLowerCase().startsWith('y'));
});
});
}
/**
* Utility functions
*/
function toClassName(name) {
const clean = name.replace(/[^a-zA-Z0-9]/g, '');
const className = clean.charAt(0).toUpperCase() + clean.slice(1);
return className.endsWith('Page') ? className : className + 'Page';
}
function toFileName(name) {
return name.toLowerCase().replace(/page$/i, '').replace(/[^a-z0-9]/gi, '-');
}
function toCamelCase(str) {
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '');
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
//# sourceMappingURL=scaffold-page.js.map
;