Playwright is a modern web automation framework that provides reliable end-to-end testing and automation capabilities. With Orchestrator, you can run Playwright scripts against real browser instances in the cloud without managing infrastructure.
Installation
Install Playwright in your project:
You don’t need to install browser binaries since you’ll be connecting to Orchestrator’s cloud browsers.
Basic Setup
Here’s a complete example of connecting Playwright to an Orchestrator session:
Basic Connection
TypeScript
const { chromium } = require ( 'playwright' );
async function connectToOrchestrator () {
// Create a new browser session
const sessionResponse = await fetch ( 'https://api.orchestratorhq.com/api/sessions' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer orch_your_api_key_here' ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
session_name: 'Playwright Automation' ,
duration: '1h'
})
});
const session = await sessionResponse . json ();
console . log ( 'Session created:' , session . id );
// Connect to the browser using CDP (session is active when API responds)
const browser = await chromium . connectOverCDP ( session . cdp_url );
const context = browser . contexts ()[ 0 ];
const page = context . pages ()[ 0 ];
return { browser , context , page , sessionId: session . id };
}
// Usage
async function main () {
const { browser , page , sessionId } = await connectToOrchestrator ();
try {
// Your automation code here
await page . goto ( 'https://example.com' );
console . log ( 'Page title:' , await page . title ());
// Take a screenshot
await page . screenshot ({ path: 'example.png' });
} finally {
// Clean up
await browser . close ();
// Stop the session
await fetch ( `https://api.orchestratorhq.com/api/sessions/ ${ sessionId } ` , {
method: 'DELETE' ,
headers: {
'Authorization' : 'Bearer orch_your_api_key_here'
}
});
}
}
main (). catch ( console . error );
Web Scraping Example
Here’s a practical example of using Playwright with Orchestrator for web scraping:
const { chromium } = require ( 'playwright' );
async function scrapeWithPlaywright () {
// Create and connect to session
const sessionResponse = await fetch ( 'https://api.orchestratorhq.com/api/sessions' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer orch_your_api_key_here' ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
session_name: 'Web Scraping Session' ,
duration: '30m'
})
});
const session = await sessionResponse . json ();
// Connect to browser (session is active when API responds)
const browser = await chromium . connectOverCDP ( session . cdp_url );
const context = browser . contexts ()[ 0 ];
const page = context . pages ()[ 0 ];
try {
// Navigate to the target website
await page . goto ( 'https://quotes.toscrape.com/' , { waitUntil: 'networkidle' });
// Extract quotes
const quotes = await page . evaluate (() => {
const quoteElements = document . querySelectorAll ( '.quote' );
return Array . from ( quoteElements ). map ( quote => ({
text: quote . querySelector ( '.text' )?. textContent ,
author: quote . querySelector ( '.author' )?. textContent ,
tags: Array . from ( quote . querySelectorAll ( '.tag' )). map ( tag => tag . textContent )
}));
});
console . log ( 'Scraped quotes:' , quotes );
// Navigate to next page if available
const nextButton = await page . $ ( '.next > a' );
if ( nextButton ) {
await nextButton . click ();
await page . waitForLoadState ( 'networkidle' );
// Scrape more quotes from the next page
const moreQuotes = await page . evaluate (() => {
const quoteElements = document . querySelectorAll ( '.quote' );
return Array . from ( quoteElements ). map ( quote => ({
text: quote . querySelector ( '.text' )?. textContent ,
author: quote . querySelector ( '.author' )?. textContent ,
tags: Array . from ( quote . querySelectorAll ( '.tag' )). map ( tag => tag . textContent )
}));
});
console . log ( 'More quotes:' , moreQuotes );
}
return [ ... quotes , ... moreQuotes ];
} finally {
await browser . close ();
// Clean up session
await fetch ( `https://api.orchestratorhq.com/api/sessions/ ${ session . id } ` , {
method: 'DELETE' ,
headers: { 'Authorization' : 'Bearer orch_your_api_key_here' }
});
}
}
scrapeWithPlaywright (). catch ( console . error );
Testing Example
Use Playwright with Orchestrator for end-to-end testing:
const { chromium } = require ( 'playwright' );
const { test , expect } = require ( '@playwright/test' );
// Custom fixture for Orchestrator connection
test . extend ({
orchestratorPage : async ({}, use ) => {
// Create session
const sessionResponse = await fetch ( 'https://api.orchestratorhq.com/api/sessions' , {
method: 'POST' ,
headers: {
'Authorization' : 'Bearer orch_your_api_key_here' ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
session_name: 'E2E Test Session' ,
duration: '15m'
})
});
const session = await sessionResponse . json ();
// Connect to browser (session is active when API responds)
const browser = await chromium . connectOverCDP ( session . cdp_url );
const context = browser . contexts ()[ 0 ];
const page = context . pages ()[ 0 ];
// Use the page in tests
await use ( page );
// Cleanup
await browser . close ();
await fetch ( `https://api.orchestratorhq.com/api/sessions/ ${ session . id } ` , {
method: 'DELETE' ,
headers: { 'Authorization' : 'Bearer orch_your_api_key_here' }
});
}
});
test ( 'homepage loads correctly' , async ({ orchestratorPage }) => {
await orchestratorPage . goto ( 'https://example.com' );
await expect ( orchestratorPage ). toHaveTitle ( /Example Domain/ );
const heading = orchestratorPage . locator ( 'h1' );
await expect ( heading ). toContainText ( 'Example Domain' );
});
test ( 'navigation works' , async ({ orchestratorPage }) => {
await orchestratorPage . goto ( 'https://example.com' );
// Test navigation
const moreInfoLink = orchestratorPage . locator ( 'a[href*="iana.org"]' );
await expect ( moreInfoLink ). toBeVisible ();
// Take screenshot for visual verification
await orchestratorPage . screenshot ({ path: 'navigation-test.png' });
});
Advanced Configuration
Request Interception
Intercept and modify network requests:
async function interceptRequests () {
const { browser , context , page , sessionId } = await connectToOrchestrator ();
try {
// Enable request interception
await page . route ( '**/*' , ( route ) => {
const request = route . request ();
// Block images to speed up loading
if ( request . resourceType () === 'image' ) {
route . abort ();
return ;
}
// Modify headers
const headers = {
... request . headers (),
'X-Custom-Header' : 'Orchestrator-Playwright'
};
route . continue ({ headers });
});
await page . goto ( 'https://httpbin.org/headers' );
// The response should show our custom header
const content = await page . textContent ( 'pre' );
console . log ( 'Headers:' , content );
} finally {
await browser . close ();
// Stop session...
}
}
Error Handling and Best Practices
Always clean up sessions to avoid unnecessary charges: async function withSession ( callback ) {
let sessionId ;
let browser ;
try {
const { browser : b , page , sessionId : sid } = await connectToOrchestrator ();
browser = b ;
sessionId = sid ;
await callback ( page );
} catch ( error ) {
console . error ( 'Error during automation:' , error );
throw error ;
} finally {
if ( browser ) {
await browser . close ();
}
if ( sessionId ) {
await fetch ( `https://api.orchestratorhq.com/api/sessions/ ${ sessionId } ` , {
method: 'DELETE' ,
headers: { 'Authorization' : 'Bearer orch_your_api_key_here' }
});
}
}
}
// Usage
await withSession ( async ( page ) => {
await page . goto ( 'https://example.com' );
// Your automation code
});
Configure appropriate timeouts for different operations: // Set global timeout
page . setDefaultTimeout ( 30000 ); // 30 seconds
// Set navigation timeout
page . setDefaultNavigationTimeout ( 60000 ); // 60 seconds
// Use specific timeouts for operations
await page . waitForSelector ( '.dynamic-content' , { timeout: 10000 });
await page . click ( 'button' , { timeout: 5000 });
Implement retry logic for flaky operations: async function retryOperation ( operation , maxRetries = 3 ) {
for ( let i = 0 ; i < maxRetries ; i ++ ) {
try {
return await operation ();
} catch ( error ) {
if ( i === maxRetries - 1 ) throw error ;
console . log ( `Attempt ${ i + 1 } failed, retrying...` );
await new Promise ( resolve => setTimeout ( resolve , 1000 * ( i + 1 )));
}
}
}
// Usage
await retryOperation ( async () => {
await page . click ( '.sometimes-missing-button' );
});
Debugging with VNC
When your automation isn’t working as expected, use VNC to visually debug:
Get VNC Access
Retrieve the VNC URL from your session details (available in the session creation response or by fetching session details).
Connect via Dashboard
Use the Orchestrator dashboard to view the live browser session.
Add Debugging Pauses
Add await page.pause() in your Playwright script to pause execution and inspect the browser state.
Take Screenshots
Use await page.screenshot({ path: 'debug.png' }) at key points to capture the browser state.
For multiple operations, reuse the same session instead of creating new ones to reduce overhead and improve performance.
Use specific wait conditions instead of arbitrary delays for better performance: // Good - specific wait
await page . waitForSelector ( '#submit-button' );
// Avoid - arbitrary delay
await page . waitForTimeout ( 5000 );
Block Unnecessary Resources
Block images, fonts, or other resources you don’t need to speed up page loads: await page . route ( '**/*' , ( route ) => {
if ( route . request (). resourceType () === 'image' ) {
route . abort ();
} else {
route . continue ();
}
});
Use multiple sessions for parallel execution of independent tasks to maximize throughput.
Next Steps