Table of contents

How do I handle popup windows and alerts with Symfony Panther?

Handling popup windows and JavaScript alerts is a common challenge when web scraping with Symfony Panther. This comprehensive guide covers various types of popups, alerts, and modal dialogs you'll encounter, along with practical solutions and code examples.

Understanding Different Types of Popups

Before diving into solutions, it's important to understand the different types of popups and alerts you might encounter:

1. JavaScript Alerts

These are native browser alerts created using alert(), confirm(), and prompt() functions.

2. Modal Dialogs

HTML-based modal windows that overlay the main content, typically using CSS and JavaScript frameworks.

3. New Browser Windows

Actual new browser windows or tabs opened by JavaScript's window.open() function.

4. Authentication Dialogs

HTTP Basic Authentication popups triggered by the browser.

Handling JavaScript Alerts

Symfony Panther provides built-in methods to handle native JavaScript alerts through its underlying Chrome/Chromium browser automation.

Basic Alert Handling

<?php

use Symfony\Component\Panther\PantherTestCase;

class AlertHandlingTest extends PantherTestCase
{
    public function testHandleSimpleAlert()
    {
        $client = static::createPantherClient();
        $crawler = $client->request('GET', 'https://example.com/page-with-alert');

        // Trigger an action that shows an alert
        $crawler->filter('#trigger-alert-button')->click();

        // Accept the alert
        $client->getWebDriver()->switchTo()->alert()->accept();

        // Continue with your test logic
        $this->assertSelectorTextContains('body', 'Alert accepted');
    }
}

Handling Confirm Dialogs

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

    // Trigger confirm dialog
    $crawler->filter('#delete-button')->click();

    // Get the alert and read its text
    $alert = $client->getWebDriver()->switchTo()->alert();
    $alertText = $alert->getText();

    // Assert the alert message
    $this->assertEquals('Are you sure you want to delete this item?', $alertText);

    // Accept or dismiss the confirm dialog
    $alert->accept(); // or $alert->dismiss();
}

Handling Prompt Dialogs

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

    // Trigger prompt dialog
    $crawler->filter('#name-input-button')->click();

    // Handle the prompt
    $alert = $client->getWebDriver()->switchTo()->alert();

    // Send text to the prompt
    $alert->sendKeys('John Doe');
    $alert->accept();

    // Verify the result
    $this->assertSelectorTextContains('#result', 'Hello, John Doe');
}

Handling Modal Dialogs

HTML-based modals require different approaches since they're part of the DOM rather than browser-native dialogs.

Waiting for Modal Appearance

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

    // Trigger modal
    $crawler->filter('#open-modal-button')->click();

    // Wait for modal to appear
    $client->waitFor('#modal-dialog');

    // Interact with modal content
    $modalContent = $crawler->filter('#modal-dialog .modal-body')->text();
    $this->assertStringContains('Modal content', $modalContent);

    // Close modal by clicking close button
    $crawler->filter('#modal-dialog .close-button')->click();

    // Wait for modal to disappear
    $client->waitForInvisibility('#modal-dialog');
}

Advanced Modal Handling with Custom Wait Conditions

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

    // Open modal
    $crawler->filter('#complex-modal-trigger')->click();

    // Wait for modal with custom condition
    $client->wait()->until(
        \Facebook\WebDriver\WebDriverExpectedConditions::and(
            \Facebook\WebDriver\WebDriverExpectedConditions::visibilityOfElementLocated(
                \Facebook\WebDriver\WebDriverBy::id('modal-overlay')
            ),
            \Facebook\WebDriver\WebDriverExpectedConditions::elementToBeClickable(
                \Facebook\WebDriver\WebDriverBy::cssSelector('#modal-overlay .submit-button')
            )
        )
    );

    // Fill form inside modal
    $client->executeScript('document.querySelector("#modal-form input[name=email]").value = "test@example.com"');

    // Submit modal form
    $crawler->filter('#modal-overlay .submit-button')->click();
}

Handling New Browser Windows

When dealing with popups that open new browser windows or tabs, you need to switch between browser contexts.

Basic Window Switching

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

    // Store original window handle
    $originalWindow = $client->getWebDriver()->getWindowHandle();

    // Click link that opens new window
    $crawler->filter('#open-popup-link')->click();

    // Get all window handles
    $windows = $client->getWebDriver()->getWindowHandles();

    // Switch to the new window (assuming it's the last one)
    foreach ($windows as $window) {
        if ($window !== $originalWindow) {
            $client->getWebDriver()->switchTo()->window($window);
            break;
        }
    }

    // Work with the popup window
    $popupTitle = $client->getTitle();
    $this->assertEquals('Popup Window Title', $popupTitle);

    // Close popup and switch back to original window
    $client->getWebDriver()->close();
    $client->getWebDriver()->switchTo()->window($originalWindow);
}

