panther
panther copied to clipboard
Code coverage compliance?
Your project is very interesting. :+1:
What about code coverage? Does it work like for a regular Symfony WebTestCase? If not, is that a panned thing or is it not feasible?
Thanks!
As it browses the app through a web server, coverage will not work by default. It's probably possible to hack something, but it's not on my todo list for now. I'll be glad to merge a PR anyway.
Actually, it would be possible to use a hack similar to this one: https://github.com/api-platform/core/blob/master/features/bootstrap/CoverageContext.php
I've tried the above, but I'm not really familiar with this kind of stuff
<?php
namespace App\Tests;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\Report\PHP;
use Symfony\Component\Panther\PantherTestCase;
class NewsControllerPantherTest extends PantherTestCase
{
/** @var CodeCoverage */
private static $coverage;
protected function setup()
{
$filter = new Filter();
$filter->addDirectoryToWhitelist(__DIR__ . '/../../src');
self::$coverage = new CodeCoverage(null, $filter);
self::$coverage->start("test1", true);
}
public function testJavaScript()
{
$client = static::createPantherClient();
$crawler = $client->request('GET', '/');
// do some asserts
self::assertFalse(false);
}
protected function tearDown()
{
// what does this actually do?
$feature = 'test';
(new PHP())->process(self::$coverage, __DIR__ . "/../../build/cov/coverage-$feature.cov");
self::$coverage->stop();
}
}
So there will be created a coverage-test.cov, however PhpStorm does not show covered code. I'm not sure what is missing.
I found a way to use code coverage with Panter after inspecting selenium method.
It's a small hack and it can be improved but it work you need to
- patch public/index.php for saving usage of code (2 patches in this file)
- overriding 'run()' method in all tests class to include code coverage usage at the end of each tests.
// public/index.php
// add method to start code coverage just in top of file
require dirname(__DIR__).'/config/bootstrap.php';
// code coverage in tests
if($_SERVER['APP_ENV'] == 'test' ) {
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
}
// public/index.php
// add method to strop and srtore code coverage information at the end of /public/index.php
// code coverage in tests
if($_SERVER['APP_ENV'] == 'test' || 1) {
// get code coverage information.
$data = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
$jsonCodeCoverageFile = __DIR__ . '/..'. DIRECTORY_SEPARATOR . 'var'. DIRECTORY_SEPARATOR. md5(uniqid(rand(), TRUE)).'.code_coverage';
file_put_contents($jsonCodeCoverageFile, serialize($data));
}
Overriding each tests files
// tests/myFonctionnalTest.php
public function run(TestResult $result = NULL)
{
if ($result === NULL) {
$result = $this->createResult();
}
// run original method.
parent::run($result);
$it = new \RecursiveDirectoryIterator("var/");
// Loop through files
foreach(new \RecursiveIteratorIterator($it) as $file) {
if ($file->getExtension() == 'code_coverage') {
$content = file_get_contents($file);
$content = unserialize($content);
// append in current test result.
$result->getCodeCoverage()->append(
$content, $this
);
// remove code_coverage_file
unlink($file);
}
}
return $result;
}
Now we got covered code with Panther :)

I was running into this issue as well. The hack provided by alebec worked like a charm for me. A small change I needed to make:
$content = file_get_contents($file->getRealPath());
unlink($file->getRealPath());
If one of you guys can add it to the docs, it would be awesome!
@alebec an improvement to your patch would be to put the code coverage in a dedicated subfolder of var to iterate only that folder when reading them (instead of iterating the full Symfony caches too)
Hi @alebec, thanks for the hack! Just wanted to add that if you're using PHP 7.3, you need to cast rand() as a string.
$jsonCodeCoverageFile = __DIR__ . '/..'. DIRECTORY_SEPARATOR . 'var'. DIRECTORY_SEPARATOR. md5(uniqid((string)rand(), TRUE)).'.code_coverage';
The error was silently ignored and it took me hours to figure it out.
Hi, have you got some hack for symfony 5.3 and php 8?
Version for Symfony 5.4:
Kernel.php:
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use function file_put_contents;
use function is_dir;
use function md5;
use function mkdir;
use function mt_rand;
use function serialize;
use function uniqid;
use function xdebug_get_code_coverage;
use function xdebug_start_code_coverage;
use function xdebug_stop_code_coverage;
class Kernel extends BaseKernel
{
public const COVERAGE_PANTHER_DIR = __DIR__ . '/../var/build/coverage-panther/';
public const COVERAGE_PANTHER_ENV = 'panther';
use MicroKernelTrait;
/**
* @inheritDoc
*/
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true)
{
// code coverage in tests
if ($this->environment === $this::COVERAGE_PANTHER_ENV) {
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
}
$response = parent::handle($request, $type, $catch);
if ($this->environment === $this::COVERAGE_PANTHER_ENV) {
// get code coverage information.
$data = xdebug_get_code_coverage();
xdebug_stop_code_coverage();
$jsonCodeCoverageFile = md5(uniqid((string) mt_rand(), true)) . '.code_coverage';
if (!is_dir($this::COVERAGE_PANTHER_DIR)) {
mkdir($this::COVERAGE_PANTHER_DIR, 0777, true);
}
file_put_contents($this::COVERAGE_PANTHER_DIR . $jsonCodeCoverageFile, serialize($data));
}
return $response;
}
}
ExtendedPantherTestCase.php: ...
/**
* @inheritDoc
*/
public function run(TestResult $result = null): TestResult
{
$result = parent::run($result);
if (!is_dir(Kernel::COVERAGE_PANTHER_DIR)) {
return $result;
}
$it = new \RecursiveDirectoryIterator(Kernel::COVERAGE_PANTHER_DIR);
/** @var SplFileInfo $file */
foreach (new \RecursiveIteratorIterator($it) as $file) {
if ($file->getExtension() === 'code_coverage') {
$content = file_get_contents($file->getRealPath());
$content = unserialize($content);
if (!empty($content)) {
// append in current test result.
$result->getCodeCoverage()->append(RawCodeCoverageData::fromXdebugWithoutPathCoverage($content), $this);
}
// remove code_coverage_file
unlink($file->getRealPath());
}
}
return $result;
}
...
@Dukecz Thanks! This works great!
Codeception has a very own router c3 for this task, maybe you can adopt something from it? I think it makes more sense than fiddeling around in the application kernel.
Additionally I'd prefer a kernel decorator, like the former CacheKernel, to decorate in a specific environment, only.
Anyone tried to use pcov instead of xdebug ?