Playwright Automation with Python: Simplicity Meets Power

November 18, 2024

Playwright
Python
Automation
Testing
Development

Playwright Automation with Python: Simplicity Meets Power

Python's simplicity combined with Playwright's capabilities creates an incredibly productive automation toolkit. Let's dive into how to harness this powerful combination.

Why Playwright with Python?

Python developers love Playwright because:

  • Pythonic API: Natural, intuitive syntax
  • Async Support: Modern async/await patterns
  • Easy Setup: Simple pip installation
  • Rich Ecosystem: Integrates with pytest, unittest, and more

Installation and Setup

Getting started is straightforward:

bash
1pip install playwright
2playwright install

This installs Playwright and downloads the necessary browser binaries.

Your First Python Script

Here's a simple web scraping example:

python
1from playwright.sync_api import sync_playwright
2
3with sync_playwright() as p:
4    browser = p.chromium.launch()
5    page = browser.new_page()
6    page.goto('https://example.com')
7    
8    # Extract data
9    title = page.title()
10    heading = page.locator('h1').inner_text()
11    
12    print(f"Title: {title}")
13    print(f"Heading: {heading}")
14    
15    browser.close()

Async vs Sync API

Playwright offers both synchronous and asynchronous APIs:

Synchronous (Easier for Beginners)

python
1from playwright.sync_api import sync_playwright
2
3def test_homepage():
4    with sync_playwright() as p:
5        browser = p.chromium.launch()
6        page = browser.new_page()
7        page.goto('https://example.com')
8        assert 'Example' in page.title()
9        browser.close()

Asynchronous (Better Performance)

python
1import asyncio
2from playwright.async_api import async_playwright
3
4async def test_homepage():
5    async with async_playwright() as p:
6        browser = await p.chromium.launch()
7        page = await browser.new_page()
8        await page.goto('https://example.com')
9        title = await page.title()
10        assert 'Example' in title
11        await browser.close()
12
13asyncio.run(test_homepage())

Pytest Integration

Playwright integrates seamlessly with pytest:

bash
1pip install pytest-playwright
python
1# test_example.py
2import pytest
3from playwright.sync_api import Page
4
5def test_homepage_title(page: Page):
6    page.goto('https://example.com')
7    assert 'Example Domain' in page.title()
8
9def test_navigation(page: Page):
10    page.goto('https://example.com')
11    page.click('a:has-text("More information")')
12    page.wait_for_url('**/iana.org/**')

Run tests with:

bash
1pytest --headed --browser chromium

Web Scraping with Python

A practical data extraction example:

python
1from playwright.sync_api import sync_playwright
2import json
3
4def scrape_products():
5    with sync_playwright() as p:
6        browser = p.chromium.launch(headless=True)
7        page = browser.new_page()
8        page.goto('https://example-shop.com/products')
9        
10        # Wait for products to load
11        page.wait_for_selector('.product-card')
12        
13        # Extract product data
14        products = []
15        for product in page.locator('.product-card').all():
16            products.append({
17                'name': product.locator('.product-name').inner_text(),
18                'price': product.locator('.product-price').inner_text(),
19                'image': product.locator('img').get_attribute('src')
20            })
21        
22        browser.close()
23        return products
24
25# Save to JSON
26products = scrape_products()
27with open('products.json', 'w') as f:
28    json.dump(products, f, indent=2)

Advanced Testing Patterns

Page Object Model

python
1class LoginPage:
2    def __init__(self, page: Page):
3        self.page = page
4        self.email_input = page.locator('#email')
5        self.password_input = page.locator('#password')
6        self.submit_button = page.locator('button[type="submit"]')
7    
8    def login(self, email: str, password: str):
9        self.email_input.fill(email)
10        self.password_input.fill(password)
11        self.submit_button.click()
12    
13    def expect_error(self, message: str):
14        error = self.page.locator('.error-message')
15        assert error.inner_text() == message
16
17# Usage in test
18def test_invalid_login(page: Page):
19    login_page = LoginPage(page)
20    login_page.login('invalid@email.com', 'wrongpass')
21    login_page.expect_error('Invalid credentials')

