PSP Examples

Implementation examples for different frameworks

Table of Contents

Playwright Examples

Basic Authentication Example

Capture a logged-in session and reuse it later:

import { chromium } from 'playwright';
import { PlaywrightAdapter } from '@psp/playwright';
import * as fs from 'fs';

async function saveAuthSession() {
  // Launch a browser
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();
  
  // Create a PSP session
  const adapter = new PlaywrightAdapter();
  const session = await adapter.createSession(page, {
    name: 'github-login',
    description: 'GitHub authenticated session'
  });
  
  // Navigate to GitHub login
  await page.goto('https://github.com/login');
  
  // Fill in the login form
  await page.fill('#login_field', 'your-username');
  await page.fill('#password', 'your-password');
  await page.click('input[type="submit"]');
  
  // Wait for navigation to complete
  await page.waitForURL('https://github.com/');
  
  // Capture the authenticated session
  await session.capture();
  
  // Save the session ID for later use
  const sessionId = session.getId();
  fs.writeFileSync('github-session.txt', sessionId);
  
  console.log(`Session saved with ID: ${sessionId}`);
  
  await browser.close();
}

async function loadAuthSession() {
  // Load the session ID
  const sessionId = fs.readFileSync('github-session.txt', 'utf8').trim();
  
  // Launch a new browser
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();
  
  // Create a PSP adapter
  const adapter = new PlaywrightAdapter();
  
  // Load the saved session
  const session = await adapter.loadSession(sessionId);
  
  // Restore the session to the new browser
  await session.restore(page);
  
  // Navigate to a page that requires authentication
  await page.goto('https://github.com/settings/profile');
  
  // The page should load without requiring login
  console.log('Session restored successfully!');
}

// Run the example
// saveAuthSession();
// or
// loadAuthSession();
Session Recording and Playback

Record user interactions and play them back:

import { chromium } from 'playwright';
import { PlaywrightAdapter } from '@psp/playwright';

async function recordUserActions() {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();
  
  const adapter = new PlaywrightAdapter();
  const session = await adapter.createSession(page, {
    name: 'recorded-session',
    description: 'Session with recorded user actions'
  });
  
  // Navigate to a website
  await page.goto('https://todomvc.com/examples/vanilla-es6/');
  
  // Start recording user actions
  await session.startRecording();
  
  // Perform some actions
  await page.fill('.new-todo', 'Buy groceries');
  await page.press('.new-todo', 'Enter');
  
  await page.fill('.new-todo', 'Clean the house');
  await page.press('.new-todo', 'Enter');
  
  await page.fill('.new-todo', 'Pay bills');
  await page.press('.new-todo', 'Enter');
  
  // Mark the first item as completed
  await page.click('.todo-list li:first-child .toggle');
  
  // Stop recording
  await session.stopRecording();
  
  // Capture the final state
  await session.capture();
  const sessionId = session.getId();
  
  console.log(`Recorded session saved with ID: ${sessionId}`);
  
  await browser.close();
  return sessionId;
}

async function playbackUserActions(sessionId) {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext();
  const page = await context.newPage();
  
  const adapter = new PlaywrightAdapter();
  const session = await adapter.loadSession(sessionId);
  
  // Navigate to the same website
  await page.goto('https://todomvc.com/examples/vanilla-es6/');
  
  // Restore the session state
  await session.restore(page);
  
  // Play back the recorded actions
  await session.playRecording(page, {
    speed: 0.5  // Slow playback for demonstration
  });
  
  console.log('Playback completed');
}

// Run the example
// recordUserActions().then(sessionId => {
//   setTimeout(() => playbackUserActions(sessionId), 5000);
// });
Remote Storage with Playwright

Use PSP with Redis storage for distributed access:

import { chromium } from 'playwright';
import { PlaywrightAdapter } from '@psp/playwright';
import { RedisStorageProvider } from '@psp/core/storage/redis';

async function remoteStorageExample() {
  // Create Redis storage provider
  const storageProvider = new RedisStorageProvider({
    redisUrl: 'redis://localhost:6379',
    namespace: 'my-app-sessions'
  });

  // Launch browser
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
  
  // Create adapter with Redis storage
  const adapter = new PlaywrightAdapter({
    storageProvider
  });
  
  // Create a session
  const session = await adapter.createSession(page, {
    name: 'shared-session',
    description: 'Session stored in Redis'
  });
  
  // Use the session...
  await page.goto('https://example.com');
  await session.capture();
  
  // This session can now be accessed from any machine with access to the Redis server
  console.log(`Created session with ID ${session.getId()} in Redis`);
}

Selenium Examples

Basic Authentication Example

Capture a logged-in session and reuse it later:

const { Builder, By } = require('selenium-webdriver');
const { SeleniumAdapter } = require('@psp/selenium');
const fs = require('fs');