Managing Multiple Windows

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

    $originalWindow = $client->getWebDriver()->getWindowHandle();

    // Open multiple popups
    $crawler->filter('#open-popup-1')->click();
    $crawler->filter('#open-popup-2')->click();

    $allWindows = $client->getWebDriver()->getWindowHandles();

    // Process each popup window
    foreach ($allWindows as $window) {
        if ($window !== $originalWindow) {
            $client->getWebDriver()->switchTo()->window($window);

            // Extract data from popup
            $data = $crawler->filter('body')->text();

            // Close popup
            $client->getWebDriver()->close();
        }
    }

    // Return to original window
    $client->getWebDriver()->switchTo()->window($originalWindow);
}

Advanced Popup Handling Techniques

Automatic Alert Handling

You can set up automatic alert handling to prevent popups from blocking your automation:

public function testAutomaticAlertHandling()
{
    $client = static::createPantherClient([
        'browser' => static::CHROME,
        'external_base_uri' => 'https://example.com'
    ]);

    // Set up automatic alert acceptance
    $client->getWebDriver()->executeScript('
        window.originalAlert = window.alert;
        window.originalConfirm = window.confirm;
        window.originalPrompt = window.prompt;

        window.alert = function(msg) { return true; };
        window.confirm = function(msg) { return true; };
        window.prompt = function(msg, defaultText) { return defaultText || ""; };
    ');

    $crawler = $client->request('GET', '/page-with-many-alerts');

    // Navigate through the page without being blocked by alerts
    $crawler->filter('#proceed-button')->click();
}

Detecting Alert Presence

private function isAlertPresent($client): bool
{
    try {
        $client->getWebDriver()->switchTo()->alert();
        return true;
    } catch (\Facebook\WebDriver\Exception\NoAlertOpenException $e) {
        return false;
    }
}

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

    // Perform action that might trigger an alert
    $crawler->filter('#maybe-alert-button')->click();

    // Handle alert if present
    if ($this->isAlertPresent($client)) {
        $alert = $client->getWebDriver()->switchTo()->alert();
        $alertText = $alert->getText();
        $alert->accept();

        // Log or process alert information
        echo "Alert handled: " . $alertText;
    }
}

Browser Configuration for Popup Handling

Configuring Chrome Options

public function createCustomPantherClient()
{
    $options = [
        'browser' => static::CHROME,
        'chromedriver_arguments' => [
            '--disable-web-security',
            '--disable-popup-blocking',
            '--disable-default-apps',
            '--disable-extensions'
        ]
    ];

    return static::createPantherClient($options);
}

JavaScript Modal Examples

For HTML-based modals, you'll often need to work with JavaScript frameworks and custom modal implementations:

Bootstrap Modal Handling

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

    // Trigger Bootstrap modal
    $crawler->filter('[data-toggle="modal"]')->click();

    // Wait for modal to be visible
    $client->waitFor('.modal.show');

    // Interact with modal content
    $client->executeScript('
        document.querySelector(".modal.show .btn-primary").click();
    ');

    // Wait for modal to close
    $client->waitForInvisibility('.modal.show');
}

Custom Modal Detection

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

    // Trigger modal
    $crawler->filter('#custom-trigger')->click();

    // Wait for any overlay to appear
    $client->wait()->until(function ($driver) {
        $overlays = $driver->findElements(
            \Facebook\WebDriver\WebDriverBy::cssSelector('[style*="z-index"]:not([style*="display: none"])')
        );
        return count($overlays) > 0;
    });

    // Find and interact with the highest z-index element
    $highestOverlay = $client->executeScript('
        const elements = Array.from(document.querySelectorAll("*"));
        return elements.reduce((prev, current) => {
            const prevZ = parseInt(getComputedStyle(prev).zIndex) || 0;
            const currentZ = parseInt(getComputedStyle(current).zIndex) || 0;
            return currentZ > prevZ ? current : prev;
        });
    ');

    // Close the modal
    $client->executeScript('arguments[0].remove();', [$highestOverlay]);
}

Best Practices and Common Pitfalls

1. Always Wait for Elements

When dealing with modals and popups, always wait for elements to be visible or clickable before interacting with them. Similar to how the waitFor function works in Puppeteer, Panther provides waiting mechanisms.

// Bad: Immediate interaction
$crawler->filter('#modal-button')->click();

// Good: Wait for element
$client->waitFor('#modal-button');
$crawler->filter('#modal-button')->click();

