How do I simulate mobile device browsing with Symfony Panther?
Symfony Panther provides several methods to simulate mobile device browsing, enabling you to scrape mobile-optimized websites and test responsive designs. This comprehensive guide covers viewport configuration, user agent settings, touch event simulation, and advanced mobile emulation techniques.
Setting Up Mobile Device Emulation
Basic Mobile Viewport Configuration
The most fundamental aspect of mobile simulation is setting the correct viewport dimensions. Here's how to configure a mobile viewport:
<?php
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;
class MobileScrapingTest extends PantherTestCase
{
public function testMobileViewport()
{
$client = static::createPantherClient([
'browser' => static::CHROME,
'chromeArguments' => [
'--window-size=375,667', // iPhone SE dimensions
'--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1'
]
]);
$crawler = $client->request('GET', 'https://example.com');
// Now the page will render in mobile view
$mobileMenu = $crawler->filter('.mobile-menu');
return $client;
}
}
Device-Specific Presets
For common devices, you can create preset configurations:
<?php
class MobileDevicePresets
{
public static function getIPhoneConfig(): array
{
return [
'browser' => PantherTestCase::CHROME,
'chromeArguments' => [
'--window-size=375,812', // iPhone X/11/12/13
'--device-scale-factor=3',
'--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
'--touch-events=enabled'
]
];
}
public static function getAndroidConfig(): array
{
return [
'browser' => PantherTestCase::CHROME,
'chromeArguments' => [
'--window-size=412,869', // Pixel 5
'--device-scale-factor=2.75',
'--user-agent=Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36',
'--touch-events=enabled'
]
];
}
public static function getTabletConfig(): array
{
return [
'browser' => PantherTestCase::CHROME,
'chromeArguments' => [
'--window-size=768,1024', // iPad
'--device-scale-factor=2',
'--user-agent=Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1',
'--touch-events=enabled'
]
];
}
}
Advanced Mobile Simulation
Dynamic Viewport Adjustment
You can dynamically change the viewport during scraping to test different screen sizes:
<?php
use Symfony\Component\Panther\PantherTestCase;
class ResponsiveScrapingTest extends PantherTestCase
{
public function testResponsiveBreakpoints()
{
$client = static::createPantherClient();
$client->request('GET', 'https://example.com');
// Test mobile breakpoint
$client->executeScript('window.resizeTo(375, 667);');
$this->assertMobileLayout($client);
// Test tablet breakpoint
$client->executeScript('window.resizeTo(768, 1024);');
$this->assertTabletLayout($client);
// Test desktop breakpoint
$client->executeScript('window.resizeTo(1920, 1080);');
$this->assertDesktopLayout($client);
}
private function assertMobileLayout(Client $client): void
{
$crawler = $client->getCrawler();
$this->assertGreaterThan(0, $crawler->filter('.mobile-menu')->count());
$this->assertEquals(0, $crawler->filter('.desktop-menu')->count());
}
}
Touch Event Simulation
For websites that respond to touch events, you can simulate touch interactions:
<?php
use Symfony\Component\Panther\PantherTestCase;
class TouchInteractionTest extends PantherTestCase
{
public function testTouchEvents()
{
$client = static::createPantherClient(
MobileDevicePresets::getIPhoneConfig()
);
$crawler = $client->request('GET', 'https://example.com');
// Simulate touch tap
$element = $crawler->filter('.touch-button')->first();
$client->executeScript("
var element = arguments[0];
var touchEvent = new TouchEvent('touchstart', {
bubbles: true,
cancelable: true,
touches: [{
clientX: element.getBoundingClientRect().left + 10,
clientY: element.getBoundingClientRect().top + 10,
target: element
}]
});
element.dispatchEvent(touchEvent);
", [$element->getElement(0)]);
// Simulate swipe gesture
$this->simulateSwipe($client, $element, 'left');
}
private function simulateSwipe(Client $client, $element, string $direction): void
{
$directions = [
'left' => ['startX' => 200, 'endX' => 50, 'startY' => 100, 'endY' => 100],
'right' => ['startX' => 50, 'endX' => 200, 'startY' => 100, 'endY' => 100],
'up' => ['startX' => 100, 'endX' => 100, 'startY' => 200, 'endY' => 50],
'down' => ['startX' => 100, 'endX' => 100, 'startY' => 50, 'endY' => 200]
];
$coords = $directions[$direction];
$client->executeScript("
var element = arguments[0];
var rect = element.getBoundingClientRect();
// Touch start
var touchStart = new TouchEvent('touchstart', {
bubbles: true,
cancelable: true,
touches: [{
clientX: rect.left + {$coords['startX']},
clientY: rect.top + {$coords['startY']},
target: element
}]
});
// Touch move
var touchMove = new TouchEvent('touchmove', {
bubbles: true,
cancelable: true,
touches: [{
clientX: rect.left + {$coords['endX']},
clientY: rect.top + {$coords['endY']},
target: element
}]
});
// Touch end
var touchEnd = new TouchEvent('touchend', {
bubbles: true,
cancelable: true
});
element.dispatchEvent(touchStart);
setTimeout(() => element.dispatchEvent(touchMove), 50);
setTimeout(() => element.dispatchEvent(touchEnd), 100);
", [$element->getElement(0)]);
}
}
Network Conditions Simulation
Mobile devices often have slower network connections. You can simulate these conditions:
<?php
use Symfony\Component\Panther\PantherTestCase;
class NetworkSimulationTest extends PantherTestCase
{
public function testSlowNetworkConditions()
{
$client = static::createPantherClient([
'browser' => static::CHROME,
'chromeArguments' => [
'--window-size=375,667',
'--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15',
'--force-device-scale-factor=2'
]
]);
// Enable network throttling
$client->executeScript("
chrome.runtime.sendMessage({
type: 'SET_NETWORK_CONDITIONS',
conditions: {
offline: false,
downloadThroughput: 150 * 1024, // 150 KB/s (3G speed)
uploadThroughput: 75 * 1024, // 75 KB/s
latency: 300 // 300ms latency
}
});
");
$crawler = $client->request('GET', 'https://example.com');
// Test how the page behaves under slow network conditions
$this->assertPageLoadsGracefully($client);
}
private function assertPageLoadsGracefully(Client $client): void
{
// Wait for critical content to load
$client->waitFor('.main-content');
// Check that loading indicators are present
$crawler = $client->getCrawler();
$loadingElements = $crawler->filter('.loading, .spinner, .skeleton');
// Verify progressive loading behavior
$this->assertGreaterThanOrEqual(0, $loadingElements->count());
}
}
Orientation and Device Features
Simulating Device Orientation
Test how your target website responds to orientation changes:
<?php
class OrientationTest extends PantherTestCase
{
public function testOrientationChanges()
{
$client = static::createPantherClient(
MobileDevicePresets::getIPhoneConfig()
);
$crawler = $client->request('GET', 'https://example.com');
// Portrait mode (default)
$this->assertPortraitLayout($client);
// Switch to landscape
$client->executeScript('window.resizeTo(812, 375);'); // Swap dimensions
$client->executeScript("
window.dispatchEvent(new Event('orientationchange'));
screen.orientation.angle = 90;
");
// Wait for layout to adjust
$client->waitFor('.landscape-layout', 3);
$this->assertLandscapeLayout($client);
}
private function assertPortraitLayout(Client $client): void
{
$crawler = $client->getCrawler();
$this->assertGreaterThan(0, $crawler->filter('.portrait-layout')->count());
}
private function assertLandscapeLayout(Client $client): void
{
$crawler = $client->getCrawler();
$this->assertGreaterThan(0, $crawler->filter('.landscape-layout')->count());
}
}
Geolocation Simulation
For location-based websites, you can mock geolocation:
<?php
class GeolocationTest extends PantherTestCase
{
public function testGeolocationFeatures()
{
$client = static::createPantherClient(
MobileDevicePresets::getIPhoneConfig()
);
// Mock geolocation before loading the page
$client->executeScript("
navigator.geolocation.getCurrentPosition = function(success, error) {
success({
coords: {
latitude: 40.7128,
longitude: -74.0060,
accuracy: 10
},
timestamp: Date.now()
});
};
");
$crawler = $client->request('GET', 'https://example.com/location-aware-page');
// Test location-based content
$client->waitFor('.location-content');
$locationElement = $crawler->filter('.current-location');
$this->assertStringContains('New York', $locationElement->text());
}
}
Performance Optimization for Mobile Scraping
Memory Management
Mobile simulation can be resource-intensive. Here's how to optimize performance:
<?php
class OptimizedMobileScrapingTest extends PantherTestCase
{
private static $sharedClient;
public static function setUpBeforeClass(): void
{
// Use a shared client to reduce memory overhead
self::$sharedClient = static::createPantherClient([
'browser' => static::CHROME,
'chromeArguments' => [
'--window-size=375,667',
'--disable-dev-shm-usage',
'--no-sandbox',
'--disable-gpu',
'--memory-pressure-off',
'--max_old_space_size=4096'
]
]);
}
public function testMultipleMobilePages()
{
$urls = [
'https://example.com/mobile-page-1',
'https://example.com/mobile-page-2',
'https://example.com/mobile-page-3'
];
foreach ($urls as $url) {
// Clear previous page data
self::$sharedClient->executeScript('
if (window.gc) {
window.gc();
}
performance.clearResourceTimings();
');
$crawler = self::$sharedClient->request('GET', $url);
$this->processMobilePage($crawler);
// Clear DOM references
self::$sharedClient->executeScript('
document.querySelectorAll("*").forEach(el => {
el.innerHTML = "";
});
');
}
}
public static function tearDownAfterClass(): void
{
if (self::$sharedClient) {
self::$sharedClient->quit();
}
}
}
Integration with Testing Frameworks
PHPUnit Integration
Symfony Panther integrates seamlessly with PHPUnit for mobile testing workflows:
<?php
use PHPUnit\Framework\TestCase;
use Symfony\Component\Panther\PantherTestCase;
class MobileWebScrapingTest extends PantherTestCase
{
/**
* @dataProvider mobileDeviceProvider
*/
public function testMobileCompatibility(array $deviceConfig, string $expectedLayout)
{
$client = static::createPantherClient($deviceConfig);
$crawler = $client->request('GET', 'https://example.com');
$this->assertPageHasLayout($crawler, $expectedLayout);
$this->assertMobileOptimizationsPresent($crawler);
}
public function mobileDeviceProvider(): array
{
return [
'iPhone' => [MobileDevicePresets::getIPhoneConfig(), 'mobile'],
'Android' => [MobileDevicePresets::getAndroidConfig(), 'mobile'],
'Tablet' => [MobileDevicePresets::getTabletConfig(), 'tablet']
];
}
private function assertMobileOptimizationsPresent($crawler): void
{
// Check for mobile-specific elements
$this->assertGreaterThan(0, $crawler->filter('meta[name="viewport"]')->count());
$this->assertGreaterThan(0, $crawler->filter('.mobile-optimized')->count());
// Verify touch-friendly button sizes
$buttons = $crawler->filter('button, .btn');
$buttons->each(function ($button) {
$height = $button->attr('offsetHeight');
$this->assertGreaterThanOrEqual(44, $height, 'Button should be at least 44px for touch');
});
}
}
Troubleshooting Common Issues
User Agent Detection Problems
Some websites have sophisticated mobile detection. Here's how to handle edge cases:
<?php
class RobustMobileSimulation extends PantherTestCase
{
public function testAdvancedMobileDetection()
{
$client = static::createPantherClient([
'browser' => static::CHROME,
'chromeArguments' => [
'--window-size=375,667',
'--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1'
]
]);
// Override additional mobile detection methods
$client->executeScript("
// Override screen properties
Object.defineProperty(screen, 'width', { value: 375 });
Object.defineProperty(screen, 'height', { value: 667 });
Object.defineProperty(screen, 'availWidth', { value: 375 });
Object.defineProperty(screen, 'availHeight', { value: 647 });
// Override navigator properties
Object.defineProperty(navigator, 'maxTouchPoints', { value: 5 });
Object.defineProperty(navigator, 'msMaxTouchPoints', { value: 5 });
// Override CSS media queries
window.matchMedia = function(query) {
if (query.includes('hover: none')) return { matches: true };
if (query.includes('pointer: coarse')) return { matches: true };
if (query.includes('max-width: 768px')) return { matches: true };
return { matches: false };
};
");
$crawler = $client->request('GET', 'https://example.com');
$this->assertMobileVersionLoaded($crawler);
}
}
Best Practices and Recommendations
1. Device Testing Strategy
- Test multiple device sizes and resolutions
- Include both iOS and Android user agents
- Test portrait and landscape orientations
2. Performance Considerations
- Use shared client instances when possible
- Clear browser cache between tests
- Monitor memory usage during long scraping sessions
3. Network Simulation
- Test with various network speeds
- Simulate offline conditions
- Consider timeout adjustments for slower connections
4. Cross-Platform Compatibility
- Verify functionality across different mobile browsers
- Test on actual devices when possible
- Use viewport meta tag simulation for accurate rendering
Conclusion
Symfony Panther provides comprehensive mobile device simulation capabilities for web scraping and testing. By combining viewport configuration, user agent settings, touch event simulation, and network condition emulation, you can create robust mobile scraping solutions. Remember to optimize for performance and test across multiple device configurations to ensure reliable results.
For handling dynamic content that loads after page navigation, consider implementing proper waiting strategies to ensure elements are fully rendered before interaction.