Yes, Symfony Panther supports executing custom JavaScript within web pages through its WebDriver integration. This capability allows you to interact with dynamic content, manipulate the DOM, and access browser APIs programmatically.
JavaScript Execution Methods
Symfony Panther provides two main methods for JavaScript execution:
executeScript()
- For synchronous JavaScript execution that returns immediatelyexecuteAsyncScript()
- For asynchronous operations involving callbacks, promises, or timers
Basic JavaScript Execution
Here's a simple example demonstrating both synchronous and asynchronous JavaScript execution:
<?php
use Symfony\Component\Panther\PantherTestCase;
class JavaScriptExecutionExample extends PantherTestCase
{
public function executeCustomJavaScript()
{
$client = static::createPantherClient();
$client->request('GET', 'https://example.com');
// Synchronous JavaScript execution
$pageTitle = $client->executeScript('return document.title;');
echo "Page title: " . $pageTitle . "\n";
// Get page dimensions
$dimensions = $client->executeScript('
return {
width: window.innerWidth,
height: window.innerHeight,
scrollHeight: document.body.scrollHeight
};
');
// Asynchronous JavaScript with callback
$delayedResult = $client->executeAsyncScript('
var callback = arguments[0];
setTimeout(function() {
callback("Async operation completed");
}, 1000);
');
}
}
Practical Use Cases
1. Manipulating DOM Elements
// Scroll to bottom of page
$client->executeScript('window.scrollTo(0, document.body.scrollHeight);');
// Click hidden elements
$client->executeScript('
document.querySelector(".hidden-button").click();
');
// Modify element attributes
$client->executeScript('
document.querySelector("#target-element").setAttribute("data-processed", "true");
');
2. Handling Dynamic Content
// Wait for AJAX content to load
$content = $client->executeAsyncScript('
var callback = arguments[0];
var checkContent = function() {
var element = document.querySelector(".dynamic-content");
if (element && element.textContent.trim() !== "") {
callback(element.textContent);
} else {
setTimeout(checkContent, 100);
}
};
checkContent();
');
3. Extracting Complex Data
// Extract structured data from the page
$data = $client->executeScript('
var products = [];
document.querySelectorAll(".product-item").forEach(function(item) {
products.push({
name: item.querySelector(".product-name").textContent,
price: item.querySelector(".product-price").textContent,
url: item.querySelector("a").href
});
});
return products;
');
4. Triggering Events
// Trigger custom events
$client->executeScript('
var event = new CustomEvent("myCustomEvent", {
detail: { message: "Hello from Panther" }
});
document.dispatchEvent(event);
');
// Simulate user interactions
$client->executeScript('
var element = document.querySelector("#interactive-element");
element.dispatchEvent(new MouseEvent("mouseover", {
bubbles: true,
cancelable: true
}));
');
Working with Promises and Modern JavaScript
For modern JavaScript features, you can use async/await within your scripts:
$result = $client->executeAsyncScript('
var callback = arguments[0];
(async function() {
try {
const response = await fetch("/api/data");
const data = await response.json();
callback(data);
} catch (error) {
callback({ error: error.message });
}
})();
');
Error Handling
Always include proper error handling when executing JavaScript:
try {
$result = $client->executeScript('
try {
return document.querySelector("#non-existent").textContent;
} catch (error) {
return { error: error.message };
}
');
if (is_array($result) && isset($result['error'])) {
throw new Exception("JavaScript error: " . $result['error']);
}
} catch (Exception $e) {
echo "Execution failed: " . $e->getMessage();
}
Best Practices
- Return Values: Always use
return
statements for synchronous scripts to get results back in PHP - Callback Pattern: For async scripts, use the callback function (first argument) to return results
- Error Handling: Wrap JavaScript code in try-catch blocks to handle runtime errors gracefully
- Performance: Minimize JavaScript execution time to avoid timeouts
- Browser Compatibility: Test your JavaScript across different browser drivers (Chrome, Firefox)
Limitations and Considerations
- JavaScript execution context is limited to the current page
- Large data transfers between JavaScript and PHP may impact performance
- Browser-specific JavaScript APIs may not work consistently across different WebDriver implementations
- Always validate and sanitize data returned from JavaScript execution
JavaScript execution with Symfony Panther opens up powerful possibilities for web scraping and browser automation, allowing you to handle complex dynamic websites that traditional HTTP-based scraping cannot manage effectively.