Fixtures and Reusable Components

python
1import pytest
2from playwright.sync_api import Page
3
4@pytest.fixture
5def authenticated_page(page: Page):
6    """Fixture that provides an authenticated session"""
7    page.goto('https://example.com/login')
8    page.fill('#email', 'test@example.com')
9    page.fill('#password', 'password123')
10    page.click('button:has-text("Login")')
11    page.wait_for_url('**/dashboard')
12    return page
13
14def test_dashboard_access(authenticated_page: Page):
15    assert authenticated_page.locator('.welcome-message').is_visible()

Handling Dynamic Content

Waiting for Elements

python
1# Wait for specific selector
2page.wait_for_selector('.data-loaded')
3
4# Wait for network idle
5page.goto('https://example.com', wait_until='networkidle')
6
7# Custom wait condition
8page.wait_for_function('document.querySelectorAll(".item").length > 10')

Intercepting Network Requests

python
1def test_api_mock(page: Page):
2    # Mock API response
3    def handle_route(route):
4        route.fulfill(
5            status=200,
6            body=json.dumps({'user': 'Test User', 'role': 'admin'})
7        )
8    
9    page.route('**/api/user', handle_route)
10    page.goto('https://example.com/profile')
11    assert page.locator('.username').inner_text() == 'Test User'

Taking Screenshots and Videos

python
1def test_visual_regression(page: Page):
2    page.goto('https://example.com')
3    
4    # Full page screenshot
5    page.screenshot(path='homepage.png', full_page=True)
6    
7    # Element screenshot
8    page.locator('.hero-section').screenshot(path='hero.png')
9
10# Record video (in pytest.ini or conftest.py)
11@pytest.fixture(scope='session')
12def browser_context_args(browser_context_args):
13    return {
14        **browser_context_args,
15        'record_video_dir': 'videos/'
16    }

CI/CD Integration

GitHub Actions Example

yaml
1name: Playwright Tests
2on: [push, pull_request]
3jobs:
4  test:
5    runs-on: ubuntu-latest
6    steps:
7      - uses: actions/checkout@v3
8      - uses: actions/setup-python@v4
9        with:
10          python-version: '3.11'
11      - name: Install dependencies
12        run: |
13          pip install -r requirements.txt
14          playwright install --with-deps
15      - name: Run tests
16        run: pytest --browser chromium --headed=false

Best Practices

1. Use Context Managers

python
1# Always use 'with' statement
2with sync_playwright() as p:
3    # Your code here
4    pass  # Browser automatically closes

2. Leverage Auto-Waiting

python
1# Playwright waits automatically
2page.click('button')  # Waits for button to be clickable
3page.fill('input', 'text')  # Waits for input to be editable

3. Use Data Classes for Test Data

python
1from dataclasses import dataclass
2
3@dataclass
4class User:
5    email: str
6    password: str
7    role: str
8
9admin_user = User('admin@test.com', 'admin123', 'admin')

Debugging Tips

python
1# Add pause for debugging
2page.pause()
3
4# Enable verbose logging
5import logging
6logging.basicConfig(level=logging.DEBUG)
7
8# Slow down execution
9browser = p.chromium.launch(slow_mo=1000)  # 1 second delay

Conclusion

Playwright with Python offers:

āœ… Simple, readable code āœ… Powerful automation capabilities āœ… Excellent pytest integration āœ… Great for both testing and scraping

Whether you're automating tests, scraping data, or building bots, Playwright's Python API makes it accessible and productive.

Happy coding! šŸšŸŽ­


Was this helpful?

0

0

0


Comments (0)

Join the Discussion

Sign in to share your thoughts and connect with other readers