Version 0.2: Browser API - Implementation Plan

November 22, 2025 · View on GitHub

Status: ✅ Complete

Feature: Browser launching, contexts, and page lifecycle

User Story: As a Rust developer, I want to launch browsers and create page objects so that I can prepare for browser automation (navigation and interaction come in Version 0.3).

Related ADRs:

Approach: Vertical slicing with TDD (Red → Green → Refactor), following Version 0.1 pattern

Progress: 7/7 slices complete (100%)


Overview

Version 0.2 builds on Version 0.1's protocol foundation to implement browser launching and page lifecycle management. This enables users to:

  • Launch browsers (Chromium, Firefox, WebKit)
  • Create browser contexts (isolated sessions)
  • Create page objects (empty pages at about:blank)
  • Basic lifecycle management (close browsers/contexts/pages)

Note: Navigation (page.goto()), element interaction (clicks, typing), and locators are Version 0.3. Version 0.2 only creates the browser/context/page objects.

Prerequisites from Version 0.1

✅ Protocol foundation complete:

  • JSON-RPC communication working
  • Object factory and ChannelOwner pattern established
  • Connection lifecycle management
  • Playwright initialization flow
  • Access to browser types

Note: Items deferred from Version 0.1 have been moved to Version 0.3 - see v0.3-page-interactions.md

Proposed Scope

Core Features

  1. BrowserType::launch() - Launch browser process

    • Launch options (headless, args, etc.)
    • Browser object creation
    • Browser lifecycle management
  2. Browser object - Represents browser instance

    • new_context() - Create browser context
    • new_page() - Shortcut for default context + page
    • close() - Graceful shutdown
    • contexts() - List contexts
    • Events: close
  3. BrowserContext object - Isolated browser session

    • new_page() - Create page in context
    • close() - Close all pages
    • pages() - List pages
    • Events: page, close
  4. Page object - Web page instance (initially at about:blank)

    • close() - Close page
    • url() - Get current URL (returns "about:blank" initially)
    • is_closed() - Check if page is closed
    • Events: close

Note: Navigation (goto()), content (title(), content()), and interactions are Version 0.3.

Documentation

  • Rustdoc for all public APIs
  • Examples for common patterns
  • Migration guide from Version 0.1

Testing

  • Unit tests for each object type
  • Integration tests with real browser launching
  • Cross-browser tests (all three browsers)
  • Error handling tests

Out of Scope (Future Versions)

  • Version 0.3: Navigation (page.goto()), locators (page.locator()), actions (click, type, fill)
  • Version 0.4: Screenshots, network interception, assertions, content APIs
  • Version 0.5: Mobile emulation, advanced features

Version 0.2 is strictly about object lifecycle - creating and closing Browser/Context/Page objects. The actual web automation (navigation, interaction) comes in Version 0.3.

Open Questions

  1. How to handle async Drop for Browser/Page? (Same pattern as Playwright from Version 0.1?)
  2. Launch options API design - builder pattern or options struct?
  3. Should we implement Drop for Browser/Context/Page for auto-cleanup?

Success Criteria

  • Can launch all three browsers (Chromium, Firefox, WebKit)
  • Can create browser contexts
  • Can create pages
  • Can close browsers gracefully
  • All tests passing with real browsers (macOS, Linux)
  • Documentation complete
  • Example code works

Note: Windows CI support deferred to Version 0.3 - see v0.3-page-interactions.md

Implementation Slices

Slice 1: Browser Object Foundation ✅

Goal: Create Browser protocol object that can be instantiated from server messages

Tasks:

  • Create protocol/browser.rs with Browser struct
  • Implement ChannelOwner trait for Browser
  • Add Browser to object factory type dispatch
  • Parse initializer (version, name fields)
  • Integration test (compile-time verification)

Files:

  • New: crates/playwright-core/src/protocol/browser.rs
  • Modify: crates/playwright-core/src/object_factory.rs
  • Modify: crates/playwright-core/src/protocol/mod.rs
  • New: crates/playwright-core/tests/browser_creation.rs

Tests:

#[test]
fn test_browser_type_exists() {
    // Verifies Browser implements ChannelOwner
    // Passes: ✅
}

Definition of Done:

  • ✅ Browser struct exists
  • ✅ Can be created from __create__ message
  • ✅ Registered in object factory
  • ✅ Compile-time test passes

Slice 2: Launch Options API ✅

Goal: Create LaunchOptions with builder pattern and normalization

Tasks:

  • Create api/launch_options.rs with full option set
  • Implement builder pattern methods
  • Implement normalize() for protocol compatibility
  • Unit tests for builder and normalization

Files:

  • New: crates/playwright-core/src/api/launch_options.rs
  • New: crates/playwright-core/src/api/mod.rs
  • Modify: crates/playwright-core/src/lib.rs

Tests:

#[test]
fn test_launch_options_builder()
#[test]
fn test_launch_options_normalize_env()
#[test]
fn test_launch_options_normalize_ignore_default_args()

Definition of Done:

  • ✅ LaunchOptions has all 17+ fields
  • ✅ Builder pattern works
  • ✅ Normalization matches protocol format
  • ✅ Unit tests pass (7 tests)

Slice 3: BrowserType::launch() ✅

Goal: Implement browser launching with real server integration

Tasks:

  • Add launch() method to BrowserType
  • Add launch_with_options() method
  • Handle launch RPC via Channel
  • Parse response and retrieve Browser from registry
  • Integration test with real browser launch

Files:

  • Modified: crates/playwright-core/src/protocol/browser_type.rs
  • New: crates/playwright-core/tests/browser_launch_integration.rs

Tests:

#[tokio::test]
async fn test_launch_chromium() // ✅ Passing
#[tokio::test]
async fn test_launch_with_headless_option() // ✅ Passing
#[tokio::test]
async fn test_launch_all_three_browsers() // ✅ Passing

Definition of Done:

  • ✅ Can launch Chromium with default options
  • ✅ Can launch with custom options
  • ✅ Can launch Firefox and WebKit
  • ✅ Browser object accessible after launch
  • ✅ Integration tests pass with real browsers

Key Implementation Details:

  • Used Channel::send() for "launch" RPC call
  • LaunchOptions::normalize() converts options to protocol format
  • Response contains { browser: { guid: "..." } }
  • Retrieved Browser from connection registry via get_object()
  • Downcast Arc to Browser using as_any()

Gotchas Discovered:

  • ⚠️ Browser versions must match Playwright server version
  • Server 1.49.0 requires: npx playwright@1.49.0 install
  • Documented in README.md to prevent "Executable doesn't exist" errors
  • Updated CI workflow to install matching browsers automatically

CI Updates:

  • Added browser installation step: npx playwright@1.49.0 install chromium firefox webkit --with-deps
  • Windows: Uses --with-deps flag only on Linux/macOS (Windows runners have deps pre-installed)
  • Added browser caching to speed up CI runs
  • Cache key: ${{ runner.os }}-playwright-browsers-1.49.0

Windows Workaround (Version 0.1 Known Issue):

  • ⚠️ Integration tests hang on Windows (stdio pipe cleanup issue)
  • CI now runs unit tests only on Windows: cargo test --lib --workspace
  • macOS/Linux run full test suite: cargo test --workspace
  • Will be fixed when implementing proper cleanup (Browser::close() or Drop implementation)

Slice 4: Browser::close() ✅

Goal: Implement graceful browser shutdown

Tasks:

  • Add close() method to Browser
  • Send "close" RPC to server
  • Handle server cleanup response
  • Test close with real browser
  • Update existing tests to use close()

Files:

  • Modified: crates/playwright-core/src/protocol/browser.rs
  • Modified: crates/playwright-core/tests/browser_launch_integration.rs
  • Modified: crates/playwright-core/tests/browser_creation.rs

Tests:

#[tokio::test]
async fn test_browser_close() // ✅ Passing
#[tokio::test]
async fn test_close_multiple_browsers() // ✅ Passing

Definition of Done:

  • ✅ Can close browser gracefully
  • ✅ Server process terminates cleanly
  • ✅ Tests verify cleanup works
  • ✅ All existing tests updated to use close()

Key Implementation Details:

  • Simple RPC call: channel.send_no_result("close", json!({}))
  • No response payload needed (void return)
  • Works across multiple browsers
  • All 7 tests pass with proper cleanup

Slice 5: BrowserContext Object ✅

Goal: Create BrowserContext protocol object

Tasks:

  • Create protocol/browser_context.rs
  • Implement ChannelOwner for BrowserContext
  • Add to object factory
  • Implement Browser::new_context()
  • Integration test

Files:

  • New: crates/playwright-core/src/protocol/browser_context.rs
  • Modified: crates/playwright-core/src/protocol/browser.rs
  • Modified: crates/playwright-core/src/protocol/mod.rs
  • Modified: crates/playwright-core/src/object_factory.rs
  • New: crates/playwright-core/tests/browser_context_integration.rs

Tests:

#[tokio::test]
async fn test_new_context() // ✅ Passing
#[tokio::test]
async fn test_multiple_contexts() // ✅ Passing

Definition of Done:

  • ✅ BrowserContext object exists
  • ✅ Can create context from browser
  • ✅ Can create multiple contexts
  • ✅ Can close contexts
  • ✅ Tests pass

Key Implementation Details:

  • Used Channel::send() for "newContext" RPC call
  • Response contains { context: { guid: "..." } }
  • Retrieved BrowserContext from connection registry via get_object()
  • Downcast Arc to BrowserContext using as_any()
  • Started with minimal options (empty JSON) - full ContextOptions deferred to later slice if needed
  • BrowserContext implements close() method for cleanup

Note: Full ContextOptions API (viewport, user agent, etc.) was deferred. Version 0.2 focuses on basic object lifecycle. Context configuration options can be added in a future slice or phase if needed.


