A Swift library for browser automation using Playwright. Control Chromium, Firefox, and WebKit browsers with a modern, async/await API.
- macOS 14.0+
- Swift 6.0+ (uses Swift Macros)
Add PlaywrightSwift to your Package.swift:
dependencies: [
.package(url: "https://github.com/vinayjn/playwright-swift.git", from: "1.0.0")
]Before running automation, install the browser binaries:
import PlaywrightSwift
try await Playwright.installBrowsers()Given this HTML:
<form id="search-form">
<input type="text" placeholder="Search..." />
<button type="submit">Search</button>
</form>Automate it with:
import PlaywrightSwift
// Launch browser
let playwright = try await Playwright.create()
let browser = try await playwright.chromium.launch()
// Create page and navigate
let page = try await browser.newPage()
try await page.goto("https://example.com")
// Interact with elements
try await page.locator("input").fill("Playwright")
try await page.locator("button").click()
// Take a screenshot
try await page.screenshot(options: .init(path: "screenshot.png"))
try await browser.close()Given this HTML:
<form id="login">
<input id="email" type="email" name="email" />
<input id="password" type="password" name="password" />
<button id="submit" data-testid="login-btn">Log In</button>
</form>
<nav>
<a class="nav" href="/dashboard">Dashboard</a>
</nav>Use TypedLocators for compile-time type safety:
// Create typed locators with element-specific methods
let emailInput = page.input("#email") // TypedLocator<InputElement>
let submitBtn = page.button("#submit") // TypedLocator<ButtonElement>
let navLink = page.link("a.nav") // TypedLocator<LinkElement>
// Type-specific methods only available on correct types
try await emailInput.fill("user@example.com") // ✅ Works
try await emailInput.clear() // ✅ Works
// submitBtn.fill("text") // ❌ Won't compile
// Type-safe attribute access
let href = try await navLink.getHref() // Returns "/dashboard"
let testId = try await submitBtn.getAttribute("data-testid")Learn more about specific features:
- Locators: Finding and interacting with elements.
- Navigation & Waiters: Managing URLs, history, and async waiting.
- Browser Context: Cookies, permissions, and geolocation.
- Network Interception: Mocking APIs and monitoring traffic.
- API Testing: Making HTTP requests directly from tests.
- Testing & Assertions: Writing robust tests and handling dialogs.
- TypedLocators: Phantom types provide compile-time enforcement of element-specific APIs.
- Swift Macros: Compile-time selector validation with
#selectorand type-safe Page Objects with@PageObject. - Auto-waiting: Locators automatically wait for elements to be ready before acting.
- Type-safe API: Leverage Swift's type system for browser options and results.
- Modern Concurrency: Built from the ground up using Swift's
async/await. - Event Streams: Type-safe
AsyncSequence-based event handling for console, requests, responses, dialogs, and downloads. - Cross-browser: Support for Chromium, Firefox, and WebKit.
- Network Mocking: Intercept any network request to mock backend responses.
- Video & Downloads: Record test execution and manage file downloads.
Given this HTML:
<button role="button" name="Submit">Submit Order</button>
<input data-testid="email-input" type="email" />
<a href="/help">Click here for help</a>Use the Selector enum for type-safe, autocomplete-friendly selectors:
// Role-based selector (matches the <button>)
let button = page.locator(.role(.button, name: "Submit"))
// Test ID selector (matches the <input>)
let input = page.locator(.testId("email-input"))
// Text-based selector (matches the <a>)
let link = page.locator(.text("Click here", exact: false))Build complex selectors with the locator chain builder:
Given this HTML:
<div data-testid="product-list">
<li>Samsung Galaxy</li>
<li>iPhone 15 Pro</li>
<li>Google Pixel</li>
</div>// Find the iPhone item within the product list
let item = page.locate {
LocatorStep.testId("product-list")
LocatorStep.css("li")
LocatorStep.filter(hasText: "iPhone")
}Configure network routes declaratively:
try await page.configureRoutes {
RouteConfig.mock("**/api/user", with: "{\"name\": \"Test\"}")
RouteConfig.block("**/*.analytics.js")
RouteConfig.passthrough("**/*")
}Given this HTML that updates dynamically:
<div id="status">Loading...</div>
<!-- After API call completes, changes to: -->
<div id="status">Success</div>Wait for specific element states:
let statusDiv = page.locator("#status")
try await statusDiv.waitFor(.visible)
try await statusDiv.waitFor(.hasText("Success"))
try await statusDiv.waitFor(.enabled, timeout: .seconds(10))Enable detailed logging and protocol tracing for debugging:
// Enable detailed logs
PlaywrightLogger.level = .debug
// Enable verbose protocol tracing
ProtocolTracer.shared.isEnabled = true
ProtocolTracer.shared.methodFilter = "goto" // Optional filterUse PageTestCase for XCTest integration:
import PlaywrightSwift
import XCTest
class MyAppTests: PageTestCase {
func testLogin() async throws {
try await page.goto("https://app.example.com")
// ...
}
}MIT License - see LICENSE for details.