Table of contents

How do I handle multiple browser windows and tabs with Symfony Panther?

Symfony Panther provides powerful capabilities for managing multiple browser windows and tabs, making it an excellent choice for complex web scraping scenarios and end-to-end testing. This comprehensive guide covers all aspects of handling multiple browser contexts, from basic tab management to advanced multi-window operations.

Understanding Browser Context in Symfony Panther

Symfony Panther is built on top of ChromeDriver and supports full browser automation, including the ability to work with multiple windows and tabs simultaneously. Each window or tab represents a separate browsing context with its own DOM, cookies, and session state.

Basic Window and Tab Management

Here's how to create and manage multiple tabs in Symfony Panther:

<?php

use Symfony\Component\Panther\PantherTestCase;

class MultiTabExample extends PantherTestCase
{
    public function testMultipleTabs(): void
    {
        $client = static::createPantherClient();

        // Navigate to the first page
        $crawler = $client->request('GET', 'https://example.com/page1');

        // Open a new tab and switch to it
        $client->executeScript('window.open("about:blank", "_blank");');
        $handles = $client->getWebDriver()->getWindowHandles();
        $client->getWebDriver()->switchTo()->window(end($handles));

        // Navigate to a different page in the new tab
        $client->request('GET', 'https://example.com/page2');

        // Get content from the current tab
        $content = $client->getCrawler()->text();

        // Switch back to the first tab
        $client->getWebDriver()->switchTo()->window($handles[0]);

        // Verify we're back on the original page
        $this->assertStringContainsString('Page 1', $client->getCrawler()->text());
    }
}

Advanced Window Management Techniques

Creating Multiple Windows with Specific URLs

For more sophisticated scenarios, you can create multiple windows with specific URLs and manage them programmatically:

<?php

class AdvancedWindowManagement extends PantherTestCase
{
    private array $windowHandles = [];

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

        $urls = [
            'https://example.com/products',
            'https://example.com/categories',
            'https://example.com/reviews'
        ];

        // Open multiple windows
        foreach ($urls as $index => $url) {
            if ($index === 0) {
                // First window is already open
                $client->request('GET', $url);
                $this->windowHandles[] = $client->getWebDriver()->getWindowHandle();
            } else {
                // Open new window
                $client->executeScript("window.open('$url', '_blank');");
                $handles = $client->getWebDriver()->getWindowHandles();
                $this->windowHandles[] = end($handles);
            }
        }

        // Process each window
        foreach ($this->windowHandles as $index => $handle) {
            $client->getWebDriver()->switchTo()->window($handle);

            // Perform specific actions based on the window
            $this->processWindow($client, $index);
        }
    }

    private function processWindow($client, int $windowIndex): void
    {
        switch ($windowIndex) {
            case 0: // Products window
                $products = $client->getCrawler()->filter('.product-item');
                // Process products...
                break;
            case 1: // Categories window
                $categories = $client->getCrawler()->filter('.category-link');
                // Process categories...
                break;
            case 2: // Reviews window
                $reviews = $client->getCrawler()->filter('.review');
                // Process reviews...
                break;
        }
    }
}

Window Management Helper Class

Create a reusable helper class for better window management:

<?php

class WindowManager
{
    private $client;
    private array $windows = [];

    public function __construct($client)
    {
        $this->client = $client;
        // Store the initial window
        $this->windows['main'] = $client->getWebDriver()->getWindowHandle();
    }

    public function openNewWindow(string $url, string $name = null): string
    {
        $this->client->executeScript("window.open('$url', '_blank');");
        $handles = $this->client->getWebDriver()->getWindowHandles();
        $newHandle = end($handles);

        $windowName = $name ?: 'window_' . count($this->windows);
        $this->windows[$windowName] = $newHandle;

        return $windowName;
    }

    public function switchToWindow(string $name): void
    {
        if (!isset($this->windows[$name])) {
            throw new \InvalidArgumentException("Window '$name' not found");
        }

        $this->client->getWebDriver()->switchTo()->window($this->windows[$name]);
    }

    public function closeWindow(string $name): void
    {
        if (!isset($this->windows[$name])) {
            return;
        }

        $this->switchToWindow($name);
        $this->client->getWebDriver()->close();
        unset($this->windows[$name]);

        // Switch back to main window if current window was closed
        if (!empty($this->windows)) {
            $this->switchToWindow('main');
        }
    }

