aboutsummaryrefslogtreecommitdiff
path: root/playwright-js/run.js
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
committerCraig Jennings <c@cjennings.net>2026-04-19 15:24:51 -0500
commit4ffa7417a359ef4eae09f61d7da4de06539462ca (patch)
treeb8eeb8aa5ec2344216c0f0cdcdcc82d0df307ce3 /playwright-js/run.js
parent11f5f003eef12bff9633ca8190e3c43c7dab6708 (diff)
downloadrulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.tar.gz
rulesets-4ffa7417a359ef4eae09f61d7da4de06539462ca.zip
refactor(playwright): split into playwright-js + playwright-py variants
Rename `playwright-skill/` → `playwright-js/` and add `playwright-py/` as a verbatim fork of Anthropic's official `webapp-testing` skill (Apache-2.0). Cross-pollinate: each skill gains patterns and helpers inspired by the other's strengths, with upstream semantics preserved. ## playwright-js (JS/TS stack) Renamed from playwright-skill; upstream lackeyjb MIT content untouched. New sections added (clearly marked, preserving upstream semantics): - Static HTML vs Dynamic Webapp decision tree (core Anthropic methodology) - Reconnaissance-Then-Action pattern (navigate → networkidle → inspect → act) - Console Log Capture snippet (page.on console/pageerror/requestfailed) Description updated to clarify JS/TS stack fit (React/Next/Vue/Svelte/Node) and reference `/playwright-py` as the Python sibling. ## playwright-py (Python stack) Verbatim fork of anthropics/skills/skills/webapp-testing; upstream SKILL.md and bundled `scripts/with_server.py` + examples kept intact. New scripts and examples added (all lackeyjb-style conveniences in Python): Scripts: scripts/detect_dev_servers.py Probe common localhost ports for HTTP servers; outputs JSON of found services. scripts/safe_actions.py safe_click, safe_type (retry-wrapped), handle_cookie_banner (common selectors), build_context_with_headers (env-var- driven: PW_HEADER_NAME / PW_HEADER_VALUE / PW_EXTRA_HEADERS='{…json…}'). Examples: examples/login_flow.py Login form + wait_for_url. examples/broken_links.py Scan visible external hrefs via HEAD. examples/responsive_sweep.py Multi-viewport screenshots to /tmp. SKILL.md gains 5 "Added:" sections documenting the new scripts, retry helpers, env-header injection, and /tmp script discipline. Attribution notes explicitly mark upstream vs local additions. ## Makefile SKILLS: playwright-skill → playwright-js + playwright-py deps target: extended Playwright step to install Python package + Chromium via `python3 -m pip install --user playwright && python3 -m playwright install chromium` when playwright-py/ is present. Idempotent (detected via `python3 -c "import playwright"`). ## Usage Both skills symlinked globally via `make install`. Invoke whichever matches the project stack — cross-references in descriptions route you to the right one. Run `make deps` once to install both runtimes.
Diffstat (limited to 'playwright-js/run.js')
-rwxr-xr-xplaywright-js/run.js228
1 files changed, 228 insertions, 0 deletions
diff --git a/playwright-js/run.js b/playwright-js/run.js
new file mode 100755
index 0000000..10f2616
--- /dev/null
+++ b/playwright-js/run.js
@@ -0,0 +1,228 @@
+#!/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');
+ 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');
+
+// Extra headers from environment variables (if configured)
+const __extraHeaders = helpers.getExtraHeadersFromEnv();
+
+/**
+ * Utility to merge environment headers into context options.
+ * Use when creating contexts with raw Playwright API instead of helpers.createContext().
+ * @param {Object} options - Context options
+ * @returns {Object} Options with extraHTTPHeaders merged in
+ */
+function getContextOptionsWithHeaders(options = {}) {
+ if (!__extraHeaders) return options;
+ return {
+ ...options,
+ extraHTTPHeaders: {
+ ...__extraHeaders,
+ ...(options.extraHTTPHeaders || {})
+ }
+ };
+}
+
+(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('🎭 Playwright Skill - Universal Executor\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);
+});