async function saveAuthSession() {
  // Initialize driver
  const driver = await new Builder().forBrowser('chrome').build();
  
  // Create adapter and session
  const adapter = new SeleniumAdapter();
  const session = await adapter.createSession(driver, {
    name: 'github-login',
    description: 'GitHub authenticated session'
  });
  
  // Navigate to GitHub login
  await driver.get('https://github.com/login');
  
  // Fill in the login form
  await driver.findElement(By.id('login_field')).sendKeys('your-username');
  await driver.findElement(By.id('password')).sendKeys('your-password');
  await driver.findElement(By.name('commit')).click();
  
  // Wait for navigation to complete
  await driver.wait(() => {
    return driver.getCurrentUrl().then(url => url.includes('github.com') && !url.includes('login'));
  }, 5000);
  
  // Capture the authenticated session
  await session.capture();
  
  // Save the session ID for later use
  const sessionId = session.getId();
  fs.writeFileSync('github-session.txt', sessionId);
  
  console.log(`Session saved with ID: ${sessionId}`);
  
  await driver.quit();
}

async function loadAuthSession() {
  // Load the session ID
  const sessionId = fs.readFileSync('github-session.txt', 'utf8').trim();
  
  // Initialize a new driver
  const driver = await new Builder().forBrowser('chrome').build();
  
  // Create adapter
  const adapter = new SeleniumAdapter();
  
  // Load the saved session
  const session = await adapter.loadSession(sessionId);
  
  // Restore the session to the new driver
  await session.restore(driver);
  
  // Navigate to a page that requires authentication
  await driver.get('https://github.com/settings/profile');
  
  // The page should load without requiring login
  console.log('Session restored successfully!');
}

// Run the example
// saveAuthSession();
// or
// loadAuthSession();
Selenium with Python

Use PSP with Selenium in Python:

from selenium import webdriver
from selenium.webdriver.common.by import By
from psp.selenium import SeleniumAdapter

def save_auth_session():
    # Initialize driver
    driver = webdriver.Chrome()
    
    # Create adapter and session
    adapter = SeleniumAdapter()
    session = adapter.create_session(driver, 
        name="github-login",
        description="GitHub authenticated session"
    )
    
    # Navigate to GitHub login
    driver.get("https://github.com/login")
    
    # Fill in the login form
    driver.find_element(By.ID, "login_field").send_keys("your-username")
    driver.find_element(By.ID, "password").send_keys("your-password")
    driver.find_element(By.NAME, "commit").click()
    
    # Wait for navigation to complete
    WebDriverWait(driver, 10).until(
        lambda d: "login" not in d.current_url and "github.com" in d.current_url
    )
    
    # Capture the authenticated session
    session.capture()
    
    # Save the session ID for later use
    session_id = session.get_id()
    with open("github-session.txt", "w") as f:
        f.write(session_id)
    
    print(f"Session saved with ID: {session_id}")
    
    driver.quit()

def load_auth_session():
    # Load the session ID
    with open("github-session.txt", "r") as f:
        session_id = f.read().strip()
    
    # Initialize a new driver
    driver = webdriver.Chrome()
    
    # Create adapter
    adapter = SeleniumAdapter()
    
    # Load the saved session
    session = adapter.load_session(session_id)
    
    # Restore the session to the new driver
    session.restore(driver)
    
    # Navigate to a page that requires authentication
    driver.get("https://github.com/settings/profile")
    
    # The page should load without requiring login
    print("Session restored successfully!")

# Run the example
# save_auth_session()
# or
# load_auth_session()

Browser-Use Examples

Basic Usage with Browser-Use

Integrate PSP with Browser-Use:

import { Browser } from '@browser-use/browser';
import { BrowserUseAdapter } from '@psp/browser-use';

async function browserUseExample() {
  // Initialize Browser-Use
  const browser = await Browser.launch({
    headless: false
  });
  
  // Create PSP adapter
  const adapter = new BrowserUseAdapter();
  
  // Create a session
  const session = await adapter.createSession(browser, {
    name: 'browser-use-session',
    description: 'Browser-Use authenticated session'
  });
  
  // Navigate to a login page
  await browser.goto('https://example.com/login');
  
  // Fill form and submit
  await browser.type('#username', 'user@example.com');
  await browser.type('#password', 'password123');
  await browser.click('button[type="submit"]');
  
  // Wait for successful login
  await browser.waitForNavigation();
  
  // Capture the session
  await session.capture();
  
  // Export the session ID
  const sessionId = session.getId();
  console.log(`Session saved with ID: ${sessionId}`);
  
  // Later, in another process or machine
  
  // Create a new browser instance
  const newBrowser = await Browser.launch();
  
  // Load the saved session
  const savedSession = await adapter.loadSession(sessionId);
  
  // Restore the session
  await savedSession.restore(newBrowser);
  
  // Navigate to a protected page - no login required
  await newBrowser.goto('https://example.com/dashboard');
}
AI Agent Integration with Browser-Use

Use PSP to maintain state for AI agents:

import { Browser } from '@browser-use/browser';
import { BrowserUseAdapter } from '@psp/browser-use';
import { AIAgent } from '@browser-use/ai-agent';

async function aiAgentExample() {
  // Initialize Browser-Use
  const browser = await Browser.launch();
  
  // Create PSP adapter
  const adapter = new BrowserUseAdapter();
  
  // Check if we have a saved session
  const sessionId = process.env.SAVED_SESSION_ID;
  
  if (sessionId) {
    // Load an existing session
    const session = await adapter.loadSession(sessionId);
    
    // Restore the session state
    await session.restore(browser);
    
    console.log('Restored previous session');
  }
  
  // Create an AI agent to perform tasks
  const agent = new AIAgent({
    browser,
    apiKey: process.env.AI_API_KEY
  });
  
  // Perform the AI task
  await agent.perform('Search for recent news about climate change and save the first 3 articles');
  
  // Capture the session state after the AI has performed its tasks
  const session = await adapter.createSession(browser, {
    name: 'ai-agent-session',
    description: 'Session after AI performed tasks'
  });
  
  await session.capture();
  
  // Save session ID for future use
  console.log(`Session saved with ID: ${session.getId()}`);
  
  await browser.close();
}

