24
How To Execute JavaScript In Selenium PHP?
There are cases where test scenarios may fail unexpectedly with Selenium commands (e.g., click operation on the button web element does not result in a click even though the button is enabled). Such issues are more pertinent when creating an XHR request or when attempting to access another frame. For overcoming such issues, you can execute JavaScript in Selenium through the JavaScriptExecutor interface.
The Selenium WebDriver lets you execute synchronous and asynchronous JavaScript code in the context of the currently selected frame or window. By default, JavaScript executes synchronously. Though JavaScript can also have asynchronous code, it is generally single-threaded. This Selenium WebDriver PHP Tutorial focuses on how JavaScript can be executed in Selenium PHP.
If locating or performing the required operations on the web elements does not work as expected with locators like XPath, Name, etc., the JavaScriptExecutor interface can be used to tackle the problem. JavaScriptExecutor is an interface that is available for all the languages that support the Selenium framework, including PHP.
Since the JavaScriptExecutor interface can interact with the DOM elements, it can be used instead of Selenium WebDriver APIs like findElement and others. Along with handling tricky XPaths, this interface is also useful in finding hidden web elements on the page.
In Selenium PHP, JavaScriptExecutor can be used without importing additional classes in the code. JavaScriptExecutor in Selenium provides a mechanism to execute JavaScript in Selenium, thereby helping you avoid issues that you may otherwise encounter in scenarios where web locators are not working as expected. Here are some of the scenarios where the JavaScriptExecutor in Selenium interface is useful for handling synchronization in Selenium:
- Entering text without using the sendKeys method.
- Handling alerts and pop-up windows in Selenium.
- Fetch details about the web page, e.g., title, source, domain name, URL, etc.
- Navigating to pages using JavaScript.
- Getting the innerText of a web page in Selenium.
JavaScriptExecutor in the Selenium WebDriver interface for PHP provides “executeScript” & “executeAsyncScript” methods (or commands) using which developers can execute JavaScript code in a synchronous & asynchronous way.
JavaScriptExecutor in Selenium interface has two abstract methods, which are the same irrespective of the Selenium language binding (i.e., PHP, Python, Java, C#, etc.) being used. In this Selenium WebDriver PHP Tutorial, we focus on executing JavaScript in Selenium PHP.
Here are the two methods provided by the JavaScriptExecutor in the Selenium interface:
JavaScript is synchronous and provides features like callbacks, promises, etc., for incorporating asynchronous event-handling in the project. The executeScript method executes the JavaScript code in the context of the currently selected frame or window.
The script fragment that is used in the executeScript method is executed as the body of an anonymous function. In Selenium PHP, executeScript can be used effectively for operations such as clicking a web element on a web page, fetching information about the web page such as title, domain info, URL, and more.
Within the script, a document should be used when referring to the current page. You can also pass complicated arguments to the method. Local variables used in the script cannot be used outside the body of the script.
The script can return value using the return statement. Here is the list of data types that can be returned by the script:
- executeScript returns a WebElement for an HTML element
- Double is returned for a decimal number
- Long is returned for a non-decimal number
- In all other cases, a String is returned
- For an argument of type List, the script returns a List
The script can also take arguments that are a combination of different data types such as Boolean, String, WebElement, and more. An exception is thrown if the arguments do not meet the criteria described above.
The syntax of the executeScript method to execute JavaScript in Selenium in a synchronous manner:
$js_command = "JS command to be executed";
/* For example - To get the Page Title using JavaScript in Selenium */
/* $js_command = "return document.domain;"; */
$return_var = $driver->executeScript($js_command);
The executeAsyncScript command helps to execute an asynchronous piece of JavaScript code in the context of the currently selected window or frame. The primary difference between the executeScript and executeAsyncScript methods is that the script executed using the executeAsyncScript method should signal that it has finished execution by invoking a callback function.
It is important to note that Asynchronous JavaScript does not mean the same as multi-threaded, as JavaScript is generally single-threaded. Like its synchronous equivalent, the script fragment in executeAsyncScript also executes as the body of an anonymous function. Variables names can be added in the input value field, and the ‘return’ keyword is used for storing the return value.
Here are some of the common scenarios where asynchronous JavaScript can be useful:
- Performing a sleep in the browser under test
Asynchronous JavaScript should be used in scenarios where the application has to wait for a ‘certain’ duration before performing further action. This is where the executeAsyncScript method can be used for triggering sleep in the browser under test.
This can be achieved in Selenium PHP by opening the required web page in the browser and making the application wait for a ‘certain time duration’ (e.g., 5 seconds) before it can perform further action. This makes executeAsyncScript similar to executeScript (i.e., synchronous execution), as the script still waits for the existing command to complete before it can proceed to the next command.
- Synchronizing the test with an AJAX application
Consider a scenario where input data has to be fetched from the page on which Selenium web automation testing is being performed. Rather than fetching the data from the destination in a synchronous manner (using the executeScript method), it can be fetched via an AJAX request.
Opting for a synchronous fetch would delay the test execution as the main thread would be busy executing the fetch requests. On the other hand, async execution of the JavaScript code would help in executing the fetch requests more efficiently without blocking the main thread.
Once the fetch request is complete, a callback can be fired to indicate the completion of the request. This is where the executeAsyncScript method can be useful in synchronizing the test with an AJAX application.
- Injecting a XMLHttpRequest (XHR) and waiting for the results
All modern browsers support the XMLHttpRequest (XHR) object. The object is used for updating the contents on a web page by requesting data from the web server, that too behind the scenes (i.e., the end-user will not notice that the data is being fetched from the server).
The request for injecting the XMLHttpRequest while waiting for the results can be made by invoking the executeAsyncScript method.
Example of the executeAsyncScript method to execute JavaScript code in Selenium in an asynchronous manner:
$script_link = " JS command to be executed";
/* For example – Performing sleep in the browser under test */
/* $script_link = "window.setTimeout(arguments[arguments.length - 1], " . $asyncwaittime . ");"; */
$driver->executeAsyncScript($script_link);
Since the callback is always injected in the executed function as the last argument, it can be referenced as arguments[arguments.length – 1] as seen in the example snippet shown above.
For demonstrating the executeScript method in Selenium PHP, we consider the following test examples:
- Fetch the details about a web page using executeScript
The test URL is https://www.lambdatest.com/blog/, and the JavaScriptExecutor method should be used for getting the domain name, URL, and Window Title.
Implementation
<?php
require 'vendor/autoload.php';
use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
$GLOBALS['LT_USERNAME'] = "user-name";
# accessKey: AccessKey can be generated from automation dashboard or profile section
$GLOBALS['LT_APPKEY'] = "access-key";
class JS_Capture_DataTest extends TestCase
{
protected $webDriver;
public function build_browser_capabilities(){
/* $capabilities = DesiredCapabilities::chrome(); */
$capabilities = array(
"build" => "[PHP-1] Demonstration of executeScript method using Selenium PHP",
"name" => "[PHP-1] Demonstration of executeScript method using Selenium PHP",
"platform" => "Windows 10",
"browserName" => "Chrome",
"version" => "85.0"
);
return $capabilities;
}
public function setUp(): void
{
$url = "https://". $GLOBALS['LT_USERNAME'] .":" . $GLOBALS['LT_APPKEY'] ."@hub.lambdatest.com/wd/hub";
$capabilities = $this->build_browser_capabilities();
/* $this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities); */
$this->webDriver = RemoteWebDriver::create($url, $capabilities);
}
public function tearDown(): void
{
$this->webDriver->quit();
}
/*
* @test
*/
public function test_Wait_Sleep()
{
$test_url = "https://www.lambdatest.com/blog/";
$title = "LambdaTest | A Cross Browser Testing Blog";
$driver = $this->webDriver;
$driver->get($test_url);
$driver->manage()->window()->maximize();
$this->assertEquals($title, $driver->getTitle());
$js_domain_name = "return document.domain;";
$domain_name = $driver->executeScript($js_domain_name);
echo ("\nDomain name is " .$domain_name);
/* $js_command = "return document.URL;"; */
$js_command = "return window.location.href;";
$domain_url = $driver->executeScript($js_command);
echo ("\nURL is " .$domain_url);
$doc_title_command = "return document.title;";
$window_title = $driver->executeScript($doc_title_command);
echo ("\nWindow Title is " .$window_title);
}
}
?>
Code Walkthrough
Lines (19-25): As the tests are executed on LambdaTest’s cloud-based Selenium Grid, the capabilities are generated using LambdaTest Capabilities Generator.
$capabilities = array(
"build" => "[PHP-1] Demonstration of executeScript method using Selenium PHP",
"name" => "[PHP-1] Demonstration of executeScript method using Selenium PHP",
"platform" => "Windows 10",
"browserName" => "Chrome",
"version" => "85.0"
);
Lines (34): The URL containing the address of the Selenium Grid on LambdaTest [i.e., @hub.lambdatest.com/wd/hub] and the generated browser capabilities are passed to the create method.
$this->webDriver = RemoteWebDriver::create($url, $capabilities);
Lines (54 – 55): The document.domain command in JavaScript is used for retrieving the domain name (i.e., in our case, it is https://lambdatest.com).
The command is passed to the executeScript method for execution which in turn returns a String that signifies the domain name.
$js_domain_name = "return document.domain;";
$domain_name = $driver->executeScript($js_domain_name);
Lines (59 – 60): The window.location.href command in JavaScript is used for fetching the URL details of the page. In our case it is https://www.lambdatest.com/blog
Instead of window.location.href command, the document.URL command can also be used for retrieving the URL of the current page.
$js_command = "return window.location.href;";
$domain_url = $driver->executeScript($js_command);
Lines (63 – 64): For getting the window title, document.title command in JavaScript is passed to the executeScript method.
$doc_title_command = "return document.title;";
$window_title = $driver->executeScript($doc_title_command);
Execution
The PHPUnit framework will be available in the vendor\bin folder as we had downloaded the same using the composer command. Run the command vendor\bin\phpunit on the terminal for executing the test:
vendor\bin\phpunit tests\JS_Capture_DataTest.php
Here is the execution snapshot, which indicates that the required details of the web page were fetched successfully using the executeScript method.
- Using executeScript instead of Selenium WebDriver APIs (or methods)
The executeScript method can invoke multiple arguments like arguments[0], arguments[1], etc. In the example shown below, we have invoked the click method using the JavaScript command instead of using the traditional Selenium WebDriver API.
The test scenario is below:
- Navigate to the URL https://lambdatest.github.io/sample-todo-app/.
- Select the first two checkboxes.
- Send ‘Yey, Let’s add it to list’ to the textbox with id = sampletodotext.
- Click the Add Button and verify whether the text has been added or not.
Implementation
<?php
require 'vendor/autoload.php';
use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverKeys;
use Facebook\WebDriver\WebDriverBy;
$GLOBALS['LT_USERNAME'] = "user-name";
# accessKey: AccessKey can be generated from automation dashboard or profile section
$GLOBALS['LT_APPKEY'] = "access-key";
class JS_ExecuteScriptTest extends TestCase
{
protected $webDriver;
public function build_browser_capabilities(){
/* $capabilities = DesiredCapabilities::chrome(); */
$capabilities = array(
"build" => "[PHP-2] Use executeScript instead of traditional Selenium WebDriver APIs",
"name" => "[PHP-2] Use executeScript instead of traditional Selenium WebDriver APIs",
"platform" => "Windows 10",
"browserName" => "Chrome",
"version" => "85.0"
);
return $capabilities;
}
public function setUp(): void
{
$url = "https://". $GLOBALS['LT_USERNAME'] .":" . $GLOBALS['LT_APPKEY'] ."@hub.lambdatest.com/wd/hub";
$capabilities = $this->build_browser_capabilities();
/* $this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities); */
$this->webDriver = RemoteWebDriver::create($url, $capabilities);
}
public function tearDown(): void
{
$this->webDriver->quit();
}
/*
* @test
*/
public function test_Wait_Sleep()
{
$test_url = "https://lambdatest.github.io/sample-todo-app/";
$title = "Sample page - lambdatest.com";
$itemName = 'Yey, Lets add it to list';
$driver = $this->webDriver;
$driver->get($test_url);
$driver->manage()->window()->maximize();
$elementli1 = $driver->findElements(WebDriverBy::name("li1"));
$driver->executeScript('arguments[0].click();',$elementli1);
$elementli2 = $driver->findElements(WebDriverBy::name("li2"));
$driver->executeScript('arguments[0].click();',$elementli2);
$elementtodotext = $driver->findElement(WebDriverBy::id("sampletodotext"));
$elementtodotext->sendKeys($itemName);
/* This did not work, hence, we used sendKeys method instead of the executeScript method */
/*
$elementtodotext = $driver->findElements(WebDriverBy::id("sampletodotext"));
$new_item_link = "arguments[0].value='" .$itemName. "';";
$driver->executeScript($new_item_link,$elementtodotext);
*/
sleep(2);
$addbutton = $driver->findElements(WebDriverBy::id("addbutton"));
$driver->executeScript('arguments[0].click();',$addbutton);
$driver->wait(10, 500)->until(function($driver) {
$elements = $driver->findElements(WebDriverBy::cssSelector("[class='list-unstyled'] li:nth-child(6) span"));
echo "\n New entry count " . count($elements);
$this->assertEquals(1, count($elements));
return count($elements) > 0;
}
);
}
}
?>
Code Walkthrough
Lines (55 – 59): The web element with name ‘li1’ and ‘li2’ are located using the name property. For performing a click operation on the WebElement, the executeScript method takes the reference of the element as arguments[0] along with the method to perform on the element [i.e., In this case, it is click()].
$elementli1 = $driver->findElements(WebDriverBy::name("li1"));
$driver->executeScript('arguments[0].click();',$elementli1);
$elementli2 = $driver->findElements(WebDriverBy::name("li2"));
$driver->executeScript('arguments[0].click();',$elementli2);
In the snippet shown above, $elementli1 is arguments[0], and the click method is applied to that element. The same sequence is also applicable to $elementli2.
Lines(61 – 62): A new item is added to the ToDo list by locating the element sampletodotext by ID. The content of the item to be added is sent to the element sampletodotext (which is a text box) using the sendKeys method.
$elementtodotext = $driver->findElement(WebDriverBy::id("sampletodotext"));
$elementtodotext->sendKeys($itemName);
You can also add content to the element with ID – sampletodotext by setting the value using the JavaScriptExecutor method. The issue with this approach was that through the value was being assigned to the sampletodotext, the new entry (in the ToDo list) used to show up as Blank.
Hence, we used the traditional Selenium method (i.e. sendKeys) instead of the executeScript method.
$elementtodotext = $driver->findElements(WebDriverBy::id("sampletodotext"));
$new_item_link = "arguments[0].value='" .$itemName. "';";
$driver->executeScript($new_item_link,$elementtodotext);
Lines (73 – 74): The element addbutton is located using ID. The click() method is performed on the addbutton for adding the newly added item to the list.
$addbutton = $driver->findElements(WebDriverBy::id("addbutton"));
$driver->executeScript('arguments[0].click();',$addbutton);
Lines (76 – 82): An explicit wait of 10 seconds (with condition checking frequency set at 500 ms) is triggered to check if the new item is successfully added to the list. Assert is raised if the item count is zero.
$driver->wait(10, 500)->until(function($driver) {
$elements = $driver->findElements(WebDriverBy::cssSelector("[class='list-unstyled'] li:nth-child(6) span"));
echo "\n New entry count " . count($elements);
$this->assertEquals(1, count($elements));
return count($elements) > 0;
}
);
Execution
Run the following command on the terminal to execute the test:
vendor\bin\phpunit tests\JS_ExecuteScriptTest.php
As seen in the execution snapshot, the first two items are checked, and a new item has been successfully added to the list.
The overall test scenario is the same as the one using in the demonstration of the executeScript method used in Selenium WebDriver methods. The executeScript method blocks further actions that are performed on the browser (as it executes synchronously) whereas, in executeAsyncScript, a callback is sent to the server and is executed once the script is done. This essentially means that every instruction in the script is executed by the browser and not at the server.
Before the executeAsyncScript method is used to execute JavaScript in Selenium, it should be noted that async in executeAsyncScript indicates the mechanism used for signaling the completion of execution (i.e., via a callback). As stated in this StackOverflow link, the JavaScript code is still executed asynchronously with respect to the Selenium WebDriver.
For demonstrating the usage of the executeAsyncScript method that lets you execute an asynchronous piece of JavaScript, we perform a sleep of 5 seconds in the browser under test.
Implementation
<?php
require 'vendor/autoload.php';
use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
$GLOBALS['LT_USERNAME'] = "user-name";
# accessKey: AccessKey can be generated from automation dashboard or profile section
$GLOBALS['LT_APPKEY'] = "access-key";
class JS_AsyncExecuteScriptTest extends TestCase
{
protected $webDriver;
public function build_browser_capabilities(){
/* $capabilities = DesiredCapabilities::chrome(); */
$capabilities = array(
"build" => "[PHP-3] Demonstration of executeAsyncScript method using Selenium PHP",
"name" => "[PHP-3] Demonstration of executeAsyncScript method using Selenium PHP",
"platform" => "Windows 10",
"browserName" => "Chrome",
"version" => "85.0"
);
return $capabilities;
}
public function setUp(): void
{
$url = "https://". $GLOBALS['LT_USERNAME'] .":" . $GLOBALS['LT_APPKEY'] ."@hub.lambdatest.com/wd/hub";
$capabilities = $this->build_browser_capabilities();
/* $this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities); */
$this->webDriver = RemoteWebDriver::create($url, $capabilities);
}
public function tearDown(): void
{
$this->webDriver->quit();
}
/*
* @test
*/
public function test_Wait_Sleep()
{
/* Set the script wait time to 5 seconds */
$asyncwaittime = 5000;
$test_url = "https://lambdatest.github.io/sample-todo-app/";
$title = "Sample page - lambdatest.com";
$itemName = 'Yey, Lets add it to list';
$driver = $this->webDriver;
$driver->get($test_url);
$driver->manage()->window()->maximize();
$this->assertEquals($title, $driver->getTitle());
$timeouts = $driver->manage()->timeouts();
$timeouts->setScriptTimeout(10);
$elementli1 = $driver->findElements(WebDriverBy::name("li1"));
$driver->executeScript('arguments[0].click();',$elementli1);
$elementli2 = $driver->findElements(WebDriverBy::name("li2"));
$driver->executeScript('arguments[0].click();',$elementli2);
$new_element = $this->webDriver->findElement(WebDriverBy::id("sampletodotext"));
$new_element->sendKeys($itemName);
$addbutton = $driver->findElements(WebDriverBy::id("addbutton"));
$driver->executeScript('arguments[0].click();',$addbutton);
/* Log the start time */
$start_time = microtime(true);
$script_link = "window.setTimeout(arguments[arguments.length - 1], " . $asyncwaittime . ");";
$driver->executeAsyncScript($script_link);
/* Log the end time */
$end_time = microtime(true);
$exec_time = $end_time - $start_time;
echo "\nExecution time = " . $exec_time;
echo("\n");
}
}
?>
Code Walkthrough
Lines (74 – 76): The window.setTimeout method in JavaScript is executed in async mode to wait for a specified period (i.e., 5 seconds).
/* Log the start time */
$start_time = microtime(true);
$script_link = "window.setTimeout(arguments[arguments.length - 1], " . $asyncwaittime . ");";
$driver->executeAsyncScript($script_link);
The execution time is logged by starting the timer before the executeAsyncScript method is triggered and stopped after it has been executed.
The important point to note is that the next function following the executeAsyncScript method executes after $asyncwaittime (i.e., after 5+ seconds), thereby blocking the Selenium WebDriver control flow until the operation in executeAsyncScript is completed.
Execution
Run the following command on the terminal to execute the test:
vendor\bin\phpunit tests\ JS_AsyncExecuteScriptTest.php
Here is the execution snapshot, which indicates that the application waits for a period of 5 seconds (i.e., $asyncwaittime) before further action is performed.
In this Selenium WebDriver PHP Tutorial, we had a detailed look to execute JavaScript in Selenium using the executeScript and executeAsyncScript methods. JavaScript is generally single-threaded and executes in a synchronous manner. Asynchronous event handling can be realized using the executeAsyncScript method offered by the JavaScriptExecutor in the Selenium interface. Even when the executeAsyncScript method is used, the execution is not asynchronous since the Selenium WebDriver control flow is blocked until the execution of the async method.
24