Skip to main content
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:
npm install playwright
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:
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:
1

Get VNC Access

Retrieve the VNC URL from your session details (available in the session creation response or by fetching session details).
2

Connect via Dashboard

Use the Orchestrator dashboard to view the live browser session.
3

Add Debugging Pauses

Add await page.pause() in your Playwright script to pause execution and inspect the browser state.
4

Take Screenshots

Use await page.screenshot({ path: 'debug.png' }) at key points to capture the browser state.

Performance Tips

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 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