Slice 6: Page Object ✅

Goal: Create Page protocol object with basic methods

Tasks:

  • Create protocol/page.rs
  • Implement ChannelOwner for Page
  • Add to object factory
  • Implement BrowserContext::new_page()
  • Implement Browser::new_page() (convenience)
  • Add basic page methods: url(), close()
  • Integration tests
  • Update examples

Files:

  • New: crates/playwright-core/src/protocol/page.rs
  • Modified: crates/playwright-core/src/protocol/browser_context.rs
  • Modified: crates/playwright-core/src/protocol/browser.rs
  • Modified: crates/playwright-core/src/protocol/mod.rs
  • Modified: crates/playwright-core/src/object_factory.rs
  • New: crates/playwright-core/tests/page_integration.rs
  • New: crates/playwright/examples/browser_lifecycle.rs
  • Modified: crates/playwright/examples/basic.rs

Tests:

#[tokio::test]
async fn test_context_new_page() // ✅ Passing
#[tokio::test]
async fn test_browser_new_page_convenience() // ✅ Passing
#[tokio::test]
async fn test_multiple_pages_in_context() // ✅ Passing
#[tokio::test]
async fn test_page_close() // ✅ Passing

Definition of Done:

  • ✅ Page object exists
  • ✅ Can create page from context
  • ✅ Can create page from browser (convenience)
  • ✅ Basic page methods work (url(), close())
  • ✅ Tests pass
  • ✅ Examples updated

Key Implementation Details:

  • Used Channel::send() for "newPage" RPC call
  • Response contains { page: { guid: "..." } }
  • Retrieved Page from connection registry via get_object()
  • Downcast Arc to Page using as_any()
  • url() returns "about:blank" for Version 0.2 (URL tracking in Version 0.3)
  • Browser::new_page() convenience creates default context internally
  • Created comprehensive examples demonstrating browser lifecycle

Note: Full URL tracking will be implemented in Version 0.3 when navigation and events are added. For now, page.url() always returns "about:blank".


Slice 7: Cleanup and Documentation ✅

Goal: Finalize Version 0.2 with docs and examples

Tasks:

  • Update public API exports in playwright crate
  • Verify rustdoc for all public APIs
  • Fix rustdoc link warnings
  • Update README with Version 0.2 features
  • Run full test suite
  • Verify examples run successfully
  • Review and implement deferred TODOs from Version 0.2
  • Update Version 0.2 status to Complete

Files:

  • Modified: crates/playwright/src/lib.rs
  • Modified: crates/playwright-core/src/protocol/browser.rs (doc fix)
  • Modified: crates/playwright-core/src/protocol/page.rs (doc fix)
  • Modified: crates/playwright-core/tests/connection_integration.rs (implemented deferred tests)
  • Modified: README.md
  • Modified: docs/roadmap.md
  • Modified: CLAUDE.md
  • Modified: docs/implementation-plans/v0.2-browser-api.md

Tests:

  • ✅ All tests pass
  • ✅ Both examples run successfully
  • ✅ Doc tests compile without warnings

Definition of Done:

  • ✅ All APIs documented
  • ✅ Examples demonstrate browser lifecycle
  • ✅ All tests pass (macOS, Linux)
  • ✅ Version 0.2 marked complete
  • ✅ Public API exports updated
  • Note: Windows integration tests deferred (tracked in Success Criteria and "Deferred from Version 0.1" section)

Key Accomplishments:

  • Exported all Version 0.2 types: Browser, BrowserContext, Page, BrowserType, LaunchOptions
  • Updated main lib.rs example to show full Version 0.2 workflow
  • Fixed all rustdoc warnings
  • Implemented deferred tests from connection_integration.rs:
    • test_concurrent_requests_with_server - verifies concurrent operations across Browser/Context/Page
    • test_error_response_from_server - verifies protocol error handling
  • Established documentation hierarchy and just-in-time principles
  • Verified complete test coverage
  • Both examples running cleanly

Version 0.2 Complete! 🎉

All 7 slices successfully implemented:

  1. ✅ Slice 1: Browser Object Foundation
  2. ✅ Slice 2: Launch Options API
  3. ✅ Slice 3: BrowserType::launch()
  4. ✅ Slice 4: Browser::close()
  5. ✅ Slice 5: BrowserContext Object
  6. ✅ Slice 6: Page Object
  7. ✅ Slice 7: Cleanup and Documentation

Next Steps

Version 0.3: Page Interactions - Navigation, locators, and actions

  • page.goto() - Navigate to URLs
  • page.locator() - Find elements with auto-waiting
  • Actions: click(), fill(), type(), etc.
  • Content queries: text_content(), inner_html(), etc.

See Roadmap for full Version 0.3 scope.


Created: 2025-11-06 Last Updated: 2025-11-07 Completed: 2025-11-07

Timeline:

  • Slice 1-7: All completed 2025-11-07
  • Total development time: 2 days