Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
653 changes: 653 additions & 0 deletions .claude/skills/playwright-skill/API_REFERENCE.md

Large diffs are not rendered by default.

453 changes: 453 additions & 0 deletions .claude/skills/playwright-skill/SKILL.md

Large diffs are not rendered by default.

441 changes: 441 additions & 0 deletions .claude/skills/playwright-skill/lib/helpers.js

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions .claude/skills/playwright-skill/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions .claude/skills/playwright-skill/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "playwright-skill",
"version": "4.1.0",
"description": "General-purpose browser automation with Playwright for Claude Code with auto-detection and smart test management",
"author": "lackeyjb",
"main": "run.js",
"scripts": {
"setup": "npm install && npx playwright install chromium",
"install-all-browsers": "npx playwright install chromium firefox webkit"
},
"keywords": [
"playwright",
"automation",
"browser-testing",
"web-automation",
"claude-skill",
"general-purpose"
],
"dependencies": {
"playwright": "^1.57.0"
},
"engines": {
"node": ">=14.0.0"
},
"license": "MIT"
}
228 changes: 228 additions & 0 deletions .claude/skills/playwright-skill/run.js
Original file line number Diff line number Diff line change
@@ -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);
});
18 changes: 18 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"root": true,
"env": {
"browser": true,
"es2022": true,
"node": true
},
"extends": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "script"
},
"rules": {
"no-unused-vars": "warn",
"no-empty": ["warn", { "allowEmptyCatch": true }],
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
41 changes: 40 additions & 1 deletion .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
.vscode/
# IDE
.vscode/
.idea/

# Test artifacts
test/.pw-profile/
.pw-profile/
test/.pw-*/
test/.pw-profile-*/
Comment on lines +8 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Expand ignore rules to cover nested Playwright profiles

The ignore patterns only match test/.pw-* at one directory depth, but this commit adds profile data under test/test/.pw-profile-debug* (including Cookies/Login Data databases). Because those paths are not ignored, future local test runs will keep committing machine-specific browser state and potentially more credentials. Update the pattern to match nested profile paths as well.

Useful? React with 👍 / 👎.

test-results/
dist/

# Scratch / probe scripts and tool run artifacts
.tmp-*
.claude/scheduled_tasks.lock
.claude/**/.temp-execution-*.js

# Node
node_modules/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# OS
.DS_Store
Thumbs.db

# Build
*.zip
build/
dist/

# Local config (test-only)
html/config-local-test.json

# Dev screenshots
preview-menu.png
*.png
!icons/*.png
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}
Loading