Computer-Use Examples

Basic Usage with Computer-Use

Integrate PSP with Computer-Use:

import { Computer } from '@computer-use/computer';
import { ComputerUseAdapter } from '@psp/computer-use';

async function computerUseExample() {
  // Create Computer instance
  const computer = await Computer.launch();
  
  // Create PSP adapter
  const adapter = new ComputerUseAdapter();
  
  // Create a session
  const session = await adapter.createSession(computer, {
    name: 'computer-use-session',
    description: 'Computer-Use authenticated session',
    storage: 'redis',
    storageOptions: {
      redisUrl: process.env.REDIS_URL
    }
  });
  
  // Open browser
  const browser = await computer.openBrowser();
  
  // Navigate to a login page
  await browser.goto('https://example.com/login');
  
  // Fill form and submit
  await browser.type('#username', 'user@example.com');
  await browser.type('#password', 'password123');
  await browser.click('button[type="submit"]');
  
  // Wait for successful login
  await browser.waitForNavigation();
  
  // Capture the session
  await session.capture();
  
  // Export the session ID
  const sessionId = session.getId();
  console.log(`Session saved with ID: ${sessionId}`);
  
  // Later, in another process or machine
  
  // Create a new computer instance
  const newComputer = await Computer.launch();
  
  // Load the saved session
  const savedSession = await adapter.loadSession(sessionId);
  
  // Restore the session
  await savedSession.restore(newComputer);
  
  // Open browser - session will be automatically applied
  const newBrowser = await newComputer.openBrowser();
  
  // Navigate to a protected page - no login required
  await newBrowser.goto('https://example.com/dashboard');
}
Full Computer Integration

Use PSP for full computer state (including files):

import { Computer } from '@computer-use/computer';
import { ComputerUseAdapter } from '@psp/computer-use';
import { CloudStorageProvider } from '@psp/core/storage/cloud';

async function fullComputerSessionExample() {
  // Set up cloud storage
  const storageProvider = new CloudStorageProvider({
    provider: 'aws',
    region: 'us-west-2',
    bucket: 'my-psp-sessions',
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY,
      secretAccessKey: process.env.AWS_SECRET_KEY
    }
  });
  
  // Create Computer instance
  const computer = await Computer.launch({
    persistFilesystem: true
  });
  
  // Create PSP adapter with advanced options
  const adapter = new ComputerUseAdapter({
    storageProvider,
    captureOptions: {
      includeFiles: true,
      includeSystemState: true
    }
  });
  
  // Create a session
  const session = await adapter.createSession(computer, {
    name: 'full-computer-session',
    description: 'Complete computer state including files'
  });
  
  // Perform various computer operations
  const browser = await computer.openBrowser();
  await browser.goto('https://example.com/download');
  
  // Download a file
  await browser.click('#download-button');
  
  // Use the filesystem API
  const fs = await computer.filesystem();
  await fs.writeFile('/tmp/notes.txt', 'Important information');
  
  // Capture full state including downloaded files
  await session.capture();
  
  // Later, restore the entire environment
  const newComputer = await Computer.launch();
  
  // Load and restore the session
  const savedSession = await adapter.loadSession(session.getId());
  await savedSession.restore(newComputer);
  
  // The new computer now has the same browser state AND file system content
  const newFs = await newComputer.filesystem();
  const fileContent = await newFs.readFile('/tmp/notes.txt');
  console.log('Restored file content:', fileContent);
}

Skyvern Examples

Skyvern Integration

Use PSP with Skyvern for vision-based automation:

from skyvern import SkyvernBrowser
from psp.skyvern import SkyvernAdapter
import asyncio

async def skyvern_example():
    # Initialize Skyvern
    browser = SkyvernBrowser()
    
    # Create PSP adapter
    adapter = SkyvernAdapter()
    
    # Create a session
    session = await adapter.create_session(browser, 
        name="skyvern-session",
        description="Skyvern authenticated session"
    )
    
    # Navigate to a login page
    await browser.goto("https://example.com/login")
    
    # Use Skyvern's vision-based interaction
    await browser.click_on_text("Username")
    await browser.type("user@example.com")
    
    await browser.click_on_text("Password")
    await browser.type("password123")
    
    await browser.click_on_text("Sign In")
    
    # Wait for login to complete
    await browser.wait_for_navigation()
    
    # Capture the session
    await session.capture()
    
    # Export the session ID
    session_id = session.get_id()
    print(f"Session saved with ID: {session_id}")
    
    # Later, in another process or machine
    
    # Create a new browser instance
    new_browser = SkyvernBrowser()
    
    # Load the saved session
    saved_session = await adapter.load_session(session_id)
    
    # Restore the session
    await saved_session.restore(new_browser)
    
    # Navigate to a protected page - no login required
    await new_browser.goto("https://example.com/dashboard")
    
    # Skyvern can now interact with the authenticated page
    dashboard_elements = await new_browser.get_page_elements()
    print(f"Found {len(dashboard_elements)} elements on dashboard")

