Table of contents

Can I use Symfony Panther with PHPUnit for automated testing?

Yes, Symfony Panther is specifically designed to work seamlessly with PHPUnit for automated browser testing. Panther is a powerful testing library that combines the capabilities of Symfony's DomCrawler with a real browser environment powered by ChromeDriver or Geckodriver, making it perfect for end-to-end testing scenarios.

What is Symfony Panther?

Symfony Panther is a browser testing library that allows you to write functional tests for web applications using a real browser. Unlike traditional unit tests that mock browser behavior, Panther executes JavaScript, handles AJAX requests, and provides a complete browser environment for comprehensive testing.

Installation and Setup

Installing Symfony Panther

First, install Symfony Panther via Composer in your PHP project:

composer require --dev symfony/panther

Installing Browser Drivers

Panther requires browser drivers to function. Install ChromeDriver for Chrome testing:

# Using Composer
composer require --dev dbrekelmans/bdi
vendor/bin/bdi detect drivers

# Or download manually
wget https://chromedriver.storage.googleapis.com/LATEST_RELEASE

For Firefox testing, install Geckodriver:

# macOS with Homebrew
brew install geckodriver

# Linux
wget https://github.com/mozilla/geckodriver/releases/latest/download/geckodriver-linux64.tar.gz
tar -xzf geckodriver-linux64.tar.gz
sudo mv geckodriver /usr/local/bin/

Basic PHPUnit Integration

Creating a Panther Test Class

Create a test class that extends Symfony's PantherTestCase:

<?php

namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class WebScrapingTest extends PantherTestCase
{
    public function testPageLoad(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com');

        $this->assertSelectorTextContains('h1', 'Welcome');
        $this->assertPageTitleContains('Example Domain');
    }
}

Advanced Testing with Form Interactions

<?php

namespace App\Tests;

use Symfony\Component\Panther\PantherTestCase;

class FormInteractionTest extends PantherTestCase
{
    public function testFormSubmission(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://httpbin.org/forms/post');

        // Fill out the form
        $form = $crawler->selectButton('Submit')->form();
        $form['custname'] = 'John Doe';
        $form['custtel'] = '123-456-7890';
        $form['custemail'] = 'john@example.com';

        // Submit and verify response
        $client->submit($form);
        $this->assertSelectorTextContains('pre', 'John Doe');
    }
}

Configuration and Customization

PHPUnit Configuration

Add Panther configuration to your phpunit.xml:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
    <php>
        <env name="PANTHER_APP_ENV" value="test"/>
        <env name="PANTHER_ERROR_SCREENSHOT_DIR" value="./var/error-screenshots"/>
        <env name="PANTHER_CHROME_ARGUMENTS" value="--disable-dev-shm-usage --no-sandbox"/>
        <env name="PANTHER_NO_HEADLESS" value="0"/>
    </php>

    <testsuites>
        <testsuite name="panther">
            <directory>tests/Panther</directory>
        </testsuite>
    </testsuites>
</phpunit>

Browser Options and Capabilities

Customize browser behavior with specific options:

<?php

use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;

class CustomBrowserTest extends PantherTestCase
{
    public function testWithCustomOptions(): void
    {
        $options = [
            '--window-size=1920,1080',
            '--disable-gpu',
            '--no-sandbox',
            '--disable-dev-shm-usage'
        ];

        $client = static::createPantherClient([
            'browser' => PantherTestCase::CHROME,
            'chromeArguments' => $options
        ]);

        $crawler = $client->request('GET', 'https://example.com');
        $this->assertResponseIsSuccessful();
    }
}

Handling Dynamic Content and AJAX

Waiting for Elements

When testing applications with dynamic content, use Panther's waiting capabilities:

<?php

use Symfony\Component\Panther\PantherTestCase;

class DynamicContentTest extends PantherTestCase
{
    public function testAjaxContent(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com/ajax-page');

        // Click button that triggers AJAX
        $client->clickLink('Load Data');

        // Wait for content to appear
        $client->waitFor('.ajax-content');

        // Verify the content
        $this->assertSelectorTextContains('.ajax-content', 'Dynamic data loaded');
    }

    public function testWaitForVisibility(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com');

        // Wait for element to become visible
        $client->waitForVisibility('#hidden-element');

        $this->assertSelectorIsVisible('#hidden-element');
    }
}

JavaScript Execution and Browser Events

Executing Custom JavaScript

<?php

use Symfony\Component\Panther\PantherTestCase;

class JavaScriptTest extends PantherTestCase
{
    public function testJavaScriptExecution(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com');

        // Execute JavaScript
        $result = $client->executeScript('return document.title;');
        $this->assertStringContains('Example', $result);

        // Modify page with JavaScript
        $client->executeScript('document.querySelector("h1").textContent = "Modified Title";');
        $this->assertSelectorTextContains('h1', 'Modified Title');
    }
}

Data-Driven Testing

Testing with Multiple Datasets

<?php

use Symfony\Component\Panther\PantherTestCase;

class DataDrivenTest extends PantherTestCase
{
    /**
     * @dataProvider searchTermProvider
     */
    public function testSearchFunctionality(string $searchTerm, string $expectedResult): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com/search');

        $form = $crawler->selectButton('Search')->form();
        $form['q'] = $searchTerm;

        $client->submit($form);
        $client->waitFor('.search-results');

        $this->assertSelectorTextContains('.search-results', $expectedResult);
    }

    public function searchTermProvider(): array
    {
        return [
            ['web scraping', 'Web Scraping Results'],
            ['API testing', 'API Testing Results'],
            ['automation', 'Automation Results']
        ];
    }
}

