Playwright is known for testing, but it’s a powerful automation tool for creative workflows beyond test suites. Many creative and repetitive tasks happen in browsers—automating them saves time and enables new workflows. This article explores Playwright patterns for creative workflows, authentication capture, batch processing, error handling, and real-world automation examples.
You script creativity. You automate the un-automatable. In this article, you’ll learn how to use Playwright for creative automation, from Adobe Firefly image generation to website analysis and screenshot capture. These aren’t theoretical examples—they’re production tools that demonstrate real automation patterns.
Beyond Testing: Creative Automation Use Cases
Playwright excels at browser automation, but its use cases extend far beyond testing. Here are three production examples that show different automation patterns.
FireWerk: Adobe Firefly Automation
FireWerk v2.0 automates Adobe Firefly for image and speech generation. The project uses Node.js (ESM), Playwright, Puppeteer, Express, CSV parsing, and dotenv. It demonstrates creative workflow automation where Playwright interacts with a web-based creative tool.
website-analyzer: Automated Analysis
website-analyzer performs automated accessibility and performance testing. It uses Node.js and Playwright to analyze websites across multiple devices, generating Markdown reports. This shows analysis automation—using Playwright to gather data and generate reports.
screenshot-tool-ui: Website Screenshot and Video Capture
screenshot-tool-ui captures website screenshots and videos using Playwright. It demonstrates capture automation—using Playwright to record visual content for documentation, testing, or archival purposes.
Why Playwright Over Other Tools?
Playwright offers several advantages for automation:
- Cross-browser support: Works with Chromium, Firefox, and WebKit
- Modern API: Async/await patterns, auto-waiting, and network interception
- Auto-waiting: Automatically waits for elements to be ready
- Network interception: Monitor and modify network requests
- Parallel execution: Run multiple browsers simultaneously
- Mobile emulation: Test and automate mobile experiences
These features make Playwright ideal for automation beyond testing.
FireWerk: Adobe Firefly Automation
FireWerk demonstrates creative workflow automation with Playwright. The project automates Adobe Firefly’s web interface for image and speech generation, handling authentication, batch processing, and error recovery.
Playwright Setup for Creative Tools
Setting up Playwright for creative tools requires persistent browser contexts to maintain authentication:
import { chromium } from 'playwright';
// Launch browser with persistent context
const browser = await chromium.launch({
headless: false, // Show browser for creative tools
});
const context = await browser.newContext({
// Persist authentication
storageState: 'auth-state.json',
});
const page = await context.newPage();
Authentication Capture and Persistence
Creative tools often require authentication. FireWerk captures and persists authentication state:
// Capture authentication
async function captureAuth() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
// Navigate to login page
await page.goto('https://firefly.adobe.com');
// Perform login (manual or automated)
await page.fill('#email', process.env.ADOBE_EMAIL);
await page.fill('#password', process.env.ADOBE_PASSWORD);
await page.click('button[type="submit"]');
// Wait for authentication
await page.waitForURL('https://firefly.adobe.com/**');
// Save authentication state
await context.storageState({ path: 'auth-state.json' });
await browser.close();
}
Image Generation Automation Workflow
FireWerk automates image generation by interacting with Firefly’s interface:
async function generateImage(prompt) {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
storageState: 'auth-state.json',
});
const page = await context.newPage();
await page.goto('https://firefly.adobe.com/generate');
// Enter prompt
await page.fill('textarea[placeholder*="prompt"]', prompt);
// Click generate button
await page.click('button:has-text("Generate")');
// Wait for generation to complete
await page.waitForSelector('.generated-image', { timeout: 60000 });
// Download generated image
const imageUrl = await page.getAttribute('.generated-image', 'src');
const response = await page.goto(imageUrl);
await fs.writeFile(`output/${prompt}.png`, await response.body());
await browser.close();
}
Speech Synthesis Automation
FireWerk also automates speech synthesis:
async function generateSpeech(text) {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
storageState: 'auth-state.json',
});
const page = await context.newPage();
await page.goto('https://firefly.adobe.com/speech');
// Enter text
await page.fill('textarea', text);
// Select voice
await page.selectOption('select[name="voice"]', 'en-US-Female');
// Generate speech
await page.click('button:has-text("Generate Speech")');
// Wait for audio
await page.waitForSelector('audio[src]');
// Download audio
const audioUrl = await page.getAttribute('audio', 'src');
const response = await page.goto(audioUrl);
await fs.writeFile(`output/${text}.mp3`, await response.body());
await browser.close();
}
Batch Processing from CSV Files
FireWerk processes batches from CSV files:
import { parse } from 'csv-parse/sync';
import fs from 'fs';
async function processBatch(csvPath) {
const csvContent = fs.readFileSync(csvPath, 'utf-8');
const records = parse(csvContent, {
columns: true,
skip_empty_lines: true,
});
for (const record of records) {
try {
console.log(`Processing: ${record.prompt}`);
if (record.type === 'image') {
await generateImage(record.prompt);
} else if (record.type === 'speech') {
await generateSpeech(record.text);
}
// Progress tracking
console.log(`✓ Completed: ${record.prompt}`);
} catch (error) {
console.error(`✗ Failed: ${record.prompt}`, error.message);
// Continue with next item
}
}
}
Web UI Integration
FireWerk includes an Express server for web UI control:
import express from 'express';
const app = express();
app.use(express.json());
app.post('/api/generate', async (req, res) => {
const { prompt, type } = req.body;
try {
if (type === 'image') {
await generateImage(prompt);
res.json({ success: true, message: 'Image generated' });
} else if (type === 'speech') {
await generateSpeech(prompt);
res.json({ success: true, message: 'Speech generated' });
}
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.listen(3000, () => {
console.log('FireWerk server running on http://localhost:3000');
});
Error Handling and Retry Strategies
Creative automation needs robust error handling:
async function generateWithRetry(prompt, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await generateImage(prompt);
return; // Success
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error.message);
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
Progress Tracking and Logging
Track progress for long-running batch operations:
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'firewerk.log' }),
],
});
async function processBatchWithLogging(csvPath) {
const records = parse(fs.readFileSync(csvPath, 'utf-8'), {
columns: true,
skip_empty_lines: true,
});
const total = records.length;
let completed = 0;
for (const record of records) {
try {
logger.info(`Processing ${completed + 1}/${total}: ${record.prompt}`);
await generateImage(record.prompt);
completed++;
logger.info(`Progress: ${completed}/${total} (${(completed/total*100).toFixed(1)}%)`);
} catch (error) {
logger.error(`Failed: ${record.prompt}`, error);
}
}
logger.info(`Batch complete: ${completed}/${total} succeeded`);
}
Network Interception for Monitoring
Monitor network requests to understand API calls:
async function generateWithMonitoring(prompt) {
const browser = await chromium.launch();
const page = await browser.newPage();
// Monitor network requests
page.on('request', request => {
if (request.url().includes('api')) {
console.log('API Request:', request.method(), request.url());
}
});
page.on('response', response => {
if (response.url().includes('api')) {
console.log('API Response:', response.status(), response.url());
}
});
// Perform generation
await page.goto('https://firefly.adobe.com/generate');
// ... rest of generation code
}
website-analyzer: Automated Analysis
website-analyzer demonstrates analysis automation with Playwright. It performs accessibility audits, responsive testing, and performance metrics collection.
Multi-Device Viewport Configuration
Test across multiple devices:
const devices = [
{ name: 'Desktop', viewport: { width: 1920, height: 1080 } },
{ name: 'Tablet', viewport: { width: 768, height: 1024 } },
{ name: 'Mobile', viewport: { width: 375, height: 667 } },
];
async function analyzeWebsite(url) {
const browser = await chromium.launch();
const results = [];
for (const device of devices) {
const context = await browser.newContext({
viewport: device.viewport,
});
const page = await context.newPage();
await page.goto(url);
const analysis = await analyzePage(page, device.name);
results.push(analysis);
await context.close();
}
await browser.close();
return results;
}
Accessibility Audit Implementation
Check accessibility issues:
async function analyzePage(page, deviceName) {
const issues = [];
// Check for missing alt text
const images = await page.$$('img');
for (const img of images) {
const alt = await img.getAttribute('alt');
if (!alt) {
issues.push({
type: 'accessibility',
severity: 'error',
message: 'Image missing alt text',
element: await img.evaluate(el => el.outerHTML),
});
}
}
// Check for semantic HTML
const headings = await page.$$('h1, h2, h3, h4, h5, h6');
if (headings.length === 0) {
issues.push({
type: 'accessibility',
severity: 'warning',
message: 'No headings found',
});
}
// Check keyboard navigation
const focusableElements = await page.$$('a, button, input, select, textarea');
for (const element of focusableElements) {
const tabIndex = await element.getAttribute('tabindex');
if (tabIndex === '-1') {
issues.push({
type: 'accessibility',
severity: 'warning',
message: 'Focusable element with tabindex="-1"',
});
}
}
return {
device: deviceName,
issues,
issueCount: issues.length,
};
}
Performance Metrics Collection
Collect performance metrics:
async function collectPerformanceMetrics(page) {
// Navigate and wait for load
await page.goto('https://example.com', { waitUntil: 'networkidle' });
// Get performance metrics
const metrics = await page.evaluate(() => {
const perfData = performance.getEntriesByType('navigation')[0];
return {
domContentLoaded: perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart,
loadComplete: perfData.loadEventEnd - perfData.loadEventStart,
firstPaint: performance.getEntriesByType('paint')[0]?.startTime || 0,
firstContentfulPaint: performance.getEntriesByType('paint')[1]?.startTime || 0,
};
});
// Get resource sizes
const resources = await page.evaluate(() => {
return performance.getEntriesByType('resource').map(resource => ({
name: resource.name,
size: resource.transferSize,
duration: resource.duration,
}));
});
return {
metrics,
resources,
totalSize: resources.reduce((sum, r) => sum + r.size, 0),
};
}
Screenshot Capture with Full-Page Scrolling
Capture full-page screenshots:
async function captureFullPageScreenshot(page, outputPath) {
// Get page dimensions
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight,
};
});
// Set viewport to full page
await page.setViewportSize({
width: dimensions.width,
height: dimensions.height,
});
// Capture screenshot
await page.screenshot({
path: outputPath,
fullPage: true,
});
}
Markdown Report Generation
Generate Markdown reports:
function generateMarkdownReport(results) {
let markdown = '# Website Analysis Report\n\n';
markdown += `Generated: ${new Date().toISOString()}\n\n`;
for (const result of results) {
markdown += `## ${result.device}\n\n`;
markdown += `**Issues Found:** ${result.issueCount}\n\n`;
if (result.issues.length > 0) {
markdown += '### Issues\n\n';
for (const issue of result.issues) {
markdown += `- **${issue.severity.toUpperCase()}**: ${issue.message}\n`;
}
markdown += '\n';
}
if (result.metrics) {
markdown += '### Performance Metrics\n\n';
markdown += `- DOM Content Loaded: ${result.metrics.domContentLoaded}ms\n`;
markdown += `- Load Complete: ${result.metrics.loadComplete}ms\n`;
markdown += `- First Paint: ${result.metrics.firstPaint}ms\n`;
markdown += `- First Contentful Paint: ${result.metrics.firstContentfulPaint}ms\n\n`;
}
}
return markdown;
}
Authentication Handling Patterns
Authentication is crucial for automating authenticated workflows. Here are patterns for handling authentication with Playwright.
Session Persistence Strategies
Save and load browser contexts with authentication:
// Save authentication state
async function saveAuthState(context, path) {
await context.storageState({ path });
}
// Load authentication state
async function loadAuthState(browser, path) {
return await browser.newContext({
storageState: path,
});
}
Cookie Management
Manage cookies explicitly:
// Save cookies
async function saveCookies(page, path) {
const cookies = await page.context().cookies();
await fs.writeFile(path, JSON.stringify(cookies, null, 2));
}
// Load cookies
async function loadCookies(context, path) {
const cookies = JSON.parse(await fs.readFile(path, 'utf-8'));
await context.addCookies(cookies);
}
Storage State Management
Storage state includes cookies, localStorage, and sessionStorage:
// Save complete storage state
async function saveStorageState(context, path) {
await context.storageState({ path });
}
// Use storage state
const context = await browser.newContext({
storageState: 'auth-state.json',
});
Environment Variable Usage for Credentials
Store credentials securely:
import dotenv from 'dotenv';
dotenv.config();
async function login(page) {
await page.goto('https://example.com/login');
await page.fill('#email', process.env.EMAIL);
await page.fill('#password', process.env.PASSWORD);
await page.click('button[type="submit"]');
await page.waitForURL('https://example.com/**');
}
Secure Authentication Flow
Complete secure authentication flow:
async function authenticate(credentials) {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto('https://example.com/login');
// Fill login form
await page.fill('#email', credentials.email);
await page.fill('#password', credentials.password);
await page.click('button[type="submit"]');
// Wait for authentication
await page.waitForURL('https://example.com/**', { timeout: 30000 });
// Verify authentication
const isAuthenticated = await page.evaluate(() => {
return document.cookie.includes('session');
});
if (isAuthenticated) {
// Save authentication state
await context.storageState({ path: 'auth-state.json' });
return true;
} else {
throw new Error('Authentication failed');
}
} finally {
await browser.close();
}
}
Batch Processing and Error Handling
Batch processing requires robust error handling and progress tracking.
CSV Parsing with Batch Processing Loop
Process CSV files in batches:
import { parse } from 'csv-parse/sync';
async function processBatch(csvPath, batchSize = 10) {
const records = parse(await fs.readFile(csvPath, 'utf-8'), {
columns: true,
skip_empty_lines: true,
});
// Process in batches
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
await Promise.all(
batch.map(record => processRecord(record))
);
console.log(`Processed batch ${Math.floor(i / batchSize) + 1}`);
}
}
Error Handling with Retry Logic
Implement retry logic with exponential backoff:
async function processWithRetry(record, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await processRecord(record);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Progress Tracking Implementation
Track progress for long operations:
class ProgressTracker {
constructor(total) {
this.total = total;
this.completed = 0;
this.failed = 0;
}
increment(success = true) {
if (success) {
this.completed++;
} else {
this.failed++;
}
const percentage = ((this.completed + this.failed) / this.total * 100).toFixed(1);
console.log(`Progress: ${this.completed + this.failed}/${this.total} (${percentage}%)`);
}
getStats() {
return {
completed: this.completed,
failed: this.failed,
remaining: this.total - this.completed - this.failed,
};
}
}
async function processWithProgress(records) {
const tracker = new ProgressTracker(records.length);
for (const record of records) {
try {
await processRecord(record);
tracker.increment(true);
} catch (error) {
console.error(`Failed: ${record.id}`, error.message);
tracker.increment(false);
}
}
console.log('Final stats:', tracker.getStats());
}
Parallel Execution Pattern
Process multiple items in parallel:
import pLimit from 'p-limit';
async function processParallel(records, concurrency = 5) {
const limit = pLimit(concurrency);
const promises = records.map(record =>
limit(async () => {
try {
return await processRecord(record);
} catch (error) {
console.error(`Failed: ${record.id}`, error.message);
return null;
}
})
);
const results = await Promise.all(promises);
return results.filter(r => r !== null);
}
Resource Cleanup
Clean up resources properly:
async function processWithCleanup(records) {
const browser = await chromium.launch();
try {
for (const record of records) {
const context = await browser.newContext();
const page = await context.newPage();
try {
await processRecord(page, record);
} finally {
await context.close(); // Clean up context
}
}
} finally {
await browser.close(); // Clean up browser
}
}
Automation Workflow
Here’s a visual representation of the automation workflow:
sequenceDiagram
participant User
participant Script
participant Playwright
participant Browser
participant Target
User->>Script: Start automation
Script->>Playwright: Launch browser
Playwright->>Browser: Create context
Browser->>Target: Navigate to site
alt Authentication Required
Browser->>Target: Login
Target-->>Browser: Set cookies/storage
Browser->>Script: Save auth state
end
loop Batch Processing
Script->>Browser: Process item
Browser->>Target: Interact with page
Target-->>Browser: Response
Browser->>Script: Extract data
alt Error Occurs
Script->>Script: Retry with backoff
end
end
Script->>Script: Generate report
Script->>User: Complete
Common Pitfalls
Not Handling Authentication Properly
Losing session or not persisting authentication state breaks automation. Always save and load authentication state.
Ignoring Error Recovery
One failure shouldn’t break the entire batch. Implement error recovery and continue processing.
Over-Automating Fragile Workflows
Brittle selectors and timing issues make automation unreliable. Use stable selectors and proper waiting.
Security Concerns with Credentials
Never commit secrets. Use environment variables and secure storage for credentials.
Not Using Auto-Waiting
Adding arbitrary timeouts instead of using Playwright’s auto-waiting leads to flaky automation. Use waitForSelector and other waiting methods.
Ignoring Resource Cleanup
Memory leaks and zombie processes occur without proper cleanup. Always close contexts and browsers.
Not Handling Network Failures
Timeouts and connection errors need handling. Implement retry logic for network operations.
Using Fragile Selectors
Relying on implementation details instead of stable attributes breaks when the page changes. Use data attributes and semantic selectors.
Conclusion
Playwright enables automation beyond testing, from creative workflows to batch processing and analysis. The three examples show different automation patterns: creative generation (FireWerk), analysis (website-analyzer), and capture (screenshot-tool-ui).
Key takeaways: Creative workflow automation with Playwright, authentication handling and persistence, batch processing patterns with error recovery, and real-world production examples. Playwright’s modern API, auto-waiting, and cross-browser support make it ideal for automation tasks.
Identify repetitive browser tasks in your workflow and automate them with Playwright. Start with a simple script, add authentication handling, then scale to batch processing. Automation saves time and enables new workflows that wouldn’t be possible manually.