phpphpstormcodeception

PhpStorm v2017.3.4 & Codeception v2.4.0 incompatibility


I'm struggling to make codeception work with PhpStorm.

I have setup CLI Interpreter:

enter image description here

enter image description here

I have also setup Codeception:

enter image description here

And here is the configuration of Codeception:

enter image description here

And when I run the tests from PhpStorm I receive the following:

On the left:

enter image description here

and the text on the right:

Testing started at 00:06 ...
/Applications/MAMP/bin/php/php7.2.1/bin/php /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php run --report -o "reporters: report: PhpStorm_Codeception_ReportPrinter" --no-ansi --no-interaction unit

Process finished with exit code 255

but when I run concept run inside my project folder on the terminal, it works fine and I receive this:

enter image description here

What am I doing wrong? I really read the manual on PhpStorm here and videos on youtube, but I can't make it work :/

Thanks!

UPDATE:

On further investigation, I found out the following:

$ /usr/bin/env php --version                                                                                                                                                                                     
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies

$ php --version                                                                                                                                                                                                    
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans

$ /usr/bin/env php -c "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini" --version                                                                                                                 
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans

$ cat .zprofile | grep "alias php="                                                                                                                                                                                 
alias php='/Applications/MAMP/bin/php/php7.2.1/bin/php -c "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini"'

UPDATE 2:

While this solved the issue I had with codeception when I was running with code coverage flag --coverage on terminal:

$ which php
php: aliased to /Applications/MAMP/bin/php/php7.2.1/bin/php -c '/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini'

$ /usr/bin/env php -i | grep "Loaded Configuration File"
Loaded Configuration File => /Applications/MAMP/bin/php/php7.2.1/conf/php.ini

$ mv /Applications/MAMP/bin/php/php7.2.1/conf/php.ini /Applications/MAMP/bin/php/php7.2.1/conf/php.bk.ini

$ ln -s "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini" /Applications/MAMP/bin/php/php7.2.1/conf/php.ini

$ /usr/bin/env php --version
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans

Now I receive the following message on PhpStorm:

Testing started at 15:55 ...
/Applications/MAMP/bin/php/php7.2.1/bin/php /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php run --report -o "reporters: report: PhpStorm_Codeception_ReportPrinter" --no-ansi --no-interaction

Fatal error: Class 'PHPUnit_TextUI_ResultPrinter' not found in /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php on line 22

Call Stack:
    0.0222     528280   1. {main}() /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php:0


Process finished with exit code 255

and this is the part of "require-dev" inside my composer.json file

  "require-dev": {
    "codeception/codeception": "^2.4"
  },

based on Fatal error: Class 'PHPUnit_TextUI_ResultPrinter' not found and by seeing the classname PHPUnit_TextUI_ResultPrinter instead of PHPUnit\TextUI\ResultPrinter at that point, I suspect that the phpunit version that codeception 2.4 uses is newer than the one required by PhpStorm? Maybe phpunit v6 is required?

Thanks!

UPDATE 3:

I can now confirm that PhpStorm v2017.3.4 is not compatible with Codeception 2.4 because the later, after v2.4.0 moved to PHPUnit v7.x which PhpStorm v2017.3.4 seems to not be compatible yet. After running:

$ composer remove codeception/codeception
$ composer require codeception/codeception:2.3.9 --dev

I got the following on PhpStorm:

enter image description here