Error Handling and Debugging

Screenshot on Failure

Configure automatic screenshots when tests fail:

<?php

use Symfony\Component\Panther\PantherTestCase;

class DebugTest extends PantherTestCase
{
    protected function onNotSuccessfulTest(\Throwable $t): void
    {
        if ($this->client) {
            $this->client->takeScreenshot('failure-screenshot.png');
        }

        parent::onNotSuccessfulTest($t);
    }

    public function testWithErrorHandling(): void
    {
        $client = static::createPantherClient();

        try {
            $crawler = $client->request('GET', 'https://example.com');
            $this->assertSelectorExists('.non-existent-element');
        } catch (\Exception $e) {
            $client->takeScreenshot('debug-screenshot.png');
            throw $e;
        }
    }
}

Performance Optimization

Parallel Test Execution

For faster test execution, configure parallel testing:

<!-- phpunit.xml -->
<phpunit processIsolation="true" 
         beStrictAboutResourceUsageDuringSmallTests="true">
    <extensions>
        <extension class="ParaTest\Extension" />
    </extensions>
</phpunit>

Run tests in parallel:

vendor/bin/paratest --processes=4 tests/Panther/

Browser Instance Management

<?php

use Symfony\Component\Panther\PantherTestCase;

class OptimizedTest extends PantherTestCase
{
    private static $client;

    public static function setUpBeforeClass(): void
    {
        self::$client = static::createPantherClient();
    }

    public static function tearDownAfterClass(): void
    {
        self::$client->quit();
    }

    public function testOptimizedExecution(): void
    {
        $crawler = self::$client->request('GET', 'https://example.com');
        $this->assertResponseIsSuccessful();
    }
}

Integration with CI/CD

GitHub Actions Configuration

name: Panther Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.1'
        extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite

    - name: Install dependencies
      run: composer install --prefer-dist --no-progress

    - name: Install ChromeDriver
      run: vendor/bin/bdi detect drivers

    - name: Run Panther tests
      run: vendor/bin/phpunit --testsuite=panther
      env:
        PANTHER_NO_HEADLESS: 0
        PANTHER_CHROME_ARGUMENTS: "--disable-dev-shm-usage --no-sandbox"

Working with Iframes and Complex Elements

Handling Iframe Content

When testing applications with iframes, switch context appropriately:

<?php

use Symfony\Component\Panther\PantherTestCase;

class IframeTest extends PantherTestCase
{
    public function testIframeContent(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com/iframe-page');

        // Switch to iframe
        $client->switchTo()->frame('content-frame');

        // Interact with iframe content
        $this->assertSelectorTextContains('h1', 'Iframe Content');

        // Switch back to main frame
        $client->switchTo()->defaultContent();
    }
}

Testing Single Page Applications

For SPAs and dynamic content, similar to handling AJAX requests using browser automation:

<?php

use Symfony\Component\Panther\PantherTestCase;

class SPATest extends PantherTestCase
{
    public function testSinglePageAppNavigation(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://spa-example.com');

        // Wait for SPA to load
        $client->waitFor('[data-testid="app-loaded"]');

        // Navigate within SPA
        $client->clickLink('Products');
        $client->waitFor('[data-testid="products-page"]');

        $this->assertSelectorTextContains('h1', 'Products');
    }
}

Mobile Testing and Responsive Design

Emulating Mobile Devices

<?php

use Symfony\Component\Panther\PantherTestCase;

class MobileTest extends PantherTestCase
{
    public function testMobileView(): void
    {
        $client = static::createPantherClient([
            'chromeArguments' => [
                '--window-size=375,667', // iPhone SE dimensions
                '--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15'
            ]
        ]);

        $crawler = $client->request('GET', 'https://example.com');

        // Test mobile-specific elements
        $this->assertSelectorExists('.mobile-menu');
        $this->assertSelectorNotExists('.desktop-nav');
    }
}

Best Practices

Test Organization

  1. Separate test suites: Keep Panther tests separate from unit tests for better organization
  2. Use page objects: Create page object classes for complex applications
  3. Implement waiting strategies: Always wait for elements before interactions, similar to handling timeouts in browser automation
  4. Clean up resources: Properly close browser instances after tests

Performance Considerations

  • Use headless mode in CI environments
  • Implement browser instance pooling for faster execution
  • Optimize selectors for better performance
  • Consider using data providers for parameterized tests

Security and Authentication Testing

<?php

use Symfony\Component\Panther\PantherTestCase;

class AuthenticationTest extends PantherTestCase
{
    public function testLoginFlow(): void
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com/login');

        // Fill login form
        $form = $crawler->selectButton('Login')->form();
        $form['username'] = 'testuser';
        $form['password'] = 'testpass';

        $client->submit($form);
        $client->waitFor('.dashboard');

        // Verify successful login
        $this->assertSelectorTextContains('.user-menu', 'Welcome, testuser');
    }
}

Conclusion

Symfony Panther with PHPUnit provides a robust solution for end-to-end browser testing in PHP applications. Its seamless integration with PHPUnit, support for real browser environments, and comprehensive API make it an excellent choice for testing modern web applications that rely heavily on JavaScript and dynamic content.

The combination allows you to write maintainable, reliable tests that accurately simulate user interactions while leveraging PHPUnit's familiar testing framework. Whether you're testing form submissions, AJAX interactions, or complex user workflows, Panther and PHPUnit together provide the tools needed for comprehensive automated testing.

With proper configuration and best practices, you can create a robust testing suite that ensures your web applications work correctly across different browsers and scenarios, providing confidence in your deployment process and user experience.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon