diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5536a4c..155a16b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,7 @@ jobs: php-version: - "8.0" - "8.1" + - "8.2" os: - ubuntu-latest - windows-latest @@ -48,10 +49,10 @@ jobs: ${{ matrix.composer-options }} - name: Run tests - if: ${{ matrix.php-version != '8.1' }} + if: ${{ matrix.php-version != '8.2' }} run: composer test - name: Run tests (experimental) - if: ${{ matrix.php-version == '8.1' }} + if: ${{ matrix.php-version == '8.2' }} continue-on-error: true run: composer test diff --git a/composer.json b/composer.json index 939652b..5faea3d 100644 --- a/composer.json +++ b/composer.json @@ -23,19 +23,16 @@ "require": { "php": "^8", "ext-json": "*", - "codeception/codeception": "^4.1", + "codeception/codeception": "^5", "allure-framework/allure-php-commons": "^2" }, "require-dev": { "phpunit/phpunit": "^9", - "psalm/plugin-phpunit": "^0.16.1", + "psalm/plugin-phpunit": "^0.18.4", "remorhaz/php-json-data": "^0.5.3", "remorhaz/php-json-path": "^0.7.7", - "squizlabs/php_codesniffer": "^3.6.2", - "vimeo/psalm": "^4.20" - }, - "conflict": { - "codeception/phpunit-wrapper": "<9.0.1" + "squizlabs/php_codesniffer": "^3.7.1", + "vimeo/psalm": "^5.2" }, "autoload": { "psr-4": { diff --git a/src/AllureCodeception.php b/src/AllureCodeception.php index 2ec9961..362673e 100644 --- a/src/AllureCodeception.php +++ b/src/AllureCodeception.php @@ -12,7 +12,6 @@ use Codeception\Event\TestEvent; use Codeception\Events; use Codeception\Exception\ConfigurationException; -use Codeception\Step; use Qameta\Allure\Allure; use Qameta\Allure\Allure as QametaAllure; use Qameta\Allure\Codeception\Internal\DefaultThreadDetector; @@ -26,7 +25,6 @@ use Qameta\Allure\Setup\DefaultStatusDetector; use Qameta\Allure\Setup\LinkTemplate; use Qameta\Allure\Setup\LinkTemplateInterface; -use Throwable; use function class_exists; use function is_a; @@ -46,6 +44,7 @@ final class AllureCodeception extends Extension private const DEFAULT_RESULTS_DIRECTORY = 'allure-results'; protected static array $events = [ + Events::MODULE_INIT => 'moduleInit', Events::SUITE_BEFORE => 'suiteBefore', Events::SUITE_AFTER => 'suiteAfter', Events::TEST_START => 'testStart', @@ -69,11 +68,11 @@ final class AllureCodeception extends Extension * @throws ConfigurationException * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore */ - public function _initialize(): void + public function moduleInit(): void { - // phpcs:enable PSR2.Methods.MethodDeclaration.Underscore - parent::_initialize(); QametaAllure::reset(); + $this->testLifecycle = null; + $this->threadDetector = null; QametaAllure::getLifecycleConfigurator() ->setStatusDetector(new StatusDetector(new DefaultStatusDetector())) ->setOutputDirectory($this->getOutputDirectory()); @@ -148,7 +147,11 @@ class_exists($linkConfig) && is_a($linkConfig, LinkTemplateInterface::class, tru public function suiteBefore(SuiteEvent $suiteEvent): void { /** @psalm-suppress InternalMethod */ - $suiteName = $suiteEvent->getSuite()->getName(); + $suiteName = $suiteEvent->getSuite()?->getName(); + if (!isset($suiteName)) { + return; + } + $this ->getTestLifecycle() ->switchToSuite(new SuiteInfo($suiteName)); @@ -185,12 +188,10 @@ private function getThreadDetector(): ThreadDetectorInterface */ public function testError(FailEvent $failEvent): void { - /** @var Throwable $error */ - $error = $failEvent->getFail(); $this ->getTestLifecycle() ->switchToTest($failEvent->getTest()) - ->updateTestFailure($error); + ->updateTestFailure($failEvent->getFail()); } /** @@ -198,12 +199,10 @@ public function testError(FailEvent $failEvent): void */ public function testFail(FailEvent $failEvent): void { - /** @var Throwable $error */ - $error = $failEvent->getFail(); $this ->getTestLifecycle() ->switchToTest($failEvent->getTest()) - ->updateTestFailure($error, Status::failed()); + ->updateTestFailure($failEvent->getFail(), Status::failed()); } /** @@ -211,7 +210,6 @@ public function testFail(FailEvent $failEvent): void */ public function testIncomplete(FailEvent $failEvent): void { - /** @var Throwable $error */ $error = $failEvent->getFail(); $this ->getTestLifecycle() @@ -228,7 +226,6 @@ public function testIncomplete(FailEvent $failEvent): void */ public function testSkipped(FailEvent $failEvent): void { - /** @var Throwable $error */ $error = $failEvent->getFail(); $this ->getTestLifecycle() @@ -269,12 +266,10 @@ public function testEnd(TestEvent $testEvent): void */ public function stepBefore(StepEvent $stepEvent): void { - /** @psalm-var Step $step */ - $step = $stepEvent->getStep(); $this ->getTestLifecycle() ->switchToTest($stepEvent->getTest()) - ->startStep($step) + ->startStep($stepEvent->getStep()) ->updateStep(); } @@ -283,12 +278,10 @@ public function stepBefore(StepEvent $stepEvent): void */ public function stepAfter(StepEvent $stepEvent): void { - /** @psalm-var Step $step */ - $step = $stepEvent->getStep(); $this ->getTestLifecycle() ->switchToTest($stepEvent->getTest()) - ->switchToStep($step) + ->switchToStep($stepEvent->getStep()) ->updateStepResult() ->stopStep(); } diff --git a/src/Internal/ArgumentAsString.php b/src/Internal/ArgumentAsString.php index 4667065..26455fe 100644 --- a/src/Internal/ArgumentAsString.php +++ b/src/Internal/ArgumentAsString.php @@ -4,7 +4,7 @@ namespace Qameta\Allure\Codeception\Internal; -use Codeception\Util\Locator; +use InvalidArgumentException; use Stringable; use function array_map; @@ -15,6 +15,7 @@ use function is_resource; use function is_string; use function json_encode; +use function method_exists; use function strtr; use function trim; @@ -77,7 +78,7 @@ private function prepareObject(object $argument): string $webdriverByClass = '\Facebook\WebDriver\WebDriverBy'; if (class_exists($webdriverByClass) && is_a($argument, $webdriverByClass)) { - return Locator::humanReadableString($argument); + return $this->webDriverByAsString($argument); } return trim($argument::class, "\\"); @@ -90,4 +91,21 @@ public function __toString(): string JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); } + + private function webDriverByAsString(object $selector): string + { + $type = method_exists($selector, 'getMechanism') + ? (string) $selector->getMechanism() + : null; + + $locator = method_exists($selector, 'getValue') + ? (string) $selector->getValue() + : null; + + if (!isset($type, $locator)) { + throw new InvalidArgumentException("Unrecognized selector"); + } + + return "$type '$locator'"; + } } diff --git a/src/Internal/CeptInfoBuilder.php b/src/Internal/CeptInfoBuilder.php index bd8876f..559fa6d 100644 --- a/src/Internal/CeptInfoBuilder.php +++ b/src/Internal/CeptInfoBuilder.php @@ -17,9 +17,9 @@ public function build(?string $host, ?string $thread): TestInfo { return new TestInfo( originalTest: $this->test, - signature: (string) $this->test->getSignature(), - class: (string) $this->test->getName(), - method: (string) $this->test->getName(), + signature: $this->test->getSignature(), + class: $this->test->getName(), + method: $this->test->getName(), host: $host, thread: $thread, ); diff --git a/src/Internal/CeptProvider.php b/src/Internal/CeptProvider.php index c33ea46..b98869d 100644 --- a/src/Internal/CeptProvider.php +++ b/src/Internal/CeptProvider.php @@ -100,7 +100,7 @@ public function getDisplayName(): ?string public function getFullName(): ?string { - return (string) $this->test->getSignature(); + return $this->test->getSignature(); } public function getDescription(): ?string @@ -118,7 +118,7 @@ public function getDescriptionHtml(): ?string private function getLegacyAnnotation(string $name): ?string { /** - * @var mixed $annotations + * @psalm-var mixed $annotations * @psalm-suppress InvalidArgument */ $annotations = $this->test->getMetadata()->getParam($name); @@ -140,7 +140,7 @@ private function getLegacyAnnotation(string $name): ?string private function getLegacyAnnotations(string $name): array { /** - * @var mixed $annotations + * @psalm-var mixed $annotations * @psalm-suppress InvalidArgument */ $annotations = $this->test->getMetadata()->getParam($name); diff --git a/src/Internal/CestInfoBuilder.php b/src/Internal/CestInfoBuilder.php index 1187040..1b93087 100644 --- a/src/Internal/CestInfoBuilder.php +++ b/src/Internal/CestInfoBuilder.php @@ -7,7 +7,6 @@ use Codeception\Test\Cest; use function is_int; -use function is_object; use function is_string; final class CestInfoBuilder implements TestInfoBuilderInterface @@ -19,16 +18,11 @@ public function __construct( public function build(?string $host, ?string $thread): TestInfo { - /** @var mixed $testClass */ - $testClass = $this->test->getTestClass(); - /** @var mixed $testMethod */ - $testMethod = $this->test->getTestMethod(); - return new TestInfo( originalTest: $this->test, - signature: (string) $this->test->getSignature(), - class: is_object($testClass) ? $testClass::class : null, - method: is_string($testMethod) ? $testMethod : null, + signature: $this->test->getSignature(), + class: $this->test->getTestInstance()::class, + method: $this->test->getTestMethod(), dataLabel: $this->getDataLabel(), host: $host, thread: $thread, diff --git a/src/Internal/CestProvider.php b/src/Internal/CestProvider.php index 7676916..6a25730 100644 --- a/src/Internal/CestProvider.php +++ b/src/Internal/CestProvider.php @@ -16,7 +16,6 @@ use function array_values; use function is_array; use function is_int; -use function is_object; use function is_string; /** @@ -37,16 +36,12 @@ public function __construct( */ public static function createForChain(Cest $test, LinkTemplateCollectionInterface $linkTemplates): array { - /** @var mixed $testClass */ - $testClass = $test->getTestClass(); - /** @var mixed $testMethod */ - $testMethod = $test->getTestMethod(); - /** @var callable-string|null $callableTestMethod */ - $callableTestMethod = is_string($testMethod) ? $testMethod : null; + /** @psalm-var callable-string $callableTestMethod */ + $callableTestMethod = $test->getTestMethod(); return [ ...AttributeParser::createForChain( - classOrObject: is_object($testClass) ? $testClass : null, + classOrObject: $test->getTestInstance(), methodOrFunction: $callableTestMethod, linkTemplates: $linkTemplates, ), @@ -111,6 +106,6 @@ public function getDescriptionHtml(): ?string */ public function getFullName(): ?string { - return get_class($this->test->getTestClass()) . "::" . $this->test->getTestMethod(); + return $this->test->getTestInstance()::class . "::" . $this->test->getTestMethod(); } } diff --git a/src/Internal/GherkinInfoBuilder.php b/src/Internal/GherkinInfoBuilder.php index d56d3c6..cc744ca 100644 --- a/src/Internal/GherkinInfoBuilder.php +++ b/src/Internal/GherkinInfoBuilder.php @@ -17,16 +17,11 @@ public function __construct( public function build(?string $host, ?string $thread): TestInfo { - /** @psalm-var mixed $className */ - $className = $this->test->getFeature(); - /** @psalm-var mixed $methodName */ - $methodName = $this->test->getScenarioTitle(); - return new TestInfo( originalTest: $this->test, - signature: (string) $this->test->getSignature(), - class: is_string($className) ? $className : null, - method: is_string($methodName) ? $methodName : null, + signature: $this->test->getSignature(), + class: $this->test->getFeature(), + method: $this->test->getScenarioTitle(), host: $host, thread: $thread, ); diff --git a/src/Internal/GherkinProvider.php b/src/Internal/GherkinProvider.php index 66fdd8d..86a8345 100644 --- a/src/Internal/GherkinProvider.php +++ b/src/Internal/GherkinProvider.php @@ -49,7 +49,7 @@ public function getParameters(): array public function getDisplayName(): ?string { - return (string) $this->test->toString(); + return $this->test->toString(); } public function getDescription(): ?string diff --git a/src/Internal/TestLifecycle.php b/src/Internal/TestLifecycle.php index 37c0669..5281234 100644 --- a/src/Internal/TestLifecycle.php +++ b/src/Internal/TestLifecycle.php @@ -8,8 +8,8 @@ use Codeception\Test\Cept; use Codeception\Test\Cest; use Codeception\Test\Gherkin; +use Codeception\Test\TestCaseWrapper; use Codeception\TestInterface; -use PHPUnit\Framework\TestCase; use Qameta\Allure\AllureLifecycleInterface; use Qameta\Allure\Codeception\Setup\ThreadDetectorInterface; use Qameta\Allure\Io\DataSourceFactory; @@ -60,7 +60,7 @@ public function __construct( private ThreadDetectorInterface $threadDetector, private LinkTemplateCollectionInterface $linkTemplates, ) { - /** @var WeakMap */ + /** @psalm-var WeakMap $this->stepStarts */ $this->stepStarts = new WeakMap(); } @@ -119,7 +119,7 @@ private function getTestInfoBuilder(object $test): TestInfoBuilderInterface $test instanceof Cest => new CestInfoBuilder($test), $test instanceof Gherkin => new GherkinInfoBuilder($test), $test instanceof Cept => new CeptInfoBuilder($test), - $test instanceof TestCase => new UnitInfoBuilder($test), + $test instanceof TestCaseWrapper => new UnitInfoBuilder($test), default => new UnknownInfoBuilder($test), }; } @@ -168,7 +168,7 @@ private function createModelProvidersForTest(mixed $test): array $test instanceof Cest => CestProvider::createForChain($test, $this->linkTemplates), $test instanceof Gherkin => GherkinProvider::createForChain($test), $test instanceof Cept => CeptProvider::createForChain($test, $this->linkTemplates), - $test instanceof TestCase => UnitProvider::createForChain($test, $this->linkTemplates), + $test instanceof TestCaseWrapper => UnitProvider::createForChain($test, $this->linkTemplates), default => [], }; } @@ -323,10 +323,6 @@ public function stopStep(): self { $stepStart = $this->getCurrentStepStart(); $this->lifecycle->stopStep($stepStart->getUuid()); - /** - * @var Step $step - * @psalm-ignore-var - */ foreach ($this->stepStarts as $step => $storedStart) { if ($storedStart === $stepStart) { unset($this->stepStarts[$step]); @@ -341,15 +337,9 @@ public function updateStep(): self { $stepStart = $this->getCurrentStepStart(); $step = $stepStart->getOriginalStep(); - if (null === $step->getAction()) { - $step = $step->getMetaStep(); - } $params = []; - /** - * @var array-key $name - * @var mixed $value - */ + /** @psalm-var mixed $value */ foreach ($step->getArguments() as $name => $value) { $params[] = new Parameter( is_int($name) ? "#$name" : $name, diff --git a/src/Internal/UnitInfoBuilder.php b/src/Internal/UnitInfoBuilder.php index 0009a4f..61dfae9 100644 --- a/src/Internal/UnitInfoBuilder.php +++ b/src/Internal/UnitInfoBuilder.php @@ -4,40 +4,32 @@ namespace Qameta\Allure\Codeception\Internal; -use PHPUnit\Framework\TestCase; +use Codeception\Test\TestCaseWrapper; +use function is_int; use function preg_match; final class UnitInfoBuilder implements TestInfoBuilderInterface { public function __construct( - private TestCase $test, + private TestCaseWrapper $test, ) { } public function build(?string $host, ?string $thread): TestInfo { - /** @psalm-suppress InternalMethod */ - $methodName = $this->test->getName(false); + $fields = $this->test->getReportFields(); + $index = $this->test->getMetadata()->getIndex(); + $dataLabel = is_int($index) ? "#$index" : $index; return new TestInfo( originalTest: $this->test, - signature: $this->test::class . ':' . $methodName, - class: $this->test::class, - method: $methodName, - dataLabel: $this->getDataLabel(), + signature: $this->test->getSignature(), + class: $fields['class'] ?? null, + method: $this->test->getMetadata()->getName(), + dataLabel: $dataLabel, host: $host, thread: $thread, ); } - - private function getDataLabel(): ?string - { - /** @psalm-suppress InternalMethod */ - $dataSet = $this->test->getDataSetAsString(false); - - return 1 === preg_match('#^ with data set (.+)$#', $dataSet, $matches) - ? $matches[1] - : null; - } } diff --git a/src/Internal/UnitProvider.php b/src/Internal/UnitProvider.php index 487da5c..382bc00 100644 --- a/src/Internal/UnitProvider.php +++ b/src/Internal/UnitProvider.php @@ -4,7 +4,8 @@ namespace Qameta\Allure\Codeception\Internal; -use PHPUnit\Framework\TestCase; +use Closure; +use Codeception\Test\TestCaseWrapper; use Qameta\Allure\Attribute\AttributeParser; use Qameta\Allure\Setup\LinkTemplateCollectionInterface; use Qameta\Allure\Model\ModelProviderInterface; @@ -21,32 +22,32 @@ final class UnitProvider implements ModelProviderInterface { /** - * @param TestCase $test + * @param TestCaseWrapper $test * @param LinkTemplateCollectionInterface $linkTemplates */ public function __construct( - private TestCase $test, + private TestCaseWrapper $test, LinkTemplateCollectionInterface $linkTemplates, ) { } /** - * @param TestCase $test + * @param TestCaseWrapper $test * @param LinkTemplateCollectionInterface $linkTemplates * @throws ReflectionException * @return list */ - public static function createForChain(TestCase $test, LinkTemplateCollectionInterface $linkTemplates): array + public static function createForChain(TestCaseWrapper $test, LinkTemplateCollectionInterface $linkTemplates): array { - /** - * @var callable-string|null $methodOrFunction - * @psalm-suppress InternalMethod - */ - $methodOrFunction = $test->getName(false); + $fields = $test->getReportFields(); + /** @var class-string $class */ + $class = $fields['class'] ?? null; + /** @var Closure|callable-string|null $methodOrFunction */ + $methodOrFunction = $test->getMetadata()->getName(); return [ ...AttributeParser::createForChain( - classOrObject: $test, + classOrObject: $class, methodOrFunction: $methodOrFunction, linkTemplates: $linkTemplates, ), @@ -69,16 +70,17 @@ public function getLabels(): array */ public function getParameters(): array { - /** @psalm-suppress InternalMethod */ - if (!$this->test->usesDataProvider()) { + $testMetadata = $this->test->getMetadata(); + if (null === $testMetadata->getIndex()) { return []; } - $dataMethod = new ReflectionMethod($this->test, 'getProvidedData'); + $testCase = $this->test->getTestCase(); + + $dataMethod = new ReflectionMethod($testCase, 'getProvidedData'); $dataMethod->setAccessible(true); - /** @psalm-suppress InternalMethod */ - $methodName = $this->test->getName(false); - $testMethod = new ReflectionMethod($this->test, $methodName); + $methodName = $testMetadata->getName(); + $testMethod = new ReflectionMethod($testCase, $methodName); $argNames = $testMethod->getParameters(); $params = []; @@ -86,7 +88,7 @@ public function getParameters(): array * @var array-key $key * @var mixed $param */ - foreach ($dataMethod->invoke($this->test) as $key => $param) { + foreach ($dataMethod->invoke($testCase) as $key => $param) { $argName = array_shift($argNames); $name = $argName?->getName() ?? $key; $params[] = new Parameter( @@ -100,8 +102,7 @@ public function getParameters(): array public function getDisplayName(): ?string { - /** @psalm-suppress InternalMethod */ - return $this->test->getName(); + return $this->test->getMetadata()->getName(); } public function getDescription(): ?string diff --git a/test/codeception/_support/UnitTester.php b/test/codeception/_support/UnitTester.php new file mode 100644 index 0000000..a2ff24d --- /dev/null +++ b/test/codeception/_support/UnitTester.php @@ -0,0 +1,29 @@ +failure = $failure; } - public function run(ModuleContainer $container = null) + public function run(ModuleContainer $container = null): void { $this->setFailed(true); Assert::fail($this->failure);