Solution

  • So after more research, I fixed my issue.

    simple steps:

    $ cd /Applications/PhpStorm.app/Contents/plugins/codeception/lib
    
    $ ls
    codeception.jar  resources_en.jar
    
    $ mv codeception.jar codeception.zip
    
    $ unzip codeception.zip -d codeception
    
    $ rm codeception.zip
    
    $ cd codeception
    
    $ vim scripts/codeception.php
    
    $ zip -r ../codeception.zip *
    
    $ cd ..
    
    $ mv codeception.zip codeception.jar
    
    $ rm -r codeception/
    

    instead of vim scripts/codeception.php you can use any other text editor you like and replace the contents with the following:

    <?php
    
    if (!isset($_SERVER['IDE_CODECEPTION_EXE'])) {
        fwrite(STDERR, "The value of Codeception executable is not specified" . PHP_EOL);
        exit(1);
    }
    
    $exe = realpath($_SERVER['IDE_CODECEPTION_EXE']);
    if (!file_exists($exe)) {
        $originalPath = $_SERVER['IDE_CODECEPTION_EXE'];
        fwrite(STDERR, "The value of Codeception executable is specified, but file doesn't exist '$originalPath'" . PHP_EOL);
        exit(1);
    }
    
    if (Phar::isValidPharFilename(basename($exe), true)) {
        require_once 'phar://' . $exe . '/autoload.php';
    }
    else {
        require_once dirname($exe) .'/autoload.php';
    }
    
    
    class PhpStorm_Codeception_ReportPrinter extends \PHPUnit\TextUI\ResultPrinter
    {
        protected $testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
    
        protected $failures = [];
    
        /**
         * @var bool
         */
        private $isSummaryTestCountPrinted = false;
    
        /**
         * @var string
         */
        private $startedTestName;
    
        /**
         * @var string
         */
        private $flowId;
    
        /**
         * @param string $progress
         */
        protected function writeProgress($progress): void
        {
        }
    
        /**
         * @param \PHPUnit\Framework\TestResult $result
         */
        public function printResult(\PHPUnit\Framework\TestResult $result) : void
        {
            $this->printHeader();
            $this->printFooter($result);
        }
    
        /**
         * An error occurred.
         *
         * @param \PHPUnit\Framework\Test $test
         * @param \Throwable              $e
         * @param float                  $time
         */
        public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
        {
            $this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e);
        }
    
        /**
         * A warning occurred.
         *
         * @param \PHPUnit\Framework\Test    $test
         * @param \PHPUnit\Framework\Warning $e
         * @param float                     $time
         *
         * @since Method available since Release 5.1.0
         */
        public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
        {
            $this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e);
        }
    
        /**
         * A failure occurred.
         *
         * @param \PHPUnit\Framework\Test                 $test
         * @param \PHPUnit\Framework\AssertionFailedError $e
         * @param float                                  $time
         */
        public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time): void
        {
            $parameters = [];
            if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) {
                $comparisonFailure = $e->getComparisonFailure();
    
                if ($comparisonFailure instanceof \SebastianBergmann\Comparator\ComparisonFailure) {
                    $expectedString = $comparisonFailure->getExpectedAsString();
    
                    if (is_null($expectedString) || empty($expectedString)) {
                        $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected());
                    }
    
                    $actualString = $comparisonFailure->getActualAsString();
    
                    if (is_null($actualString) || empty($actualString)) {
                        $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual());
                    }
    
                    if (!is_null($actualString) && !is_null($expectedString)) {
                        $parameters['type']     = 'comparisonFailure';
                        $parameters['actual']   = $actualString;
                        $parameters['expected'] = $expectedString;
                    }
                }
            }
    
            $this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e, $parameters);
        }
    
        /**
         * Incomplete test.
         *
         * @param \PHPUnit\Framework\Test $test
         * @param \Throwable              $e
         * @param float                  $time
         */
        public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
        {
            $this->addIgnoredTest($test, $e);
        }
    
        /**
         * Risky test.
         *
         * @param \PHPUnit\Framework\Test $test
         * @param \Throwable              $e
         * @param float                  $time
         */
        public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
        {
            $this->addError($test, $e, $time);
        }
    
        /**
         * Skipped test.
         *
         * @param \PHPUnit\Framework\Test $test
         * @param \Throwable              $e
         * @param float                  $time
         */
        public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
        {
            $testName = self::getTestAsString($test);
            if ($this->startedTestName != $testName) {
                $this->startTest($test);
                $this->printEvent(
                    'testIgnored',
                    [
                        'name'    => $testName,
                        'message' => self::getMessage($e),
                        'details' => self::getDetails($e),
                    ]
                );
                $this->endTest($test, $time);
            } else {
                $this->addIgnoredTest($test, $e);
            }
        }
    
        public function addIgnoredTest(\PHPUnit\Framework\Test $test, Exception $e) {
            $this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED, $test, $e);
        }
    
        private function addFail($status, \PHPUnit\Framework\Test $test, $e, $parameters = []) {
            $key = self::getTestSignature($test);
            $this->testStatus = $status;
            $parameters['message'] = self::getMessage($e);
            $parameters['details'] = self::getDetails($e);
    
            $this->failures[$key][] = $parameters;
        }
    
        /**
         * A testsuite started.
         *
         * @param \PHPUnit\Framework\TestSuite $suite
         */
        public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
        {
            if (stripos(ini_get('disable_functions'), 'getmypid') === false) {
                $this->flowId = getmypid();
            } else {
                $this->flowId = false;
            }
    
            if (!$this->isSummaryTestCountPrinted) {
                $this->isSummaryTestCountPrinted = true;
    
                $this->printEvent(
                    'testCount',
                    ['count' => count($suite)]
                );
            }
    
            $suiteName = $suite->getName();
    
            if (empty($suiteName)) {
                return;
            }
    
            //TODO: configure 'locationHint' to navigate to 'unit', 'acceptance', 'functional' test suite
            //TODO: configure 'locationHint' to navigate to  DataProvider tests for Codeception earlier 2.2.6
            $parameters = ['name' => $suiteName];
            $this->printEvent('testSuiteStarted', $parameters);
        }
    
        /**
         * A testsuite ended.
         *
         * @param \PHPUnit\Framework\TestSuite $suite
         */
        public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
        {
            $suiteName = $suite->getName();
    
            if (empty($suiteName)) {
                return;
            }
    
            $parameters = ['name' => $suiteName];
            $this->printEvent('testSuiteFinished', $parameters);
        }
    
        public static function getTestSignature(\PHPUnit\Framework\SelfDescribing $testCase)
        {
            if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
                return $testCase->getSignature();
            }
            if ($testCase instanceof \PHPUnit\Framework\TestCase) {
                return get_class($testCase) . ':' . $testCase->getName(false);
            }
            return $testCase->toString();
        }
    
        public static function getTestAsString(\PHPUnit\Framework\SelfDescribing $testCase)
        {
            if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
                return $testCase->toString();
            }
            if ($testCase instanceof \PHPUnit\Framework\TestCase) {
                $text = $testCase->getName();
                $text = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $text);
                $text = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $text);
                $text = preg_replace('/^test /', '', $text);
                $text = ucfirst(strtolower($text));
                $text = str_replace(['::', 'with data set'], [':', '|'], $text);
                return Codeception\Util\ReflectionHelper::getClassShortName($testCase) . ': ' . $text;
            }
            return $testCase->toString();
        }
    
        public static function getTestFileName(\PHPUnit\Framework\SelfDescribing $testCase)
        {
            if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
                return $testCase->getFileName();
            }
            return (new \ReflectionClass($testCase))->getFileName();
        }
    
        public static function getTestFullName(\PHPUnit\Framework\SelfDescribing $testCase)
        {
            if ($testCase instanceof Codeception\Test\Interfaces\Plain) {
                return self::getTestFileName($testCase);
            }
            if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
                $signature = $testCase->getSignature(); // cut everything before ":" from signature
                return self::getTestFileName($testCase) . '::' . preg_replace('~^(.*?):~', '', $signature);
            }
            if ($testCase instanceof \PHPUnit\Framework\TestCase) {
                return self::getTestFileName($testCase) . '::' . $testCase->getName(false);
            }
            return self::getTestFileName($testCase) . '::' . $testCase->toString();
        }
    
        /**
         * A test started.
         *
         * @param \PHPUnit\Framework\Test $test
         */
        public function startTest(\PHPUnit\Framework\Test $test): void
        {
            $testName              = self::getTestAsString($test);
            $this->startedTestName = $testName;
            $location              = "php_qn://" . self::getTestFullName($test);
            $gherkin = self::getGherkinTestLocation($test);
            if ($gherkin != null) {
                $location = $gherkin;
            }
            $params                = ['name' => $testName, 'locationHint' => $location];
    
            if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) {
                $this->printEvent('testSuiteStarted', $params);
            }
            else {
                $this->printEvent('testStarted', $params);
            }
        }
    
        /**
         * A test ended.
         *
         * @param \PHPUnit\Framework\Test $test
         * @param float                  $time
         */
        public function endTest(\PHPUnit\Framework\Test $test, float $time): void
        {
            $result = null;
            switch ($this->testStatus) {
                case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR:
                case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE:
                    $result = 'testFailed';
                    break;
                case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED:
                    $result = 'testIgnored';
                    break;
            }
    
            $name = self::getTestAsString($test);
            if ($this->startedTestName != $name) {
                $name = $this->startedTestName;
            }
            $gherkin = self::getGherkinTestLocation($test);
            $duration = (int)(round($time, 2) * 1000);
            if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) {
                $steps = $test->getScenario()->getSteps();
                $len = sizeof($steps);
                $printed = 0;
                for ($i = 0; $i < $len; $i++) {
                    $step = $steps[$i];
                    if ($step->getAction() == null && $step->getMetaStep()) {
                        $step = $step->getMetaStep();
                    }
    
                    if ($step instanceof \Codeception\Step\Comment) {
                        // TODO: render comments in grey color?
                        // comments are not shown because at the moment it's hard to distinguish them from real tests.
                        // e.g. comment steps show descriptions from *.feature tests.
                        continue;
                    }
                    $printed++;
                    $testName = sprintf('%s %s %s',
                        ucfirst($step->getPrefix()),
                        $step->getHumanizedActionWithoutArguments(),
                        $step->getHumanizedArguments()
                    );
    
                    $location = $gherkin != null ? $gherkin : $step->getLine();
                    $this->printEvent('testStarted',
                        [
                            'name' => $testName,
                            'locationHint' => "file://$location"
                        ]);
    
                    $params = ['name' => $testName];
                    if ($i == $len - 1) {
                        parent::endTest($test, $time);
                        $this->printError($test, $result, $testName);
                        $params['duration'] = $duration;
                    }
                    $this->printEvent('testFinished', $params);
                }
    
                if ($printed == 0 && $result != null) {
                    $this->printEvent('testStarted', ['name' => $name]);
                    parent::endTest($test, $time);
                    $this->printError($test, $result, $name);
                    $this->printEvent('testFinished', [
                        'name' => $name,
                        'duration' => $duration
                    ]);
                }
    
                $this->printEvent('testSuiteFinished', ['name' => $name]);
            }
            else {
                parent::endTest($test, $time);
                $this->printError($test, $result, self::getTestAsString($test));
    
                $this->printEvent(
                    'testFinished',
                    [
                        'name' => self::getTestAsString($test),
                        'duration' => $duration
                    ]
                );
            }
        }
    
        private function printError(\PHPUnit\Framework\Test $test, $result, $name) {
            if ($result != null) {
                $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
                $key = self::getTestSignature($test);
                if (isset($this->failures[$key])) {
                    $failures = $this->failures[$key];
                    //TODO: check if it's possible to have sizeof($params) > 1
                    assert(sizeof($failures) == 1);
    
                    $params = $failures[0];
                    $params['name'] = $name;
                    $this->printEvent($result, $params);
                    unset($this->failures[$key]);
                }
            }
        }
    
        /**
         * @param string $eventName
         * @param array  $params
         */
        private function printEvent($eventName, $params = [])
        {
            $this->write("\n##teamcity[$eventName");
    
            if ($this->flowId) {
                $params['flowId'] = $this->flowId;
            }
    
            foreach ($params as $key => $value) {
                $escapedValue = self::escapeValue($value);
                $this->write(" $key='$escapedValue'");
            }
    
            $this->write("]\n");
        }
    
        private static function getGherkinTestLocation(\PHPUnit\Framework\Test $test) {
            if ($test instanceof \Codeception\Test\Gherkin) {
                $feature = $test->getFeatureNode();
                $scenario = $test->getScenarioNode();
                if ($feature != null && $scenario != null) {
                    return "file://" . $test->getFeatureNode()->getFile() . ":" . $test->getScenarioNode()->getLine();
                }
            }
            return null;
        }
    
        /**
         * @param Exception $e
         *
         * @return string
         */
        private static function getMessage(Exception $e)
        {
            $message = '';
    
            if (!$e instanceof \PHPUnit\Framework\Exception) {
                if (strlen(get_class($e)) != 0) {
                    $message = $message . get_class($e);
                }
    
                if (strlen($message) != 0 && strlen($e->getMessage()) != 0) {
                    $message = $message . ' : ';
                }
            }
    
            return $message . $e->getMessage();
        }
    
        /**
         * @param Exception $e
         *
         * @return string
         */
        private static function getDetails(Exception $e)
        {
            $stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($e);
            $previous   = $e->getPrevious();
    
            while ($previous) {
                $stackTrace .= "\nCaused by\n" .
                    \PHPUnit\Framework\TestFailure::exceptionToString($previous) . "\n" .
                    \PHPUnit\Util\Filter::getFilteredStacktrace($previous);
    
                $previous = $previous->getPrevious();
            }
    
            return ' ' . str_replace("\n", "\n ", $stackTrace);
        }
    
        /**
         * @param mixed $value
         *
         * @return string
         */
        private static function getPrimitiveValueAsString($value)
        {
            if (is_null($value)) {
                return 'null';
            } elseif (is_bool($value)) {
                return $value == true ? 'true' : 'false';
            } elseif (is_scalar($value)) {
                return print_r($value, true);
            }
    
            return;
        }
    
        /**
         * @param  $text
         *
         * @return string
         */
        private static function escapeValue($text)
        {
            $text = str_replace('|', '||', $text);
            $text = str_replace("'", "|'", $text);
            $text = str_replace("\n", '|n', $text);
            $text = str_replace("\r", '|r', $text);
            $text = str_replace(']', '|]', $text);
            $text = str_replace('[', '|[', $text);
    
            return $text;
        }
    
        /**
         * @param string $className
         *
         * @return string
         */
        private static function getFileName($className)
        {
            $reflectionClass = new ReflectionClass($className);
            $fileName        = $reflectionClass->getFileName();
    
            return $fileName;
        }
    }
    
    
    $app = new \Codeception\Application('Codeception', \Codeception\Codecept::VERSION);
    
    if (version_compare(\Codeception\Codecept::VERSION, "2.2.6") >= 0) {
        $app->add(new \Codeception\Command\Run('run'));
        $app->run();
    }
    else {
        class PhpStorm_Codeception_Command_Run extends \Codeception\Command\Run {
    
            public function execute(\Symfony\Component\Console\Input\InputInterface $input,
                                    \Symfony\Component\Console\Output\OutputInterface $output)
            {
                $this->ensureCurlIsAvailable();
                $this->options = $input->getOptions();
                $this->output = $output;
    
                $config = \Codeception\Configuration::config($this->options['config']);
    
                if (!$this->options['colors']) {
                    $this->options['colors'] = $config['settings']['colors'];
                }
                if (!$this->options['silent']) {
                    $this->output->writeln(
                        \Codeception\Codecept::versionString() . "\nPowered by " . \PHPUnit\Runner\Version::getVersionString()
                    );
                }
                if ($this->options['debug']) {
                    $this->output->setVerbosity(\Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE);
                }
    
                $userOptions = array_intersect_key($this->options, array_flip($this->passedOptionKeys($input)));
                $userOptions = array_merge(
                    $userOptions,
                    $this->booleanOptions($input, ['xml', 'html', 'json', 'tap', 'coverage', 'coverage-xml', 'coverage-html'])
                );
                $userOptions['verbosity'] = $this->output->getVerbosity();
                $userOptions['interactive'] = !$input->hasParameterOption(['--no-interaction', '-n']);
                $userOptions['ansi'] = (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi'));
    
                if ($this->options['no-colors'] || !$userOptions['ansi']) {
                    $userOptions['colors'] = false;
                }
                if ($this->options['group']) {
                    $userOptions['groups'] = $this->options['group'];
                }
                if ($this->options['skip-group']) {
                    $userOptions['excludeGroups'] = $this->options['skip-group'];
                }
                if ($this->options['report']) {
                    $userOptions['silent'] = true;
                }
                if ($this->options['coverage-xml'] or $this->options['coverage-html'] or $this->options['coverage-text']) {
                    $this->options['coverage'] = true;
                }
                if (!$userOptions['ansi'] && $input->getOption('colors')) {
                    $userOptions['colors'] = true; // turn on colors even in non-ansi mode if strictly passed
                }
    
                $suite = $input->getArgument('suite');
                $test = $input->getArgument('test');
    
                if (! \Codeception\Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) {
                    list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']);
                }
    
                if ($this->options['group']) {
                    $this->output->writeln(sprintf("[Groups] <info>%s</info> ", implode(', ', $this->options['group'])));
                }
                if ($input->getArgument('test')) {
                    $this->options['steps'] = true;
                }
    
                if ($test) {
                    $filter = $this->matchFilteredTestName($test);
                    $userOptions['filter'] = $filter;
                }
    
                $this->codecept = new PhpStorm_Codeception_Codecept($userOptions);
    
                if ($suite and $test) {
                    $this->codecept->run($suite, $test);
                }
    
                if (!$test) {
                    $suites = $suite ? explode(',', $suite) : \Codeception\Configuration::suites();
                    $this->executed = $this->runSuites($suites, $this->options['skip']);
    
                    if (!empty($config['include']) and !$suite) {
                        $current_dir = \Codeception\Configuration::projectDir();
                        $suites += $config['include'];
                        $this->runIncludedSuites($config['include'], $current_dir);
                    }
    
                    if ($this->executed === 0) {
                        throw new \RuntimeException(
                            sprintf("Suite '%s' could not be found", implode(', ', $suites))
                        );
                    }
                }
    
                $this->codecept->printResult();
    
                if (!$input->getOption('no-exit')) {
                    if (!$this->codecept->getResult()->wasSuccessful()) {
                        exit(1);
                    }
                }
            }
    
            private function matchFilteredTestName(&$path)
            {
                if (version_compare(\Codeception\Codecept::VERSION, "2.2.5") >= 0) {
                    $test_parts = explode(':', $path, 2);
                    if (count($test_parts) > 1) {
                        list($path, $filter) = $test_parts;
                        // use carat to signify start of string like in normal regex
                        // phpunit --filter matches against the fully qualified method name, so tests actually begin with :
                        $carat_pos = strpos($filter, '^');
                        if ($carat_pos !== false) {
                            $filter = substr_replace($filter, ':', $carat_pos, 1);
                        }
                        return $filter;
                    }
                    return null;
                }
                else {
                    $test_parts = explode(':', $path);
                    if (count($test_parts) > 1) {
                        list($path, $filter) = $test_parts;
                        return $filter;
                    }
                    return null;
                }
            }
    
            private function ensureCurlIsAvailable()
            {
                if (!extension_loaded('curl')) {
                    throw new \Exception(
                        "Codeception requires CURL extension installed to make tests run\n"
                        . "If you are not sure, how to install CURL, please refer to StackOverflow\n\n"
                        . "Notice: PHP for Apache/Nginx and CLI can have different php.ini files.\n"
                        . "Please make sure that your PHP you run from console has CURL enabled."
                    );
                }
            }
        }
    
        class PhpStorm_Codeception_Codecept extends \Codeception\Codecept {
            public function __construct($options = [])
            {
                parent::__construct($options);
    
                $printer = new PhpStorm_Codeception_ReportPrinter();
                $this->runner = new \Codeception\PHPUnit\Runner();
                $this->runner->setPrinter($printer);
            }
        }
    
        $app->add(new PhpStorm_Codeception_Command_Run('run'));
        $app->run();
    }
    

    and now it works fine with PHPUnit v7.x and Codeception 2.4.x

    enter image description here

    enter image description here

    UPDATE:

    I have uploaded here a jar file that fixes the issue, until PhpStorm fixes it officially, for anyone who don't want to go through the above steps.

    UPDATE 2:

    Just want to point out that this fix, does not provide backwards compatibility for versions of codeception less than 2.4.0, I'm sure PhpStorm team will provide a more elegant solution that would allow having projects that use codeception 2.4.x and other projects that use earlier verions.