Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

On This Page
Optimize your testing process with comprehensive PHPUnit coverage reports in HTML and XML.

Himanshu Sheth
December 25, 2025
Code coverage is a vital measure for describing how the source implementation is tested by the test code (or test suite). It is one of the critical factors for ensuring the effectiveness of the code. PHPUnit, a popular test framework in PHP, also provides different ways for generating PHPUnit coverage report in HTML and XML.

Like me, you would have also come across many scenarios where the quality team comes back regarding insufficient code coverage since lower code coverage could be catastrophic for the product. As a Selenium PHP automation tester, it is essential to leverage the PHPUnit framework features for generating PHPUnit coverage report in HTML and XML.
By the end of this blog, you would be comfortable generating code coverage reports using the PHPUnit framework with Selenium.
Xdebug, the PHP debugger and profiler tool, was initially presented by Derick Rethans in 2003. At that time, he introduced code coverage information collection to the PHP community for the first time.
php-code-coverage, which was later spun out from PHPUnit as a reusable component, leverages the code coverage functionality provided by Xdebug or PCOV(code coverage driver for PHP). The PHPUnit framework provides options for generating reports and log files in widely-used formats like HTML and XML. The framework lets you facilitate code coverage information in formats such as Clover, Crap4J, Text, and PHP.

PHPUnit code coverage report information can also be printed on STDOUT, avoiding the need for exporting the results in a detailed report. This is not a feasible option to consider for large automation projects, as code coverage information will not be available for access to different project stakeholders.
Xdebug, which is an extension for PHP, has been in development since 2012. The latest version of Xdebug is 3.0.2, which was released on Sep 2, 2020. These are the primary features of Xdebug that aids in debugging and development:
var_dump() function of PHP.It also provides functionality that does the recording of every function call and assignment to the disk.
Xdebug is not available by default with the PHP installer. The Xdebug feature support has to be enabled in the PHP configuration file (php.ini).
For installing the PHPUnit framework, create a composer.json file for the project:
{
"require":{
"php":">=7.1",
"phpunit/phpunit-selenium": "*",
"php-webdriver/webdriver":"1.8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
}
}
Run the command composer require and press ‘Enter’ twice for installing the PHPUnit framework. With this, the PHPUnit framework with version 9.3 is installed.
As seen in the terminal screenshot below, Xdebug (or alternate code coverage driver) is not available in the framework. Hence, trying to generate a PHPUnit coverage report in HTML by invoking the command vendor\bin\phpunit --coverage-html < directory > gives a warning ‘No code coverage driver available.’

Alternately, you can redirect the console output to some text file by running the command php –i > <some_text_file> and paste the text file’s contents in https://xdebug.org/wizard, to view the PHPUnit coverage report in HTML. As seen in the ‘PHP Info Summary,’ Xdebug is not installed on our machine:

For enabling support for Xdebug in the framework, download the appropriate DLL as mentioned in the ‘Instructions’ section in https://xdebug.org/wizard.

We performed the following operations for installing Xdebug for PHP on Windows:

c:\PHP\php.ini by adding the entry of Xdebug DLL in zend_extension
With this, the Xdebug support is enabled. To confirm, execute the command php –i > < some_text_file_2 > and paste the contents of the text file in https://xdebug.org/wizard. As seen in the ‘PHP Info Summary,’ it is confirmed that Xdebug support is enabled for PHPUnit installed on our machine:

The execution of command vendor\bin\phpunit --coverage-html <directory> on the terminal now indicates that ‘code coverage driver is available’.
Unlike Xdebug, which provides features for debugging, code coverage reporting, etc., PCOV is just a self-contained code coverage compatible driver for PHP7 (and above).
The PCOV project is hosted on GitHub.
For installing PCOV for PHP, perform the following steps:


We also copied the Program Debugger Database file for PCOV to the ‘dev’ folder present in the PHP installation directory.