2. Handle Exceptions Gracefully

try {
    $alert = $client->getWebDriver()->switchTo()->alert();
    $alert->accept();
} catch (\Facebook\WebDriver\Exception\NoAlertOpenException $e) {
    // Handle case where no alert is present
    $this->markTestSkipped('Expected alert not found');
}

3. Clean Up After Tests

Always ensure windows and alerts are properly closed to avoid affecting subsequent tests:

protected function tearDown(): void
{
    if ($this->client) {
        // Close any remaining windows except the main one
        $handles = $this->client->getWebDriver()->getWindowHandles();
        $mainWindow = $handles[0];

        foreach ($handles as $handle) {
            if ($handle !== $mainWindow) {
                $this->client->getWebDriver()->switchTo()->window($handle);
                $this->client->getWebDriver()->close();
            }
        }

        $this->client->getWebDriver()->switchTo()->window($mainWindow);
    }

    parent::tearDown();
}

Integration with Testing Frameworks

PHPUnit Integration

<?php

use PHPUnit\Framework\TestCase;
use Symfony\Component\Panther\PantherTestCase;

class PopupIntegrationTest extends PantherTestCase
{
    private $client;

    protected function setUp(): void
    {
        $this->client = static::createPantherClient();
    }

    /**
     * @dataProvider popupTestProvider
     */
    public function testVariousPopupTypes($url, $trigger, $expectedText)
    {
        $crawler = $this->client->request('GET', $url);
        $crawler->filter($trigger)->click();

        $this->handlePopupBasedOnType($expectedText);
    }

    public function popupTestProvider(): array
    {
        return [
            ['https://example.com/alert', '#alert-btn', 'Warning message'],
            ['https://example.com/confirm', '#confirm-btn', 'Delete confirmation'],
            ['https://example.com/prompt', '#prompt-btn', 'Enter name'],
        ];
    }

    private function handlePopupBasedOnType($expectedText)
    {
        if ($this->isAlertPresent($this->client)) {
            $alert = $this->client->getWebDriver()->switchTo()->alert();
            $this->assertStringContains($expectedText, $alert->getText());
            $alert->accept();
        } else {
            // Handle modal dialogs
            $this->client->waitFor('.modal');
            $modalText = $this->client->getCrawler()->filter('.modal-body')->text();
            $this->assertStringContains($expectedText, $modalText);
        }
    }
}

Common Anti-Bot Detection Scenarios

Many websites use popups as part of their anti-bot detection. Here's how to handle common scenarios:

CAPTCHA Popups

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

    // Check for CAPTCHA modal
    try {
        $client->waitFor('.captcha-modal', 5);

        // If CAPTCHA appears, you might need to:
        // 1. Use a CAPTCHA solving service
        // 2. Implement manual intervention
        // 3. Skip the test

        $this->markTestSkipped('CAPTCHA detected - manual intervention required');
    } catch (\Exception $e) {
        // No CAPTCHA, continue with normal flow
        $this->assertTrue(true, 'No CAPTCHA interference detected');
    }
}

Cookie Consent Popups

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

    // Handle cookie consent popup
    try {
        $client->waitFor('.cookie-consent', 3);
        $crawler->filter('.cookie-consent .accept-all')->click();
        $client->waitForInvisibility('.cookie-consent');
    } catch (\Exception $e) {
        // No cookie consent popup
    }

    // Continue with main scraping logic
    $data = $crawler->filter('.main-content')->text();
    $this->assertNotEmpty($data);
}

Performance Considerations

Timeout Management

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

    // Set shorter timeouts for popup detection
    $client->manage()->timeouts()->implicitlyWait(2);

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

    // Quick popup check with minimal waiting
    $hasPopup = false;
    try {
        $client->waitFor('.popup', 1); // Very short wait
        $hasPopup = true;
    } catch (\Exception $e) {
        // No popup found quickly
    }

    if ($hasPopup) {
        $crawler->filter('.popup .close')->click();
    }

    // Reset to normal timeouts
    $client->manage()->timeouts()->implicitlyWait(10);
}

Conclusion

Handling popup windows and alerts in Symfony Panther requires understanding the different types of popups and choosing the appropriate handling strategy. JavaScript alerts use the WebDriver alert API, while HTML modals require DOM interaction and waiting strategies. New browser windows need window handle management, and proper exception handling ensures robust automation scripts.

For more complex scenarios involving multiple page interactions, consider exploring how to handle pop-ups and modals in Puppeteer for additional techniques that can be adapted to Panther workflows.

By following these patterns and best practices, you can create reliable web scraping and testing scripts that gracefully handle various popup scenarios while maintaining code maintainability and test stability.

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