# Run the example
asyncio.run(skyvern_example())
Visual State Capture with Skyvern

Capture visual state with screenshot comparison:

from skyvern import SkyvernBrowser
from psp.skyvern import SkyvernAdapter
import asyncio

async def visual_state_example():
    # Initialize Skyvern
    browser = SkyvernBrowser()
    
    # Create PSP adapter with visual state options
    adapter = SkyvernAdapter(
        visual_comparison=True,
        screenshot_on_capture=True
    )
    
    # Create a session
    session = await adapter.create_session(browser, 
        name="visual-session",
        description="Session with visual state capture"
    )
    
    # Navigate and login
    await browser.goto("https://example.com/login")
    await browser.fill_by_label("Username", "user@example.com")
    await browser.fill_by_label("Password", "password123")
    await browser.click_button("Sign In")
    
    # Capture the session with screenshots
    await session.capture()
    
    # Later, restore session
    new_browser = SkyvernBrowser()
    saved_session = await adapter.load_session(session.get_id())
    
    # Restore session with visual verification
    await saved_session.restore(new_browser, 
        verify_visual_state=True,
        visual_similarity_threshold=0.9
    )
    
    # If the visual state doesn't match, restoration will fail
    print("Visual state successfully verified and restored")

asyncio.run(visual_state_example())

UI Dashboard

PSP includes a modern, responsive UI dashboard for managing sessions across different frameworks. The dashboard provides an intuitive interface for creating, viewing, and managing browser sessions.

Dashboard Overview
PSP Dashboard

The main dashboard showing session metrics and activity

Sessions Management
Sessions List

Browse, search, and manage all your browser sessions

Session Details
Session Details

View detailed information about a specific session

Session Recording
Session Recorder

Record browser sessions for later playback

Dark Mode Support
Dark Mode

The dashboard supports both light and dark themes

The UI dashboard is built with React, TypeScript, and Material UI to provide a modern, responsive user experience. To start using the dashboard, see the UI README for installation and configuration instructions.

Cloudflare Workers Examples

Cloudflare Browser Rendering with PSP

Use PSP with Cloudflare's Browser Rendering:

import { Renderer } from '@cloudflare/browser-rendering';
import { CloudflareAdapter } from '@psp/cloudflare';

export default {
  async fetch(request, env, ctx) {
    // Check if we have a session ID in the request
    const url = new URL(request.url);
    const sessionId = url.searchParams.get('sessionId');
    
    // Initialize the browser renderer
    const renderer = new Renderer();
    
    // Create PSP adapter
    const adapter = new CloudflareAdapter({
      storageProvider: {
        type: 'cloudflare-kv',
        namespace: env.PSP_SESSIONS
      }
    });
    
    // If we have a session ID, try to restore it
    if (sessionId) {
      try {
        const session = await adapter.loadSession(sessionId);
        await session.restore(renderer);
        
        console.log('Session restored successfully');
      } catch (error) {
        console.error('Failed to restore session:', error);
        // Continue with a new session
      }
    }
    
    // Navigate to the target page
    const targetUrl = url.searchParams.get('url') || 'https://example.com';
    await renderer.goto(targetUrl);
    
    // Interact with the page
    if (!sessionId) {
      // If this is a new session, perform login
      await renderer.goto('https://example.com/login');
      await renderer.fill('#username', 'user@example.com');
      await renderer.fill('#password', 'password123');
      await renderer.click('button[type="submit"]');
      await renderer.waitForNavigation();
      
      // Create and capture the session
      const session = await adapter.createSession(renderer, {
        name: 'cloudflare-session',
        description: 'Cloudflare Browser authenticated session'
      });
      
      await session.capture();
      
      // Return the session ID in the response
      return new Response(JSON.stringify({
        sessionId: session.getId(),
        message: 'Session created successfully'
      }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    // Take a screenshot of the authenticated page
    const screenshot = await renderer.screenshot();
    
    // Return the screenshot
    return new Response(screenshot, {
      headers: { 'Content-Type': 'image/png' }
    });
  }
};
Serverless Session Management

Implement PSP in a serverless environment:

import { Renderer } from '@cloudflare/browser-rendering';
import { CloudflareAdapter } from '@psp/cloudflare';

export default {
  async scheduled(event, env, ctx) {
    // This function runs on a schedule to update sessions
    
    // Initialize the browser renderer
    const renderer = new Renderer();
    
    // Create PSP adapter
    const adapter = new CloudflareAdapter({
      storageProvider: {
        type: 'cloudflare-kv',
        namespace: env.PSP_SESSIONS
      }
    });
    
    // List all sessions
    const sessions = await adapter.listSessions();
    
    // Find sessions that need refreshing
    const expiringSessions = sessions.filter(metadata => {
      // Find sessions that expire in the next hour
      const expiresIn = (metadata.expireAt || 0) - Date.now();
      return expiresIn > 0 && expiresIn < 3600000; // less than 1 hour
    });
    
    console.log(`Found ${expiringSessions.length} sessions that need refreshing`);
    
    // Process each session
    for (const metadata of expiringSessions) {
      try {
        // Load the session
        const session = await adapter.loadSession(metadata.id);
        
        // Restore the session
        await session.restore(renderer);
        
        // Perform a simple action to refresh tokens
        await renderer.goto('https://example.com/dashboard');
        
        // Capture the refreshed state
        await session.capture();
        
        console.log(`Refreshed session: ${metadata.id}`);
      } catch (error) {
        console.error(`Failed to refresh session ${metadata.id}:`, error);
      }
    }
    
    return new Response('Session refresh complete');
  }
};

Stagehand Examples

Basic Integration with Stagehand

Use PSP with Stagehand for reliable automation:

import { Stagehand } from '@stagehand/core';
import { StagehandAdapter } from '@psp/stagehand';

async function stagehandExample() {
  // Initialize Stagehand
  const stagehand = await Stagehand.create();
  
  // Create PSP adapter
  const adapter = new StagehandAdapter();
  
  // Create a session
  const session = await adapter.createSession(stagehand, {
    name: 'stagehand-session',
    description: 'Stagehand authenticated session'
  });
  
  // Navigate to a website
  await stagehand.goto('https://example.com/login');
  
  // Fill form and submit using Stagehand's reliable element selection
  await stagehand.withinForm('Login', async form => {
    await form.fillField('Username', 'user@example.com');
    await form.fillField('Password', 'password123');
    await form.submit();
  });
  
  // Wait for navigation
  await stagehand.waitFor({ pageLoad: true });
  
  // Capture the authenticated session
  await session.capture();
  
  console.log(`Session saved with ID: ${session.getId()}`);
  
  // Later, restore the session
  const newStagehand = await Stagehand.create();
  
  // Load the saved session
  const savedSession = await adapter.loadSession(session.getId());
  
  // Restore the session to the new browser
  await savedSession.restore(newStagehand);
  
  // Navigate to a protected page - already authenticated
  await newStagehand.goto('https://example.com/dashboard');
  
  // Verify we're logged in
  const username = await newStagehand.text('username-display');
  console.log(`Logged in as: ${username}`);
}
Multi-stage Workflows with Stagehand

Implement complex workflows with session persistence:

import { Stagehand } from '@stagehand/core';
import { StagehandAdapter } from '@psp/stagehand';

async function multistageWorkflow() {
  // Check if we have a session ID from a previous stage
  const previousSessionId = process.env.WORKFLOW_SESSION_ID;
  
  // Initialize Stagehand
  const stagehand = await Stagehand.create();
  
  // Create PSP adapter
  const adapter = new StagehandAdapter();
  
  let session;
  
  // If we have a previous session, restore it
  if (previousSessionId) {
    session = await adapter.loadSession(previousSessionId);
    await session.restore(stagehand);
    console.log(`Restored session from previous stage`);
  } else {
    // Create a new session
    session = await adapter.createSession(stagehand, {
      name: 'workflow-session',
      description: 'Multi-stage workflow session'
    });
    
    // Stage 1: Login
    await stagehand.goto('https://example.com/login');
    await stagehand.fillField('Username', 'user@example.com');
    await stagehand.fillField('Password', 'password123');
    await stagehand.click('Sign In');
    await stagehand.waitFor({ pageLoad: true });
    
    // Capture after login
    await session.capture();
    console.log(`Completed Stage 1: Login`);
  }
  
  // Identify the current stage from the URL
  const currentUrl = await stagehand.getCurrentUrl();
  
  if (currentUrl.includes('/dashboard')) {
    // Stage 2: Fill form
    await stagehand.click('New Item');
    await stagehand.fillField('Item Name', 'New Product');
    await stagehand.fillField('Description', 'This is a new product');
    await stagehand.selectOption('Category', 'Electronics');
    await stagehand.click('Next');
    await stagehand.waitFor({ pageLoad: true });
    
    // Capture after form completion
    await session.capture();
    console.log(`Completed Stage 2: Fill form`);
  } else if (currentUrl.includes('/item/details')) {
    // Stage 3: Review and submit
    await stagehand.click('Confirm Details');
    await stagehand.withinDialog(async dialog => {
      await dialog.click('Submit');
    });
    await stagehand.waitFor({ pageLoad: true });
    
    // Verify success message
    const successMessage = await stagehand.text('status-message');
    console.log(`Success: ${successMessage}`);
    
    // Final capture
    await session.capture();
    console.log(`Completed Stage 3: Review and submit`);
  } else {
    console.log(`Unknown stage with URL: ${currentUrl}`);
  }
  
  // Save session ID for potential next stage
  console.log(`WORKFLOW_SESSION_ID=${session.getId()}`);
}

Puppeteer Examples

Basic Authentication with Puppeteer

Use PSP with Puppeteer:

const puppeteer = require('puppeteer');
const { PuppeteerAdapter } = require('@psp/puppeteer');

async function puppeteerExample() {
  // Launch browser
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Create PSP adapter
  const adapter = new PuppeteerAdapter();
  
  // Create a session
  const session = await adapter.createSession(page, {
    name: 'puppeteer-session',
    description: 'Puppeteer authenticated session'
  });
  
  // Navigate to a login page
  await page.goto('https://example.com/login');
  
  // Fill form and login
  await page.type('#username', 'user@example.com');
  await page.type('#password', 'password123');
  await page.click('button[type="submit"]');
  
  // Wait for navigation
  await page.waitForNavigation();
  
  // Capture the session
  await session.capture();
  
  console.log(`Session saved with ID: ${session.getId()}`);
  
  // Close the browser
  await browser.close();
  
  // Later, in a new browser instance
  const newBrowser = await puppeteer.launch();
  const newPage = await newBrowser.newPage();
  
  // Load the saved session
  const savedSession = await adapter.loadSession(session.getId());
  
  // Restore the session
  await savedSession.restore(newPage);
  
  // Navigate to a protected page - already authenticated
  await newPage.goto('https://example.com/dashboard');
  
  // Take a screenshot to verify
  await newPage.screenshot({ path: 'authenticated.png' });
  
  await newBrowser.close();
}
Using CDP with Puppeteer and PSP

Leverage Chrome DevTools Protocol for advanced session handling:

const puppeteer = require('puppeteer');
const { PuppeteerAdapter } = require('@psp/puppeteer');

async function cdpExample() {
  // Launch browser
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Get CDP client
  const cdpClient = await page.target().createCDPSession();
  
  // Create PSP adapter with CDP support
  const adapter = new PuppeteerAdapter({
    useCDP: true,
    cdpClient
  });
  
  // Create a session
  const session = await adapter.createSession(page, {
    name: 'cdp-session',
    description: 'Session with CDP support'
  });
  
  // Use CDP to enable more capabilities
  await cdpClient.send('Network.enable');
  await cdpClient.send('Network.setCacheDisabled', { cacheDisabled: true });
  
  // Set up request interception for tokens
  await cdpClient.on('Network.responseReceived', async (params) => {
    if (params.response.url.includes('/api/auth')) {
      const responseBody = await cdpClient.send('Network.getResponseBody', {
        requestId: params.requestId
      });
      
      try {
        const authData = JSON.parse(responseBody.body);
        if (authData.token) {
          console.log('Captured authentication token');
          
          // Store the token in the session
          await session.updateState({
            authToken: authData.token
          });
        }
      } catch (e) {
        // Not JSON or no token
      }
    }
  });
  
  // Navigate to login
  await page.goto('https://example.com/login');
  await page.type('#username', 'user@example.com');
  await page.type('#password', 'password123');
  await page.click('button[type="submit"]');
  
  // Wait for auth token response
  await page.waitForResponse(response => 
    response.url().includes('/api/auth'));
  
  // Capture full session with auth token
  await session.capture();
  
  console.log(`Session saved with ID: ${session.getId()}`);
  
  await browser.close();
}

Custom Implementation Examples

Custom Storage Provider

Implement a custom storage provider:

import { StorageProvider, StoredSession, SessionFilter, SessionMetadata } from '@psp/core';

// Example custom storage provider using AWS S3
export class S3StorageProvider implements StorageProvider {
  private s3Client: AWS.S3;
  private bucketName: string;
  
  constructor(options: { 
    bucketName: string,
    region?: string,
    credentials?: { 
      accessKeyId: string, 
      secretAccessKey: string 
    }
  }) {
    const AWS = require('aws-sdk');
    
    this.bucketName = options.bucketName;
    this.s3Client = new AWS.S3({
      region: options.region || 'us-east-1',
      credentials: options.credentials
    });
  }
  
  async save(session: StoredSession): Promise {
    // Serialize the session state
    const serializedState = this.serializeState(session.state);
    
    // Create the serialized session
    const serializedSession = {
      metadata: session.metadata,
      state: serializedState
    };
    
    // Upload to S3
    await this.s3Client.putObject({
      Bucket: this.bucketName,
      Key: `sessions/${session.metadata.id}.json`,
      Body: JSON.stringify(serializedSession),
      ContentType: 'application/json'
    }).promise();
  }
  
  async load(id: string): Promise {
    // Get the session from S3
    const response = await this.s3Client.getObject({
      Bucket: this.bucketName,
      Key: `sessions/${id}.json`
    }).promise();
    
    // Parse the session data
    const serializedSession = JSON.parse(response.Body.toString());
    
    // Deserialize the state
    const deserializedState = this.deserializeState(serializedSession.state);
    
    return {
      metadata: serializedSession.metadata,
      state: deserializedState
    };
  }
  
  async delete(id: string): Promise {
    await this.s3Client.deleteObject({
      Bucket: this.bucketName,
      Key: `sessions/${id}.json`
    }).promise();
  }
  
  async list(filter?: SessionFilter): Promise {
    // List objects in the sessions/ prefix
    const response = await this.s3Client.listObjectsV2({
      Bucket: this.bucketName,
      Prefix: 'sessions/'
    }).promise();
    
    // Get metadata for each session
    const metadataPromises = response.Contents.map(async (obj) => {
      const objResponse = await this.s3Client.getObject({
        Bucket: this.bucketName,
        Key: obj.Key
      }).promise();
      
      const sessionData = JSON.parse(objResponse.Body.toString());
      return sessionData.metadata;
    });
    
    let metadata = await Promise.all(metadataPromises);
    
    // Apply filters
    if (filter) {
      if (filter.name) {
        metadata = metadata.filter(m => 
          m.name.toLowerCase().includes(filter.name.toLowerCase())
        );
      }
      
      if (filter.tags && filter.tags.length > 0) {
        metadata = metadata.filter(m => 
          filter.tags.every(tag => m.tags?.includes(tag))
        );
      }
      
      // Sort by updated time (newest first)
      metadata.sort((a, b) => b.updatedAt - a.updatedAt);
      
      // Apply offset and limit
      if (filter.offset || filter.limit) {
        const offset = filter.offset || 0;
        const limit = filter.limit;
        
        metadata = metadata.slice(offset, limit ? offset + limit : undefined);
      }
    }
    
    return metadata;
  }
  
  async exists(id: string): Promise {
    try {
      await this.s3Client.headObject({
        Bucket: this.bucketName,
        Key: `sessions/${id}.json`
      }).promise();
      return true;
    } catch (error) {
      if (error.code === 'NotFound') {
        return false;
      }
      throw error;
    }
  }
  
  // Helper methods to handle serialization and deserialization
  private serializeState(state: any): any {
    const serialized: any = { ...state };
    
    // Convert Maps to objects
    if (state.storage?.localStorage instanceof Map) {
      serialized.storage = { ...serialized.storage };
      const localStorage: Record> = {};
      
      for (const [origin, storage] of state.storage.localStorage.entries()) {
        localStorage[origin] = Object.fromEntries(storage);
      }
      
      serialized.storage.localStorage = localStorage;
    }
    
    if (state.storage?.sessionStorage instanceof Map) {
      serialized.storage = { ...serialized.storage };
      const sessionStorage: Record> = {};
      
      for (const [origin, storage] of state.storage.sessionStorage.entries()) {
        sessionStorage[origin] = Object.fromEntries(storage);
      }
      
      serialized.storage.sessionStorage = sessionStorage;
    }
    
    return serialized;
  }
  
  private deserializeState(state: any): any {
    const deserialized: any = { ...state };
    
    // Convert objects back to Maps
    if (state.storage?.localStorage && typeof state.storage.localStorage === 'object') {
      deserialized.storage = { ...deserialized.storage };
      const localStorage = new Map>();
      
      for (const [origin, storage] of Object.entries(state.storage.localStorage)) {
        localStorage.set(origin, new Map(Object.entries(storage as Record)));
      }
      
      deserialized.storage.localStorage = localStorage;
    }
    
    if (state.storage?.sessionStorage && typeof state.storage.sessionStorage === 'object') {
      deserialized.storage = { ...deserialized.storage };
      const sessionStorage = new Map>();
      
      for (const [origin, storage] of Object.entries(state.storage.sessionStorage)) {
        sessionStorage.set(origin, new Map(Object.entries(storage as Record)));
      }
      
      deserialized.storage.sessionStorage = sessionStorage;
    }
    
    return deserialized;
  }
}
Custom Framework Adapter

Create an adapter for a custom automation framework:

import { Adapter, BrowserSessionState, Event, RecordingOptions, PlaybackOptions } from '@psp/core';

// Example adapter for a custom framework
export class CustomFrameworkAdapter extends Adapter {
  private browser: any; // Your custom browser API
  
  constructor(options: any = {}) {
    super({
      ...options,
      type: 'custom-framework'
    });
  }
  
  async connect(browser: any): Promise {
    this.browser = browser;
    await super.connect(browser);
  }
  
  async captureState(): Promise {
    if (!this.browser) {
      throw new Error('Not connected to a browser');
    }
    
    // Get current URL and title
    const url = await this.browser.getCurrentUrl();
    const title = await this.browser.getTitle();
    const origin = new URL(url).origin;
    
    // Get cookies
    const cookies = await this.browser.getCookies();
    
    // Get localStorage and sessionStorage
    const storage = await this.browser.executeScript(`
      const localStorage = {};
      for (let i = 0; i < window.localStorage.length; i++) {
        const key = window.localStorage.key(i);
        localStorage[key] = window.localStorage.getItem(key);
      }
      
      const sessionStorage = {};
      for (let i = 0; i < window.sessionStorage.length; i++) {
        const key = window.sessionStorage.key(i);
        sessionStorage[key] = window.sessionStorage.getItem(key);
      }
      
      return { localStorage, sessionStorage };
    `);
    
    // Build the full state object
    return {
      version: '1.0.0',
      timestamp: Date.now(),
      origin,
      storage: {
        cookies: cookies.map(this.mapCookie),
        localStorage: this.convertToMap(storage.localStorage, origin),
        sessionStorage: this.convertToMap(storage.sessionStorage, origin)
      },
      history: {
        currentUrl: url,
        entries: [
          {
            url,
            title,
            timestamp: Date.now()
          }
        ],
        currentIndex: 0
      }
    };
  }
  
  async applyState(state: BrowserSessionState): Promise {
    if (!this.browser) {
      throw new Error('Not connected to a browser');
    }
    
    // Navigate to the URL
    if (state.history?.currentUrl) {
      await this.browser.navigate(state.history.currentUrl);
    }
    
    // Apply cookies
    for (const cookie of state.storage.cookies) {
      await this.browser.setCookie(this.reverseCookieMap(cookie));
    }
    
    // Apply localStorage and sessionStorage
    const origin = new URL(await this.browser.getCurrentUrl()).origin;
    const localStorage = state.storage.localStorage.get(origin);
    const sessionStorage = state.storage.sessionStorage.get(origin);
    
    await this.browser.executeScript(`
      // Apply localStorage
      const localStorage = ${JSON.stringify(Object.fromEntries(localStorage || []))};
      window.localStorage.clear();
      for (const [key, value] of Object.entries(localStorage)) {
        window.localStorage.setItem(key, value);
      }
      
      // Apply sessionStorage
      const sessionStorage = ${JSON.stringify(Object.fromEntries(sessionStorage || []))};
      window.sessionStorage.clear();
      for (const [key, value] of Object.entries(sessionStorage)) {
        window.sessionStorage.setItem(key, value);
      }
    `);
    
    // Refresh to apply all state
    await this.browser.refresh();
  }
  
  async startRecording(options?: RecordingOptions): Promise {
    if (!this.browser) {
      throw new Error('Not connected to a browser');
    }
    
    // Initialize recording
    await this.browser.executeScript(`
      window._pspEvents = [];
      window._pspStartTime = Date.now();
      
      // Set up event listeners
      document.addEventListener('click', e => {
        window._pspEvents.push({
          type: 'click',
          timestamp: Date.now() - window._pspStartTime,
          target: e.target.tagName + (e.target.id ? '#' + e.target.id : ''),
          data: {
            x: e.clientX,
            y: e.clientY
          }
        });
      });
      
      // Additional event listeners
    `);
  }
  
  async stopRecording(): Promise {
    if (!this.browser) {
      throw new Error('Not connected to a browser');
    }
    
    // Retrieve recorded events
    const events = await this.browser.executeScript(`
      const events = window._pspEvents;
      window._pspEvents = [];
      return events;
    `);
    
    return events;
  }
  
  async playRecording(events: Event[], options?: PlaybackOptions): Promise {
    if (!this.browser) {
      throw new Error('Not connected to a browser');
    }
    
    // Play back each event
    for (const event of events) {
      switch (event.type) {
        case 'click':
          await this.browser.click(event.target);
          break;
          
        // Handle other event types
          
        default:
          console.warn(`Unsupported event type: ${event.type}`);
      }
      
      // Add delay between events
      if (options?.speed) {
        await new Promise(resolve => setTimeout(resolve, 100 / (options.speed || 1)));
      }
    }
  }
  
  // Helper methods
  private mapCookie(cookie: any): any {
    return {
      name: cookie.name,
      value: cookie.value,
      domain: cookie.domain || '',
      path: cookie.path || '/',
      expires: cookie.expires ? new Date(cookie.expires).getTime() : null,
      httpOnly: cookie.httpOnly || false,
      secure: cookie.secure || false,
      sameSite: 'Lax' // Default
    };
  }
  
  private reverseCookieMap(cookie: any): any {
    return {
      name: cookie.name,
      value: cookie.value,
      domain: cookie.domain,
      path: cookie.path,
      expires: cookie.expires ? new Date(cookie.expires) : undefined,
      httpOnly: cookie.httpOnly,
      secure: cookie.secure
    };
  }
  
  private convertToMap(obj: Record, origin: string): Map> {
    const map = new Map>();
    map.set(origin, new Map(Object.entries(obj)));
    return map;
  }
}
End-to-End Testing Example

Integrate PSP into your testing framework:

// Example with Jest and Playwright
import { test, expect } from '@jest/globals';
import { chromium } from 'playwright';
import { PlaywrightAdapter } from '@psp/playwright';

// Setup and teardown
let browser;
let context;
let page;
let adapter;
let authSession;

beforeAll(async () => {
  // Launch browser once for all tests
  browser = await chromium.launch();
  
  // Create adapter
  adapter = new PlaywrightAdapter();
  
  // Initialize authentication session
  context = await browser.newContext();
  page = await context.newPage();
  
  // Create a session
  const session = await adapter.createSession(page, {
    name: 'auth-session-for-tests',
    description: 'Authenticated session for tests'
  });
  
  // Log in
  await page.goto('https://example.com/login');
  await page.fill('#username', 'test-user');
  await page.fill('#password', 'test-password');
  await page.click('button[type="submit"]');
  await page.waitForURL('https://example.com/dashboard');
  
  // Capture the authenticated session
  await session.capture();
  
  // Store for future tests
  authSession = session;
  
  // Close the login context
  await context.close();
});

afterAll(async () => {
  await browser.close();
});

// Helper function to create an authenticated page
async function getAuthenticatedPage() {
  const context = await browser.newContext();
  const page = await context.newPage();
  
  // Restore the auth session
  await authSession.restore(page);
  
  return { context, page };
}

// Tests
test('User can access profile page', async () => {
  const { page, context } = await getAuthenticatedPage();
  
  // Navigate directly to profile (already authenticated)
  await page.goto('https://example.com/profile');
  
  // Verify we're authenticated and can see the profile
  const username = await page.textContent('.username');
  expect(username).toBe('test-user');
  
  await context.close();
});

test('User can update settings', async () => {
  const { page, context } = await getAuthenticatedPage();
  
  // Navigate to settings
  await page.goto('https://example.com/settings');
  
  // Toggle a setting
  await page.click('#notifications-toggle');
  
  // Save changes
  await page.click('#save-button');
  
  // Verify success message
  const message = await page.textContent('.success-message');
  expect(message).toContain('Settings saved');
  
  await context.close();
});