extension=pcov
Though additional modifications are recommended in the official link of PCOV, we commented on those entries in php.ini, as we witnessed issues when using php-code-coverage with PCOV.
Shown below are the final settings for using PCOV for PHPUnit code coverage report:

{
"require":{
"php":">=7.1",
"phpunit/phpunit-selenium": "*",
"php-webdriver/webdriver":"1.8.0"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"pcov/clobber": "^2.0"
}
}
Run the command composer require on the terminal and press ‘Enter’ two times to install the PCOV package. As seen below, the PCOV package for the project was installed successfully.

When PCOV is enabled with the addition of pcov.enabled=1 in the PHP configuration file (php.ini), interoperability with Xdebug is not possible. When PCOV is disabled using the configuration pcov.enabled=0, code runs at full speed, and Xdebug can also be loaded.
PCOV support was merged in PHPUnit 8. If you are using the older versions of PHPUnit (i.e., 5, 6, or 7), the developer of PCOV has provided a package that can clobber the Xdebug driver in the earlier versions of PHPUnit.
composer require pcov/clobber, installs the drivers for PCOV in the vendor directory, and installs vendor\bin\pcov.vendor/bin/pcov clobber clobbers the Xdebug driver in the current directory.vendor/bin/pcov clobber unclobbers the Xdebug driver in the current directory.Apart from installing the PCOV package, we would not require the other two commands, as we are using PHPUnit 9.3 for demonstration. As mentioned earlier, we have commented on the options provided by PCOV, even when php-code-coverage uses PCOV.

The php-code-coverage module is spun out from PHPUnit and uses Xdebug & PCOV to provide code coverage information for Selenium PHP tests. Based on the configuration settings for Xdebug and PCOV in php.ini, php-code-coverage uses either of them to provide the user with useful detailed PHPUnit coverage report in HTML and XML.
The php-code-coverage package is available on GitHub. We would be using php-code-coverage with Xdebug and PCOV for collecting PHPUnit code coverage report information.
The php-code-coverage library can be added as a per-project dependency using the Composer module. Run the following command on the terminal to install the php-code-coverage library for your project:
composer require phpunit/php-code-coverage
A better option is to add the library requirement in the existing composer.json of the project. We modify the project composer.json by adding this extra request for installing php-code-coverage-library:
{
"require":{
"php":">=7.1",
"phpunit/phpunit-selenium": "*",
"php-webdriver/webdriver":"1.8.0",
"symfony/symfony":"4.4"
},
"require-dev": {
"phpunit/php-code-coverage": "^9.1",
"phpunit/phpunit": "^9.3",
"pcov/clobber": "^2.0"
}
}
Run the command composer require on the terminal and press ‘Enter’ twice to install the phpunit/php-code-coverage library. On successful execution, the php-code-coverage library will be installed in < project_folder>\vendor\phpunit \php-code-coverage folder.

