#!/usr/bin/env node /** * Universal Playwright Executor for Claude Code * * Executes Playwright automation code from: * - File path: node run.js script.js * - Inline code: node run.js 'await page.goto("...")' * - Stdin: cat script.js | node run.js * * Ensures proper module resolution by running from skill directory. */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // Change to skill directory for proper module resolution process.chdir(__dirname); /** * Check if Playwright is installed */ function checkPlaywrightInstalled() { try { require.resolve('playwright'); return true; } catch (e) { return false; } } /** * Install Playwright if missing */ function installPlaywright() { console.log('šŸ“¦ Playwright not found. Installing...'); try { execSync('npm install', { stdio: 'inherit', cwd: __dirname }); execSync('npx playwright install chromium', { stdio: 'inherit', cwd: __dirname }); console.log('āœ… Playwright installed successfully'); return true; } catch (e) { console.error('āŒ Failed to install Playwright:', e.message); console.error('Please run manually: cd', __dirname, '&& npm run setup'); return false; } } /** * Get code to execute from various sources */ function getCodeToExecute() { const args = process.argv.slice(2); // Case 1: File path provided if (args.length > 0 && fs.existsSync(args[0])) { const filePath = path.resolve(args[0]); console.log(`šŸ“„ Executing file: ${filePath}`); return fs.readFileSync(filePath, 'utf8'); } // Case 2: Inline code provided as argument if (args.length > 0) { console.log('⚔ Executing inline code'); return args.join(' '); } // Case 3: Code from stdin if (!process.stdin.isTTY) { console.log('šŸ“„ Reading from stdin'); return fs.readFileSync(0, 'utf8'); } // No input console.error('āŒ No code to execute'); console.error('Usage:'); console.error(' node run.js script.js # Execute file'); console.error(' node run.js "code here" # Execute inline'); console.error(' cat script.js | node run.js # Execute from stdin'); console.error(''); console.error('Environment Variables:'); console.error(' CWD=/path/to/repo node run.js script.js # Set working directory for outputs'); process.exit(1); } /** * Clean up old temporary execution files from previous runs */ function cleanupOldTempFiles() { try { const files = fs.readdirSync(__dirname); const tempFiles = files.filter(f => f.startsWith('.temp-execution-') && f.endsWith('.js')); if (tempFiles.length > 0) { tempFiles.forEach(file => { const filePath = path.join(__dirname, file); try { fs.unlinkSync(filePath); } catch (e) { // Ignore errors - file might be in use or already deleted } }); } } catch (e) { // Ignore directory read errors } } /** * Wrap code in async IIFE if not already wrapped */ function wrapCodeIfNeeded(code) { // Check if code already has require() and async structure const hasRequire = code.includes('require('); const hasAsyncIIFE = code.includes('(async () => {') || code.includes('(async()=>{'); // If it's already a complete script, return as-is if (hasRequire && hasAsyncIIFE) { return code; } // If it's just Playwright commands, wrap in full template if (!hasRequire) { return ` const { chromium, firefox, webkit, devices } = require('playwright'); const helpers = require('./lib/helpers'); (async () => { try { ${code} } catch (error) { console.error('āŒ Automation error:', error.message); if (error.stack) { console.error(error.stack); } process.exit(1); } })(); `; } // If has require but no async wrapper if (!hasAsyncIIFE) { return ` (async () => { try { ${code} } catch (error) { console.error('āŒ Automation error:', error.message); if (error.stack) { console.error(error.stack); } process.exit(1); } })(); `; } return code; } /** * Main execution */ async function main() { console.log('šŸŽ­ Web Tests - Universal Executor\n'); // Show working directory info if (process.env.CWD) { console.log(`šŸ“ Working directory: ${process.env.CWD}`); console.log(`šŸ“ø Screenshots will be saved to: ${process.env.CWD}/.web-tests/screenshots/\n`); } // Clean up old temp files from previous runs cleanupOldTempFiles(); // Check Playwright installation if (!checkPlaywrightInstalled()) { const installed = installPlaywright(); if (!installed) { process.exit(1); } } // Get code to execute const rawCode = getCodeToExecute(); const code = wrapCodeIfNeeded(rawCode); // Create temporary file for execution const tempFile = path.join(__dirname, `.temp-execution-${Date.now()}.js`); try { // Write code to temp file fs.writeFileSync(tempFile, code, 'utf8'); // Execute the code console.log('šŸš€ Starting automation...\n'); require(tempFile); // Note: Temp file will be cleaned up on next run // This allows long-running async operations to complete safely } catch (error) { console.error('āŒ Execution failed:', error.message); if (error.stack) { console.error('\nšŸ“‹ Stack trace:'); console.error(error.stack); } process.exit(1); } } // Run main function main().catch(error => { console.error('āŒ Fatal error:', error.message); process.exit(1); });