    public function getAllWindowNames(): array
    {
        return array_keys($this->windows);
    }

    public function getWindowCount(): int
    {
        return count($this->windows);
    }
}

Handling Pop-ups and Modal Windows

Symfony Panther can effectively handle pop-ups and modal windows that open in new browser contexts:

<?php

public function testPopupHandling(): void
{
    $client = static::createPantherClient();
    $windowManager = new WindowManager($client);

    // Navigate to page with popup trigger
    $client->request('GET', 'https://example.com/popup-test');

    // Click button that opens popup
    $client->getCrawler()->selectButton('Open Popup')->click();

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

    // Switch to popup window
    $handles = $client->getWebDriver()->getWindowHandles();
    if (count($handles) > 1) {
        $client->getWebDriver()->switchTo()->window(end($handles));

        // Interact with popup content
        $popupText = $client->getCrawler()->filter('.popup-message')->text();

        // Close popup
        $client->getCrawler()->selectButton('Close')->click();

        // Switch back to main window
        $client->getWebDriver()->switchTo()->window($handles[0]);
    }
}

Parallel Processing with Multiple Windows

For efficient web scraping, you can process multiple pages simultaneously. While Symfony Panther doesn't support true parallel execution within a single browser instance, you can simulate concurrent processing:

<?php

class ParallelProcessing extends PantherTestCase
{
    public function testParallelPageProcessing(): void
    {
        $client = static::createPantherClient();
        $windowManager = new WindowManager($client);

        $urls = [
            'page1' => 'https://example.com/data/1',
            'page2' => 'https://example.com/data/2',
            'page3' => 'https://example.com/data/3'
        ];

        // Open all pages in separate windows
        foreach ($urls as $name => $url) {
            if ($name === 'page1') {
                $client->request('GET', $url);
            } else {
                $windowManager->openNewWindow($url, $name);
            }
        }

        $results = [];

        // Process each window
        foreach (array_keys($urls) as $windowName) {
            $windowManager->switchToWindow($windowName === 'page1' ? 'main' : $windowName);

            // Wait for page to load completely
            $client->waitFor('[data-loaded="true"]', 10);

            // Extract data
            $results[$windowName] = $this->extractDataFromCurrentPage($client);
        }

        return $results;
    }

    private function extractDataFromCurrentPage($client): array
    {
        return [
            'title' => $client->getCrawler()->filter('h1')->text(),
            'content' => $client->getCrawler()->filter('.content')->text(),
            'metadata' => $client->getCrawler()->filter('[data-meta]')->extract(['data-meta'])
        ];
    }
}

Session and Cookie Management Across Windows

Each browser window maintains its own session state, but you can share cookies and session data when needed:

<?php

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

    // Login in the main window
    $client->request('GET', 'https://example.com/login');
    $client->submitForm('login-form', [
        'username' => 'testuser',
        'password' => 'password123'
    ]);

    // Get session cookies
    $cookies = $client->getCookieJar()->all();

    // Open new window
    $client->executeScript('window.open("about:blank", "_blank");');
    $handles = $client->getWebDriver()->getWindowHandles();
    $client->getWebDriver()->switchTo()->window(end($handles));

    // Set cookies in new window (same domain)
    foreach ($cookies as $cookie) {
        $client->getCookieJar()->set($cookie);
    }

    // Navigate to protected page
    $client->request('GET', 'https://example.com/protected');

    // Verify authentication state is preserved
    $this->assertStringNotContainsString('Please log in', $client->getCrawler()->text());
}

Error Handling and Best Practices

When working with multiple windows, implement robust error handling:

<?php

class RobustWindowHandling extends PantherTestCase
{
    public function testRobustWindowManagement(): void
    {
        $client = static::createPantherClient();
        $windowManager = new WindowManager($client);

        try {
            // Attempt to open multiple windows
            $windowNames = [];
            $urls = ['https://example.com/page1', 'https://example.com/page2'];

            foreach ($urls as $index => $url) {
                try {
                    $windowName = "window_$index";
                    $windowManager->openNewWindow($url, $windowName);
                    $windowNames[] = $windowName;

                    // Verify window opened successfully
                    $windowManager->switchToWindow($windowName);
                    $client->waitFor('body', 5);

                } catch (\Exception $e) {
                    // Log error and continue with other windows
                    error_log("Failed to open window for $url: " . $e->getMessage());
                }
            }

            // Process successfully opened windows
            foreach ($windowNames as $windowName) {
                try {
                    $windowManager->switchToWindow($windowName);
                    $this->processCurrentWindow($client);
                } catch (\Exception $e) {
                    error_log("Failed to process window $windowName: " . $e->getMessage());
                }
            }

        } finally {
            // Clean up all windows
            foreach ($windowManager->getAllWindowNames() as $windowName) {
                if ($windowName !== 'main') {
                    $windowManager->closeWindow($windowName);
                }
            }
        }
    }

    private function processCurrentWindow($client): void
    {
        // Implement your window processing logic
        $title = $client->getTitle();
        $content = $client->getCrawler()->filter('body')->text();

        // Store or process the extracted data
    }
}

JavaScript Execution Across Windows

You can execute JavaScript code in specific windows to enhance your automation capabilities:

<?php

public function testJavaScriptExecutionAcrossWindows(): void
{
    $client = static::createPantherClient();
    $windowManager = new WindowManager($client);

    // Open multiple windows
    $windowManager->openNewWindow('https://example.com/page1', 'window1');
    $windowManager->openNewWindow('https://example.com/page2', 'window2');

    // Execute JavaScript in specific windows
    $windowManager->switchToWindow('window1');
    $result1 = $client->executeScript('return document.title;');

    $windowManager->switchToWindow('window2');
    $result2 = $client->executeScript('return window.location.href;');

    // Manipulate DOM elements in different windows
    $windowManager->switchToWindow('window1');
    $client->executeScript('document.body.style.backgroundColor = "red";');

    $windowManager->switchToWindow('window2');
    $client->executeScript('document.body.style.backgroundColor = "blue";');
}

Integration with Web Scraping Workflows

When building comprehensive web scraping solutions, multiple window management becomes particularly valuable. Similar to how to run multiple pages in parallel with Puppeteer, Symfony Panther allows you to efficiently scrape multiple pages simultaneously.

For complex authentication scenarios across multiple windows, you might also want to explore how to handle authentication in Puppeteer for additional insights that can be adapted to Symfony Panther.

Performance Considerations

When working with multiple windows in Symfony Panther:

  1. Memory Usage: Each window consumes additional memory. Monitor your application's memory usage and close windows when no longer needed.

  2. Browser Limits: Most browsers have limits on the number of concurrent tabs/windows. Chrome typically allows around 50-100 tabs before performance degrades.

  3. Synchronization: Be careful about timing issues when switching between windows. Always wait for elements to load before interacting with them.

  4. Resource Cleanup: Always close unused windows to free up resources:

// Clean up resources
foreach ($windowHandles as $handle) {
    try {
        $client->getWebDriver()->switchTo()->window($handle);
        $client->getWebDriver()->close();
    } catch (\Exception $e) {
        // Window might already be closed
    }
}

Common Pitfalls and Solutions

Window Handle Management

One common issue is losing track of window handles. Always store handles in a structured way:

<?php

class WindowHandleTracker
{
    private array $windowRegistry = [];

    public function registerWindow(string $name, string $handle): void
    {
        $this->windowRegistry[$name] = [
            'handle' => $handle,
            'created_at' => time(),
            'active' => true
        ];
    }

    public function getActiveWindows(): array
    {
        return array_filter($this->windowRegistry, fn($window) => $window['active']);
    }

    public function markWindowClosed(string $name): void
    {
        if (isset($this->windowRegistry[$name])) {
            $this->windowRegistry[$name]['active'] = false;
        }
    }
}

Timing Issues

When switching between windows, ensure proper timing:

<?php

public function safeWindowSwitch($client, string $windowHandle): void
{
    $client->getWebDriver()->switchTo()->window($windowHandle);

    // Wait for window to be ready
    $client->waitFor('body', 5);

    // Verify we're in the correct window
    $currentHandle = $client->getWebDriver()->getWindowHandle();
    if ($currentHandle !== $windowHandle) {
        throw new \RuntimeException('Failed to switch to expected window');
    }
}

Conclusion

Symfony Panther provides robust support for managing multiple browser windows and tabs, making it an excellent choice for complex web scraping and testing scenarios. By implementing proper window management strategies, error handling, and resource cleanup, you can build efficient and reliable automation scripts that leverage multiple browser contexts effectively.

Remember to always clean up resources, handle errors gracefully, and consider the performance implications when working with multiple windows. The techniques outlined in this guide will help you build sophisticated web automation solutions that can handle complex multi-window scenarios with confidence.

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