In order to use php-code-coverage for reporting on PHPUnit coverage report in HTML, necessary hooks have to be added to the existing test implementation (or test cases). The library provides flexibility to perform code coverage on a ‘per line’ basis, which is a good indicator for checking whether the test code covers every line in the implementation.
Here are the changes that have be added to the test code for phpunit/php-code-coverage library to generate test reports (Reference Source):
Changes for using php-code-coverage for report generation
<?php declare(strict_types=1);
use SebastianBergmannCodeCoverageCodeCoverage;
use SebastianBergmannCodeCoverageDriverDriver;
use SebastianBergmannCodeCoverageFilter;
use SebastianBergmannCodeCoverageReportHtmlFacade as HtmlReport;
/* These can be one-time operations. However, it is based on the source code and test code structure */
/*************** Start of one time operations **********************************/
$filter = new Filter;
/* Perform coverage at a directory level */
$filter->includeDirectory('path odirectory');
/* Perform coverage at a file level */
$filter->includeFile('path osource-fileile.php');
$driver = Driver::forLineCoverage($filter);
$coverage = new CodeCoverage($driver, $filter);
/*************** End of one time operations **********************************/
$coverage->start('<name of test>');
/* Test implementation goes here */
$coverage->stop();
(new HtmlReport)->process($coverage, 'path ocoverage report');
?>
Here is the rundown of the changes required in the test code:
use SebastianBergmannCodeCoverageCodeCoverage;
use SebastianBergmannCodeCoverageDriverDriver;
use SebastianBergmannCodeCoverageFilter;
use SebastianBergmannCodeCoverageReportHtmlFacade as HtmlReport;
If there are multiple files in a directory and code coverage has to be performed against a single file, it is recommended to use includeFile with input argument as the location of the PHP file against which tests are performed.
$filter = new Filter;
/* Perform coverage at a directory level */
$filter->includeDirectory('path odirectory');
/* Perform coverage at a file level */
$filter->includeFile('path osource-fileile.php');
$driver = Driver::forLineCoverage($filter);
$coverage = new CodeCoverage($driver, $filter);
$coverage->start('<name of test>');
/* Test implementation goes here */
$coverage->stop();
(new HtmlReport)->process($coverage, 'path ocoverage report');
The steps (2 – 4) can be one-time operations by creating global objects of $filter, $driver, and $coverage classes. The process operation mentioned in step (6) can be performed only once for generating PHPUnit coverage report in HTML of the executed test scenarios.
The php-code-coverage module, an extension of PHPUnit, can use XDebug or PCOV, which facilitates code coverage functionality. For demonstrating the usage of php-code-coverage for code coverage analysis and reporting, we consider two test scenarios that are executed against different browsers (and OS combinations).
The test scenarios are tested on a local Selenium Grid installed on our Windows 10 machine. We have used Selenium Grid 3 (i.e., 3.141.59) for performing the tests. You can use the following command to start the Selenium Grid server:
java -jar selenium-server-standalone-3.141.59.jar
Here are the test scenarios:
Test Scenario – 1 (Browser – Chrome, Platform – Windows 10)
Test Scenario – 2 (Browser – Firefox, Platform – Windows 10)
Directory Structure
We create separate ‘src’ and ‘tests’ folders where:
Apart from these folders, composer.json, and phpunit.xml.dist are kept in the parent folder. Here is the overall folder structure:

Figure 2 Parent Folder Structure

Figure 3 Parent Folder Structure

Figure 4 Parent Folder Structure

Implementation
a. composer.json
The first step is the creation of composer.json in the root project folder for downloading the dependencies for the Selenium PHP project under development.
{
"require":{
"php":">=7.1",
"phpunit/phpunit-selenium": "*",
"php-webdriver/webdriver":"1.8.0",
"symfony/symfony":"4.4",
"brianium/paratest": "dev-master"
},
"require-dev": {
"phpunit/php-code-coverage": "^9.1",
"phpunit/phpunit": "^9.3",
"pcov/clobber": "^2.0"
}
}
For validating the format of JSON can be validated by going to JSON Lint. Below are the projected dependencies that are listed in composer.json:
For installing the dependencies mentioned in composer.json, run the command composer.require on the terminal and press Enter twice to proceed with the installation of the specified packages:
composer require
Here is the screenshot of the terminal:

Upon completing the command execution, the file composer.lock and folder ‘vendor’ would be created in the project folder. Vendor contains all the downloaded packages and composer.lock contains detailed information on the dependencies.

Figure 5 Creation of vendor and composer.lock

Figure 6 php-code-coverage created by composer.json

Figure 7autoload.php in the vendor folder
As seen above, the composer module has generated a vendor\autoload.php file. The file should be included in the PHP files that contain the test implementation so that the classes provided by those libraries can be used without putting extra efforts (in the code).
b. src\ Todos.php
<?php
use PHPUnitFrameworkTestCase;
use FacebookWebDriverRemoteDesiredCapabilities;
use FacebookWebDriverRemoteRemoteWebDriver;
use FacebookWebDriverWebDriverBy;
class Todos
{
private $session;
/* Sample-ToDoText ID */
public $input;
public function __construct($session)
{
$this->session = $session;
$this->input = $session->findElement(WebDriverBy::id("addbutton"));
}
public function ToggleLi1()
{
$element1 = $this->session->findElement(WebDriverBy::name("li1"));
$element1->click();
}
public function ToggleLi2()
{
$element2 = $this->session->findElement(WebDriverBy::name("li2"));
$element2->click();
}
public function addTodo($itemName)
{
$element = $this->session->findElement(WebDriverBy::id("sampletodotext"));
$element->click();
$element->sendKeys($itemName . "
");
}
}
Here is the code walkthrough of the essential parts in the implementation:
public function __construct($session)
{
$this->session = $session;
$this->input = $session->findElement(WebDriverBy::id("addbutton"));
}
In ToggleLi1, the web element with the name ‘li1’ on https://lambdatest.github.io/sample-todo-app/ is located, and the a ‘click’ operation is performed. The methods are created based on the assumption that the ToDo app on TestMu AI is used as the web page under test.
The Inspect tool in the Chrome browser is used for getting information about the web element.

public function ToggleLi1()
{
$element1 = $this->session->findElement(WebDriverBy::name("li1"));
$element1->click();
}
The same set of operations are also performed for elements with the name ‘li2’. For adding a new item in the ToDo list, the text box with ID ‘sampletodotext’ is located, and the sendKeys() method in Selenium is invoked for adding the new item in the list.
We used the POM Builder extension in Chrome to get details of the web element on the page.

public function addTodo($itemName)
{
$element = $this->session->findElement(WebDriverBy::id("sampletodotext"));
$element->click();
$element->sendKeys($itemName . "
");
}
c. src\ Search.php
<?php
use FacebookWebDriverWebDriverBy;
class Search
{
private $session;
public $input;
public function __construct($session)
{
$this->session = $session;
$this->input = $session->findElement(WebDriverBy::name("q"));
}
public function SearchonGoogle($search_string)
{
$element = $this->session->findElement(WebDriverBy::name("q"));
$element->click();
if($element) {
$element->sendKeys($search_string);
$element->submit();
}
}
public function ClickSearchResult()
{
$element = $this->session->findElement(WebDriverBy::xpath("//h3[.='LambdaTest: Most Powerful Cross Browser Testing Tool Online']"));
sleep(2);
if($element) {
$element->click();
}
}
}
Here is the code walkthrough of the important parts of the implementation:
public function __construct($session)
{
$this->session = $session;
$this->input = $session->findElement(WebDriverBy::name("q"));
}

public function SearchonGoogle($search_string)
{
$element = $this->session->findElement(WebDriverBy::name("q"));
$element->click();
if($element) {
$element->sendKeys($search_string);
$element->submit();
}
}

public function ClickSearchResult()
{
$element = $this->session->findElement(WebDriverBy::xpath("//h3[.='LambdaTest: Most Powerful Cross Browser Testing Tool Online']"));
sleep(2);
if($element) {
$element->click();
}
}
d. tests\ ChromeTodoTest.php
<?php
require 'vendor/autoload.php';
require 'src/Todos.php';
use PHPUnitFrameworkTestCase;
use FacebookWebDriverRemoteDesiredCapabilities;
use FacebookWebDriverRemoteRemoteWebDriver;
use FacebookWebDriverWebDriverBy;
use SebastianBergmannCodeCoverageCodeCoverage;
use SebastianBergmannCodeCoverageDriverDriver;
use SebastianBergmannCodeCoverageFilter;
use SebastianBergmannCodeCoverageReportHtmlFacade as HtmlReport;
$filter = new Filter;
$filter->includeFile('location-to-pathTodos.php');
$driver = Driver::forLineCoverage($filter);
$coverage = new CodeCoverage($driver, $filter);
$condition_comp = true;
class ChromeTodoTest extends TestCase
{
protected $todos;
protected $webDriver;
protected function setUp(): void
{
global $condition_comp;
if ($condition_comp == true) {
global $filter;
global $driver;
global $coverage;
}
$this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());
$this->webDriver->get('https://lambdatest.github.io/sample-todo-app/');
$this->webDriver->manage()->window()->maximize();
$this->todos = new Todos($this->webDriver);
print("Called");
}
/**
* @covers Todos::ToggleLi1
*/
public function testClickli1()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testClickli1');
}
$this->todos->ToggleLi1();
sleep(5);
$this->assertEquals('Sample page - lambdatest.com', $this->webDriver->getTitle());
if ($condition_comp == true) {
$coverage->stop();
}
}
/**
* @covers Todos::ToggleLi2
*/
public function testClickli2()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testClickli2');
}
$this->todos->ToggleLi2();
sleep(5);
$this->assertEquals('Sample page - lambdatest.com', $this->webDriver->getTitle());
if ($condition_comp == true) {
$coverage->stop();
}
}
/**
* @covers Todos::addTodo
*/
public function testAddToList()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testAddToList');
}
$itemName = 'Yey, Lets add it to list';
$this->todos->addTodo($itemName);
$this->webDriver->wait(10, 500)->until(function($driver) {
$elements = $this->webDriver->findElements(WebDriverBy::cssSelector("[class='list-unstyled'] li:nth-child(6) span"));
return count($elements) > 0;
});
sleep(5);
$this->assertEquals('Sample page - lambdatest.com', $this->webDriver->getTitle());
if ($condition_comp == true) {
$coverage->stop();
(new HtmlReport)->process($coverage, 'code-coverage-report
eport');
}
}
public function tearDown(): void
{
$this->webDriver->quit();
}
}
Here is the code walkthrough that deep dives into the test code’s essential parts (used for running Test Scenario-1).
includeFile() method of the Filter class is used for limiting the code coverage to the concerned PHP file (i.e. ToDos.php)$filter = new Filter;
$filter->includeFile('absolute-path-toTodos.php');
The CodeCoverage object is created, with driver and filter objects as the arguments.
$driver = Driver::forLineCoverage($filter);
$coverage = new CodeCoverage($driver, $filter);
We would touch upon that aspect in subsequent sections.
$condition_comp = true;
PHPUnit\Framework\TestCase package.class ChromeTodoTest extends TestCase
The create method of RemoteWebDriver class is used for creating a new instance of the Chrome browser. The first argument of the method is the Selenium server address that has to be started before the tests are executed. In our case, the Selenium server is listening on port 4444 for any incoming requests (i.e., http://localhost:4444/wd/hub).
protected function setUp(): void
{
global $condition_comp;
if ($condition_comp == true) {
global $filter;
global $driver;
global $coverage;
}
$this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::chrome());
.................................................
.................................................
}
A new object (i.e. todos) of Todos class is created with an input parameter as the webdriver object.
$this->webDriver->get('https://lambdatest.github.io/sample-todo-app/');
.................................................
.................................................
$this->todos = new Todos($this->webDriver);
public function testClickli1()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testClickli1');
}
$this->todos->ToggleLi1();
.................................................
.................................................
if ($condition_comp == true) {
$coverage->stop();
}
}
In this test case, a non-blocking wait of 10 seconds is performed using wait(10,500), where the presence of the required web element is checked at a 500ms duration. The required web element count should return a non-zero value if that particular web element (i.e., Yey, Lets add it to list’) is present on the page.
/**
* @covers Todos::addTodo
*/
public function testAddToList()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testAddToList');
}
$itemName = 'Yey, Lets add it to list';
$this->todos->addTodo($itemName);
$this->webDriver->wait(10, 500)->until(function($driver) {
$elements = $this->webDriver->findElements(WebDriverBy::cssSelector("[class='list-unstyled'] li:nth-child(6) span"));
return count($elements) > 0;
});
.................................................
.................................................
if ($condition_comp == true) {
$coverage->stop();
(new HtmlReport)->process($coverage, 'code-coverage-report
eport');
}
}
public function testAddToList()
{
global $condition_comp;
if ($condition_comp == true) {
global $coverage;
$coverage->start('testAddToList');
}
.................................................
.................................................
if ($condition_comp == true) {
$coverage->stop();
(new HtmlReport)->process($coverage, 'code-coverage-report
eport');
}
}
e. tests\ FirefoxSearchTest.php
<?php
require 'vendor/autoload.php';
require 'src/Search.php';
use PHPUnitFrameworkTestCase;
use FacebookWebDriverRemoteDesiredCapabilities;
use FacebookWebDriverRemoteRemoteWebDriver;
use FacebookWebDriverWebDriverBy;
use SebastianBergmannCodeCoverageCodeCoverage;
use SebastianBergmannCodeCoverageDriverDriver;
use SebastianBergmannCodeCoverageFilter;
use SebastianBergmannCodeCoverageReportHtmlFacade as HtmlReport;
$filter_t = new Filter;
$filter_t->includeFile('Path-to-locationSearch.php');
$driver_t = Driver::forLineCoverage($filter_t);
$coverage_t = new CodeCoverage($driver_t, $filter_t);
$condition_compl = true;
class FirefoxSearchTest extends TestCase
{
protected $webDriver;
protected $search;
protected function setUp(): void
{
$this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::firefox());
$this->webDriver->get('https://www.google.com');
$this->webDriver->manage()->window()->maximize();
$this->search = new Search($this->webDriver);
print("FCalled");
}
/*
* @covers Search::ClickSearchResult
*/
public function testSearchResult()
{
global $condition_compl;
if ($condition_compl == true) {
global $coverage_t;
$coverage_t->start('testSearchResult');
}
$this->search->SearchonGoogle("LambdaTest");
sleep(2);
$this->search->ClickSearchResult();
sleep(2);
$this->assertEquals('Most Powerful Cross Browser Testing Tool Online | LambdaTest', $this->webDriver->getTitle());
if ($condition_compl == true) {
$coverage_t->stop();
(new HtmlReport)->process($coverage_t, 'code-coverage-report
eport');
}
}
public function tearDown(): void
{
$this->webDriver->quit();
}
}
Here is the code walkthrough of the test implementation used for running Test Scenario-2.
require 'vendor/autoload.php';
require 'src/Search.php';
protected function setUp(): void
{
$this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', DesiredCapabilities::firefox());
$this->webDriver->get('https://www.google.com');
.................................................
.................................................
}
$this->search = new Search($this->webDriver);
The test code contains only one test case, i.e., testSearchResult(). Hence, start and stop methods of code coverage object and the test code report’s processing is done in this method.
public function testSearchResult()
{
global $condition_compl;
if ($condition_compl == true) {
global $coverage_t;
$coverage_t->start('testSearchResult');
}
$this->search->SearchonGoogle("LambdaTest");
sleep(2);
$this->search->ClickSearchResult();
sleep(2);
$this->assertEquals('Most Powerful Cross Browser Testing Tool Online | LambdaTest', $this->webDriver->getTitle());
if ($condition_compl == true) {
$coverage_t->stop();
(new HtmlReport)->process($coverage_t, 'code-coverage-report
eport');
}
}
public function tearDown(): void
{
$this->webDriver->quit();
}
f. phpunit.xml.dist
As we have to invoke multiple test cases, we compose a test suite using XML configuration.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="chrome">
<file>src/Todos.php</file>
<directory>tests/chrome</directory>
</testsuite>
<testsuite name="firefox">
<file>src/Search.php</file>
<directory>tests/firefox</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
</php>
</phpunit>
Between the <directory> tags, we include the location of the directory that contains the source implementation. In our case, it is src.
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
The respective tests are added between the <testsuite> tags, and those are further enclosed between the <testsuites> tags. As we have two test cases, the implementation is located in different folders (i.e., tests/chrome/ChromeTodoTest.php and tests/firefox/FirefoxSearchTest.php), both would be stored under different <testsuite> tags. The details under <directory> tag indicate the source files’ location (i.e., src/Todos.php, src/ Search.php) so that coverage is calculated only against those files. If this is not done, the coverage is calculated against all the source files, and the coverage percentage would be incorrect.
<testsuites>
.................................................
.................................................
<testsuite name="chrome">
<file>src/Todos.php</file>
<directory>tests/chrome</directory>
</testsuite>
.................................................
.................................................
<testsuites>
Execution
For executing the test cases, invoke the following command on the terminal:
vendorinphpunit
We have not passed the directory details where the PHPUnit coverage HTML reports would be stored, as the directory details were already implemented using phpunit/php-code-coverage package methods.
Here is the terminal screenshot, which indicates that the tests have been successfully executed.

The PHPUnit framework invokes a new browser instance for each test case. Since tests\ChromeTodoTest.php comprises three test cases i.e. testClickli1(), testClickli2(), and testAddToList(); hence a fresh Chrome browser instance is invoked for every test case. Hence, we have the log under setUp (i.e., Called) is printed thrice on the terminal

The PHPUnit coverage HTML reports are available in the newly created directory – code-coverage-report.

Two reports named Search.php.html and Todos.php.html are created in the code coverage directory.

As seen in the PHPUnit HTML report contents, the test cases were instrumental in testing all the individual source files’ methods.

Contents of Search.php.html

Contents of Todods.php.html
We have obtained 100 percent code coverage, and the same is reflected in the test reports. As seen at the bottom of the reports, php-code-coverage used PCOV for performing code coverage.

Since we have enabled the PCOV extension in php.ini, hence it is used by php-code-coverage.

It is important to note that PCOV and Xdebug cannot be used simultaneously. Hence, one extension should be disabled in php.ini when the other one is used. For using php-code-coverage with Xdebug, we disable the PCOV extension and keep the entry titled zend_extension enabled in the file.

Here is the report snapshot after php-unit-coverage uses Xdebug. As indicated in the report footer, Xdebug 2.9.6 was used for code coverage report generation.

Our recommendation is to use phpunit/php-code-coverage with PCOV instead of Xdebug extension.

The advantage of using phpunit/php-unit-coverage for code coverage is that you selectively use it for performing code coverage on a ‘selected set of files.’ However, the –coverage-html option offered by PHPUnit can also be used for getting code coverage. Xdebug or PCOV should be enabled in php.ini for using this option. No changes in the test code are required. Apart from HTML format, code coverage reports can be generated in text, Clover XML, and Crap4J XML formats.

We disable the PCOV and enable support for Xdebug in php.ini

For performing code coverage on the existing test code, we disable the support for php-unit-coverage in the test files. The Boolean variable condition_comp is set to false to disable the support for php-unit-coverage.

FileName – tests\FirefoxSearchTest.php

FileName – tests\ChromeTodoTest.php
Run the following command on the terminal:
vendorinphpunit --coverage-html xHTML
where xHTML is the directory where test reports are generated.
Here is the execution snapshot:

As seen in the report snapshot of ToDo tests, the Xdebug extension was used for profiling and code coverage.

Separate PHPUnit code coverage report is generated for the tests executed using the Xdebug extension.

We disable Xdebug and enable support for PCOV in php.ini

Run the following command on the terminal:
vendorinphpunit --coverage-html pHTML
where pHTML is the directory where code coverage reports are generated.
Here is the execution snapshot:

As seen in the report snapshot, the PCOV extension was used for profiling and code coverage.

Separate PHPUnit code coverage report is generated by PCOV 1.0.6:

However, we personally recommend using phpunit/php-code-coverage instead of the non-programmatic options provided by Xdebug and PCOV.
When using PHPUnit on a local Selenium Grid, the Selenium Grid Server has to be started up and the browser binaries have to be made available on that machine.
Testing and reporting on a local Selenium Grid is not scalable, as it becomes a daunting task to perform testing on different browsers and old browser versions. The respective browsers and their corresponding browser binaries have to be downloaded to that system.
Instead, using PHPUnit on a cloud-based Selenium Grid like TestMu AI lets you perform testing and reporting on close to 2,000+ real browsers & operating systems online. Porting the existing code that generates PHPUnit code coverage report from a local Selenium to a cloud-based Selenium Grid requires minimal changes.
After creating an account on TestMu AI, keep a note of the user-name & access-key available on the profile page since the combination would be used for accessing the resources on the Grid on TestMu AI. For the test scenarios 1 & 2, generate the browser capabilities using TestMu AI browser capabilities generator.

After generation of browser capabilities, we port the existing implementation that demonstrates reporting on local Selenium Grid to TestMu AI’s cloud-based Grid (i.e., only infrastructure-related changes in tests are made).
For reporting, we used php-code-coverage hence, the global variable $condition_compl is set to True.
<?php
require 'vendor/autoload.php';
require 'src/Search.php';
...............................................
$GLOBALS['LT_USERNAME'] = "user-name";
# accessKey: AccessKey can be generated from automation dashboard or profile section
$GLOBALS['LT_APPKEY'] = "access-key";
...............................................
...............................................
$driver_t = Driver::forLineCoverage($filter_t);
$coverage_t = new CodeCoverage($driver_t, $filter_t);
$condition_compl = true;
class FirefoxSearchTest extends TestCase
{
protected $webDriver;
protected $search;
protected function setUp(): void
{
$capabilities = array(
"build" => "[PHP-Reporting] Reporting with Firefox on macOS Mojave",
"name" => "[PHP-Reporting] Reporting with Firefox on macOS Mojave",
"platform" => "Windows 10",
"browserName" => "Firefox",
"version" => "78.0"
);
$url = "https://". $GLOBALS['LT_USERNAME'] .":" . $GLOBALS['LT_APPKEY'] ."@hub.lambdatest.com/wd/hub";
$this->webDriver = RemoteWebDriver::create($url, $capabilities);
...............................................
...............................................
$this->search = new Search($this->webDriver);
print("FCalled");
}
...............................................
...............................................
As seen in the code snippet, capabilities that have been generated using the TestMu AI capabilities generator are used for testing. The combination of user-name and access-key, which are stored in global variables, is used for accessing the TestMu AI Grid [@hub.lambdatest.com/wd/hub]
We have marked the code changes for the ToDo test conducted on Chrome below.

Run the following command for executing the tests:
vendor\bin\phpunit
As seen in the execution screenshot obtained by accessing the Automation tab on TestMu AI, it is clear that four tests (in total) were executed on the Grid.

If you are a PHP expert, you can acquire a benchmark industry certification specializing in core PHP programming skills and expand your opportunities to advance your career in PHP automation testing.


PHPUnit code coverage reports come extremely handy as they give detailed information about code coverage. This helps in optimizing the test code in a manner that the code coverage is optimum. Xdebug and PCOV are the two widely used extensions for code coverage analysis.
For creating a PHPUnit coverage report in HTML, the php-code-coverage module that is spun out from PHPUnit and extends Xdebug and PCOV should be used. Using PCOV and Xdebug for code coverage does not involve implementation changes. It is recommended to use php-code-coverage as there is better control over the test report generation.
For attaining better code coverage while using cross browser testing, it is recommended to shift the testing process to a cloud-based Selenium Grid. When moving to the cloud-based Grid, no changes are involved in the reporting mechanism, and minimal changes are involved in the test code.

Deliver immersive digital experiences with Next-Generation Mobile Apps and Cross Browser Testing Cloud
Did you find this page helpful?
More Related Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance