diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d02762..fe0d75f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.3" + php-version: "8.5" - name: Validate composer.json run: composer validate @@ -52,6 +52,8 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" + - "8.5" os: - ubuntu - windows diff --git a/composer.json b/composer.json index dc4a984..6cc477f 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,6 @@ }, "require-dev": { "psalm/plugin-phpunit": "^0.19.0 || ^0.20.1", - "remorhaz/php-json-data": "^0.5.3", - "remorhaz/php-json-path": "^0.7.7", "squizlabs/php_codesniffer": "^4.0.1", "vimeo/psalm": "^5.26.1 || ^6.16.1" }, diff --git a/src/AllureCodeception.php b/src/AllureCodeception.php index ba9dbf4..fc796af 100644 --- a/src/AllureCodeception.php +++ b/src/AllureCodeception.php @@ -68,6 +68,7 @@ final class AllureCodeception extends Extension * @throws ConfigurationException * phpcs:disable PSR2.Methods.MethodDeclaration.Underscore */ + #[\Override] public function _initialize(): void { parent::_initialize(); diff --git a/src/Internal/ArgumentAsString.php b/src/Internal/ArgumentAsString.php index 6e2fee1..4d539ce 100644 --- a/src/Internal/ArgumentAsString.php +++ b/src/Internal/ArgumentAsString.php @@ -88,12 +88,19 @@ private function prepareObject(object $argument): string return trim($argument::class, "\\"); } + #[\Override] public function __toString(): string { - return json_encode( + $json = json_encode( $this->prepareArgument($this->argument), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); + + if ($json === false) { + return ""; + } + + return $json; } private function webDriverByAsString(object $selector): string diff --git a/src/Internal/CeptInfoBuilder.php b/src/Internal/CeptInfoBuilder.php index 559fa6d..caa4eb0 100644 --- a/src/Internal/CeptInfoBuilder.php +++ b/src/Internal/CeptInfoBuilder.php @@ -13,6 +13,7 @@ public function __construct( ) { } + #[\Override] public function build(?string $host, ?string $thread): TestInfo { return new TestInfo( diff --git a/src/Internal/CeptProvider.php b/src/Internal/CeptProvider.php index b98869d..4de726b 100644 --- a/src/Internal/CeptProvider.php +++ b/src/Internal/CeptProvider.php @@ -63,6 +63,7 @@ public static function createForChain(Cept $test, LinkTemplateCollectionInterfac return [new self($test, $linkTemplates)]; } + #[\Override] public function getLinks(): array { $this->loadLegacyModels(); @@ -70,6 +71,7 @@ public function getLinks(): array return $this->legacyLinks; } + #[\Override] public function getLabels(): array { $this->loadLegacyModels(); @@ -77,11 +79,13 @@ public function getLabels(): array return $this->legacyLabels; } + #[\Override] public function getParameters(): array { return []; } + #[\Override] public function getDisplayName(): ?string { $this->loadLegacyModels(); @@ -98,11 +102,13 @@ public function getDisplayName(): ?string : null; } + #[\Override] public function getFullName(): ?string { return $this->test->getSignature(); } + #[\Override] public function getDescription(): ?string { $this->loadLegacyModels(); @@ -110,6 +116,7 @@ public function getDescription(): ?string return $this->legacyDescription; } + #[\Override] public function getDescriptionHtml(): ?string { return null; diff --git a/src/Internal/CestInfoBuilder.php b/src/Internal/CestInfoBuilder.php index 1b93087..634e427 100644 --- a/src/Internal/CestInfoBuilder.php +++ b/src/Internal/CestInfoBuilder.php @@ -16,6 +16,7 @@ public function __construct( ) { } + #[\Override] public function build(?string $host, ?string $thread): TestInfo { return new TestInfo( diff --git a/src/Internal/CestProvider.php b/src/Internal/CestProvider.php index 6a25730..8ae5746 100644 --- a/src/Internal/CestProvider.php +++ b/src/Internal/CestProvider.php @@ -49,16 +49,19 @@ classOrObject: $test->getTestInstance(), ]; } + #[\Override] public function getLinks(): array { return []; } + #[\Override] public function getLabels(): array { return []; } + #[\Override] public function getParameters(): array { /** @var mixed $currentExample */ @@ -80,6 +83,7 @@ public function getParameters(): array ); } + #[\Override] public function getDisplayName(): ?string { /** @psalm-var mixed $displayName */ @@ -90,11 +94,13 @@ public function getDisplayName(): ?string : null; } + #[\Override] public function getDescription(): ?string { return null; } + #[\Override] public function getDescriptionHtml(): ?string { return null; @@ -104,6 +110,7 @@ public function getDescriptionHtml(): ?string * @psalm-suppress MixedOperand * @psalm-suppress MixedArgument */ + #[\Override] public function getFullName(): ?string { return $this->test->getTestInstance()::class . "::" . $this->test->getTestMethod(); diff --git a/src/Internal/DefaultThreadDetector.php b/src/Internal/DefaultThreadDetector.php index 06bbd63..63c141f 100644 --- a/src/Internal/DefaultThreadDetector.php +++ b/src/Internal/DefaultThreadDetector.php @@ -12,6 +12,7 @@ final class DefaultThreadDetector implements ThreadDetectorInterface { private string|false|null $host = null; + #[\Override] public function getHost(): ?string { $this->host ??= gethostname(); @@ -21,6 +22,7 @@ public function getHost(): ?string : $this->host; } + #[\Override] public function getThread(): ?string { return null; diff --git a/src/Internal/GherkinInfoBuilder.php b/src/Internal/GherkinInfoBuilder.php index cc744ca..9d15b7d 100644 --- a/src/Internal/GherkinInfoBuilder.php +++ b/src/Internal/GherkinInfoBuilder.php @@ -15,6 +15,7 @@ public function __construct( ) { } + #[\Override] public function build(?string $host, ?string $thread): TestInfo { return new TestInfo( diff --git a/src/Internal/GherkinProvider.php b/src/Internal/GherkinProvider.php index 415768b..c0cd698 100644 --- a/src/Internal/GherkinProvider.php +++ b/src/Internal/GherkinProvider.php @@ -26,11 +26,13 @@ public static function createForChain(Gherkin $test): array return [new self($test)]; } + #[\Override] public function getLinks(): array { return []; } + #[\Override] public function getLabels(): array { return array_map( @@ -42,26 +44,31 @@ public function getLabels(): array ); } + #[\Override] public function getParameters(): array { return []; } + #[\Override] public function getDisplayName(): ?string { return $this->test->toString(); } + #[\Override] public function getDescription(): ?string { return null; } + #[\Override] public function getDescriptionHtml(): ?string { return null; } + #[\Override] public function getFullName(): ?string { return null; diff --git a/src/Internal/SuiteProvider.php b/src/Internal/SuiteProvider.php index 5b6a01f..8a04d3f 100644 --- a/src/Internal/SuiteProvider.php +++ b/src/Internal/SuiteProvider.php @@ -41,11 +41,13 @@ public static function createForChain( : $providers; } + #[\Override] public function getLinks(): array { return []; } + #[\Override] public function getLabels(): array { return [ @@ -56,26 +58,31 @@ public function getLabels(): array ]; } + #[\Override] public function getParameters(): array { return []; } + #[\Override] public function getDisplayName(): ?string { return null; } + #[\Override] public function getDescription(): ?string { return null; } + #[\Override] public function getDescriptionHtml(): ?string { return null; } + #[\Override] public function getFullName(): ?string { return null; diff --git a/src/Internal/TestInfoProvider.php b/src/Internal/TestInfoProvider.php index b813c10..fe40b3e 100644 --- a/src/Internal/TestInfoProvider.php +++ b/src/Internal/TestInfoProvider.php @@ -23,11 +23,13 @@ public static function createForChain(TestInfo $info): array return [new self($info)]; } + #[\Override] public function getLinks(): array { return []; } + #[\Override] public function getLabels(): array { return [ @@ -38,26 +40,31 @@ public function getLabels(): array ]; } + #[\Override] public function getParameters(): array { return []; } + #[\Override] public function getDisplayName(): ?string { return null; } + #[\Override] public function getDescription(): ?string { return null; } + #[\Override] public function getDescriptionHtml(): ?string { return null; } + #[\Override] public function getFullName(): ?string { return null; diff --git a/src/Internal/TestLifecycle.php b/src/Internal/TestLifecycle.php index 725524a..4c24bca 100644 --- a/src/Internal/TestLifecycle.php +++ b/src/Internal/TestLifecycle.php @@ -79,6 +79,7 @@ public function getCurrentStepStart(): StepStartInfo return $this->currentStepStart ?? throw new RuntimeException("Current step start not found"); } + #[\Override] public function switchToSuite(SuiteInfo $suiteInfo): self { $this->currentSuite = $suiteInfo; @@ -86,6 +87,7 @@ public function switchToSuite(SuiteInfo $suiteInfo): self return $this; } + #[\Override] public function resetSuite(): self { $this->currentSuite = null; @@ -93,6 +95,7 @@ public function resetSuite(): self return $this; } + #[\Override] public function switchToTest(object $test): self { $thread = $this->threadDetector->getThread(); @@ -119,6 +122,7 @@ private function getTestInfoBuilder(object $test): TestInfoBuilderInterface }; } + #[\Override] public function create(): self { $containerResult = $this->resultFactory->createContainer(); @@ -135,6 +139,7 @@ public function create(): self return $this; } + #[\Override] public function updateTest(): self { $provider = new ModelProviderChain( @@ -169,6 +174,7 @@ private function createModelProvidersForTest(mixed $test): array }; } + #[\Override] public function startTest(): self { $this->lifecycle->startTest($this->getCurrentTestStart()->getTestUuid()); @@ -176,6 +182,7 @@ public function startTest(): self return $this; } + #[\Override] public function stopTest(): self { $testUuid = $this->getCurrentTestStart()->getTestUuid(); @@ -196,6 +203,7 @@ public function stopTest(): self return $this; } + #[\Override] public function updateTestFailure( Throwable $error, ?Status $status = null, @@ -210,6 +218,7 @@ public function updateTestFailure( return $this; } + #[\Override] public function updateTestSuccess(): self { $this->lifecycle->updateTest( @@ -219,6 +228,7 @@ public function updateTestSuccess(): self return $this; } + #[\Override] public function attachReports(): self { $originalTest = $this->getCurrentTest()->getOriginalTest(); @@ -247,6 +257,7 @@ public function attachReports(): self return $this; } + #[\Override] public function updateTestResult(): self { $this->lifecycle->updateTest( @@ -291,6 +302,7 @@ private function buildHistoryId(string $testCaseId, TestInfo $testInfo, Paramete return md5("$testCaseId::{$testInfo->getSignature()}::$parameterNames"); } + #[\Override] public function startStep(Step $step): self { $stepResult = $this->resultFactory->createStep(); @@ -306,6 +318,7 @@ public function startStep(Step $step): self return $this; } + #[\Override] public function switchToStep(Step $step): self { $this->currentStepStart = @@ -314,6 +327,7 @@ public function switchToStep(Step $step): self return $this; } + #[\Override] public function stopStep(): self { $stepStart = $this->getCurrentStepStart(); @@ -332,6 +346,7 @@ public function stopStep(): self return $this; } + #[\Override] public function updateStep(): self { $stepStart = $this->getCurrentStepStart(); @@ -357,6 +372,7 @@ public function updateStep(): self return $this; } + #[\Override] public function updateStepResult(): self { $this->lifecycle->updateStep( diff --git a/src/Internal/UnitInfoBuilder.php b/src/Internal/UnitInfoBuilder.php index 524be55..8b2711d 100644 --- a/src/Internal/UnitInfoBuilder.php +++ b/src/Internal/UnitInfoBuilder.php @@ -16,6 +16,7 @@ public function __construct( ) { } + #[\Override] public function build(?string $host, ?string $thread): TestInfo { $fields = $this->test->getReportFields(); diff --git a/src/Internal/UnitProvider.php b/src/Internal/UnitProvider.php index 82939a2..8d1394c 100644 --- a/src/Internal/UnitProvider.php +++ b/src/Internal/UnitProvider.php @@ -55,11 +55,13 @@ classOrObject: $class, ]; } + #[\Override] public function getLinks(): array { return []; } + #[\Override] public function getLabels(): array { return []; @@ -68,6 +70,7 @@ public function getLabels(): array /** * @throws ReflectionException */ + #[\Override] public function getParameters(): array { $testMetadata = $this->test->getMetadata(); @@ -100,21 +103,25 @@ public function getParameters(): array return $params; } + #[\Override] public function getDisplayName(): ?string { return $this->test->getMetadata()->getName(); } + #[\Override] public function getDescription(): ?string { return null; } + #[\Override] public function getDescriptionHtml(): ?string { return null; } + #[\Override] public function getFullName(): ?string { return $this->test->getTestCase()::class . '::' . $this->test->getMetadata()->getName(); diff --git a/src/Internal/UnknownInfoBuilder.php b/src/Internal/UnknownInfoBuilder.php index 9944612..223e66f 100644 --- a/src/Internal/UnknownInfoBuilder.php +++ b/src/Internal/UnknownInfoBuilder.php @@ -11,6 +11,7 @@ public function __construct( ) { } + #[\Override] public function build(?string $host, ?string $thread): TestInfo { return new TestInfo( diff --git a/test/codeception-report/unit/AnnotationTest.php b/test/codeception-report/unit/AnnotationTest.php index 081d181..91886f0 100644 --- a/test/codeception-report/unit/AnnotationTest.php +++ b/test/codeception-report/unit/AnnotationTest.php @@ -7,7 +7,7 @@ use Codeception\Test\Unit; use Qameta\Allure\Attribute; -class AnnotationTest extends Unit +final class AnnotationTest extends Unit { #[Attribute\DisplayName('Test title')] public function testTitleAnnotation(): void diff --git a/test/codeception-report/unit/DataProviderTest.php b/test/codeception-report/unit/DataProviderTest.php index 670050e..dd8c903 100644 --- a/test/codeception-report/unit/DataProviderTest.php +++ b/test/codeception-report/unit/DataProviderTest.php @@ -7,7 +7,7 @@ use Codeception\Test\Unit; use Qameta\Allure\Attribute; -class DataProviderTest extends Unit +final class DataProviderTest extends Unit { /** * @dataProvider providerData diff --git a/test/codeception-report/unit/StepsTest.php b/test/codeception-report/unit/StepsTest.php index fbb53de..2bb7bfb 100644 --- a/test/codeception-report/unit/StepsTest.php +++ b/test/codeception-report/unit/StepsTest.php @@ -12,7 +12,7 @@ use Codeception\Test\Unit; use Exception; -class StepsTest extends Unit +final class StepsTest extends Unit { public function testNoStepsSuccess(): void { @@ -94,6 +94,7 @@ public function __construct(string $failure, string $action, array $arguments = $this->failure = $failure; } + #[\Override] public function run(ModuleContainer $container = null): void { $this->setFailed(true); diff --git a/test/codeception/report-check/ReportTest.php b/test/codeception/report-check/ReportTest.php index 09f74f5..509d353 100644 --- a/test/codeception/report-check/ReportTest.php +++ b/test/codeception/report-check/ReportTest.php @@ -8,46 +8,37 @@ use Qameta\Allure\Codeception\Test\Report\Functional\NestedStepsCest; use Qameta\Allure\Codeception\Test\Report\Unit\AnnotationTest; use Qameta\Allure\Codeception\Test\Report\Unit\StepsTest; -use Remorhaz\JSON\Data\Value\EncodedJson\NodeValueFactory; -use Remorhaz\JSON\Data\Value\NodeValueInterface; -use Remorhaz\JSON\Path\Processor\Processor; -use Remorhaz\JSON\Path\Processor\ProcessorInterface; -use Remorhaz\JSON\Path\Query\QueryFactory; -use Remorhaz\JSON\Path\Query\QueryFactoryInterface; use RuntimeException; +use function array_filter; +use function array_map; +use function array_values; use function file_get_contents; use function is_file; +use function json_decode; use function pathinfo; use function scandir; use function str_ends_with; use const DIRECTORY_SEPARATOR; +use const JSON_THROW_ON_ERROR; use const PATHINFO_EXTENSION; -class ReportTest extends Unit +final class ReportTest extends Unit { /** - * @var array> + * @var array> */ private static array $testResults = []; - private ?ProcessorInterface $jsonPathProcessor = null; - - private ?QueryFactoryInterface $jsonPathQueryFactory = null; - + #[\Override] public static function setUpBeforeClass(): void { $buildPath = __DIR__ . '/../../../build/allure-results'; $files = scandir($buildPath); - - $jsonValueFactory = NodeValueFactory::create(); - $jsonPathProcessor = Processor::create(); - $jsonPathQueryFactory = QueryFactory::create(); - $testMethodsQuery = $jsonPathQueryFactory - ->createQuery('$.labels[?(@.name=="testMethod")].value'); - $testClassesQuery = $jsonPathQueryFactory - ->createQuery('$.labels[?(@.name=="testClass")].value'); + if ($files === false || empty($files)) { + throw new RuntimeException("No test results found. Run 'composer test-report-generate' first"); + } foreach ($files as $fileName) { $file = $buildPath . DIRECTORY_SEPARATOR . $fileName; @@ -61,21 +52,17 @@ public static function setUpBeforeClass(): void continue; } $fileContent = file_get_contents($file); - $data = $jsonValueFactory->createValue($fileContent); - /** @var mixed $class */ - $class = $jsonPathProcessor - ->select($testClassesQuery, $data) - ->decode()[0] ?? null; - /** @var mixed $method */ - $method = $jsonPathProcessor - ->select($testMethodsQuery, $data) - ->decode()[0] ?? null; - if (!isset($class, $method)) { - throw new RuntimeException("Test not found in file $file"); + if ($fileContent === false) { + throw new RuntimeException("Can't read " . $file); } - self::assertIsString($class); - self::assertIsString($method); - self::$testResults[$class][$method] = $data; + + $testResult = json_decode($fileContent, flags: JSON_THROW_ON_ERROR); + self::assertIsObject($testResult); + + $class = self::findLabel($testResult, "testClass"); + $method = self::findLabel($testResult, "testMethod"); + + self::$testResults[$class][$method] = $testResult; } } } @@ -83,34 +70,27 @@ public static function setUpBeforeClass(): void /** * @param string $class * @param string $method - * @param string $jsonPath + * @param callable(object): mixed $selector * @param non-empty-string $expectedValue * @dataProvider providerSingleNodeValueStartsFromString */ public function testSingleNodeValueStartsFromString( string $class, string $method, - string $jsonPath, + callable $selector, string $expectedValue ): void { - /** @psalm-var mixed $nodes */ - $nodes = $this - ->getJsonPathProcessor() - ->select( - $this->getJsonPathQueryFactory()->createQuery($jsonPath), - self::$testResults[$class][$method] - ?? throw new RuntimeException("Result not found for $class::$method"), - ) - ->decode(); - self::assertIsArray($nodes); - self::assertCount(1, $nodes); - $value = $nodes[0] ?? null; - self::assertIsString($value); - self::assertStringStartsWith($expectedValue, $value); + $testResult = self::$testResults[$class][$method] + ?? throw new RuntimeException("Result not found for $class::$method"); + + $actualResult = $selector($testResult); + + self::assertIsString($actualResult); + self::assertStringStartsWith($expectedValue, $actualResult); } /** - * @return iterable + * @return iterable */ public static function providerSingleNodeValueStartsFromString(): iterable { @@ -118,7 +98,7 @@ public static function providerSingleNodeValueStartsFromString(): iterable 'Error message in test case without steps' => [ StepsTest::class, 'testNoStepsError', - '$.statusDetails.message', + fn (object $tr): mixed => self::property(self::objectProperty($tr, 'statusDetails'), 'message'), "Error\nException(0)", ], ]; @@ -126,26 +106,25 @@ public static function providerSingleNodeValueStartsFromString(): iterable /** * @dataProvider providerExistingNodeValue + * @param callable(object): mixed $selector */ public function testExistingNodeValue( string $class, string $method, - string $jsonPath, - array $expected + callable $selector, + mixed $expected ): void { - $nodes = $this - ->getJsonPathProcessor() - ->select( - $this->getJsonPathQueryFactory()->createQuery($jsonPath), - self::$testResults[$class][$method] - ?? throw new RuntimeException("Result not found for $class::$method"), - ) - ->decode(); - self::assertSame($expected, $nodes); + $testResult = self::$testResults[$class][$method] + ?? throw new RuntimeException("Result not found for $class::$method"); + + /** @psalm-var mixed $actualResult */ + $actualResult = $selector($testResult); + + self::assertSame($expected, $actualResult); } /** - * @return iterable}> + * @return iterable */ public static function providerExistingNodeValue(): iterable { @@ -153,217 +132,335 @@ public static function providerExistingNodeValue(): iterable 'Test case title annotation' => [ AnnotationTest::class, 'testTitleAnnotation', - '$.name', - ['Test title'], + fn (object $tr): mixed => $tr->name, + 'Test title', ], 'Test case severity annotation' => [ AnnotationTest::class, 'testSeverityAnnotation', - '$.labels[?(@.name=="severity")].value', - ['minor'], + fn (object $tr): mixed => self::findLabel($tr, "severity"), + 'minor', ], 'Test case parameter annotation' => [ AnnotationTest::class, 'testParameterAnnotation', - '$.parameters[?(@.name=="foo")].value', - ['bar'], + fn (object $tr): mixed => self::findParameter($tr, "foo")->value, + 'bar', ], 'Test case stories annotation' => [ AnnotationTest::class, 'testStoriesAnnotation', - '$.labels[?(@.name=="story")].value', + fn (object $tr): mixed => self::findLabels($tr, "story"), ['Story 2', 'Story 1'], ], 'Test case features annotation' => [ AnnotationTest::class, 'testFeaturesAnnotation', - '$.labels[?(@.name=="feature")].value', + fn (object $tr): mixed => self::findLabels($tr, "feature"), ['Feature 2', 'Feature 1'], ], 'Successful test case without steps' => [ StepsTest::class, 'testNoStepsSuccess', - '$.status', - ['passed'], + fn (object $tr): mixed => $tr->status, + 'passed', ], 'Successful test case without steps: no steps' => [ StepsTest::class, 'testNoStepsSuccess', - '$.steps[*]', + fn (object $tr): mixed => self::objectListProperty($tr, 'steps'), [], ], 'Error in test case without steps' => [ StepsTest::class, 'testNoStepsError', - '$.status', - ['broken'], + fn (object $tr): mixed => $tr->status, + 'broken', ], 'Failure message in test case without steps' => [ StepsTest::class, 'testNoStepsFailure', - '$.statusDetails.message', - ['Failure'], + fn (object $tr): mixed => self::property(self::objectProperty($tr, 'statusDetails'), 'message'), + 'Failure', ], 'Test case without steps skipped' => [ StepsTest::class, 'testNoStepsSkipped', - '$.status', - ['skipped'], + fn (object $tr): mixed => $tr->status, + 'skipped', ], 'Skipped message in test case without steps' => [ StepsTest::class, 'testNoStepsSkipped', - '$.statusDetails.message', - ['Skipped'], + fn (object $tr): mixed => self::property(self::objectProperty($tr, 'statusDetails'), 'message'), + 'Skipped', ], 'Successful test case with single step: status' => [ StepsTest::class, 'testSingleSuccessfulStepWithTitle', - '$.status', - ['passed'], + fn (object $tr): mixed => $tr->status, + 'passed', ], 'Successful test case with single step: step status' => [ StepsTest::class, 'testSingleSuccessfulStepWithTitle', - '$.steps[*].status', - ['passed'], + fn (object $tr): mixed => self::singleStep($tr)->status, + 'passed', ], 'Successful test case with single step: step name' => [ StepsTest::class, 'testSingleSuccessfulStepWithTitle', - '$.steps[*].name', - ['step 1 name'], + fn (object $tr): mixed => self::singleStep($tr)->name, + 'step 1 name', ], 'Successful test case with arguments in step: status' => [ StepsTest::class, 'testSingleSuccessfulStepWithArguments', - '$.status', - ['passed'], + fn (object $tr): mixed => $tr->status, + 'passed', ], 'Successful test case with arguments in step: step status' => [ StepsTest::class, 'testSingleSuccessfulStepWithArguments', - '$.steps[*].status', - ['passed'], + fn (object $tr): mixed => self::singleStep($tr)->status, + 'passed', ], 'Successful test case with arguments in step: step name' => [ StepsTest::class, 'testSingleSuccessfulStepWithArguments', - '$.steps[*].name', - ['step 1 name'], + fn (object $tr): mixed => self::singleStep($tr)->name, + 'step 1 name', ], 'Successful test case with arguments in step: step parameter' => [ StepsTest::class, 'testSingleSuccessfulStepWithArguments', - '$.steps[*].parameters[?(@.name=="foo")].value', - ['"bar"'], + fn (object $tr): mixed => self::findParameter(self::singleStep($tr), "foo")->value, + '"bar"', ], 'Successful test case with two successful steps: status' => [ StepsTest::class, 'testTwoSuccessfulSteps', - '$.status', - ['passed'], + fn (object $tr): mixed => $tr->status, + 'passed', ], 'Successful test case with two successful steps: step status' => [ StepsTest::class, 'testTwoSuccessfulSteps', - '$.steps[*].status', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'status'), + self::objectListProperty($tr, 'steps'), + ), ['passed', 'passed'], ], 'Successful test case with two successful steps: step name' => [ StepsTest::class, 'testTwoSuccessfulSteps', - '$.steps[*].name', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'name'), + self::objectListProperty($tr, 'steps'), + ), ['step 1 name', 'step 2 name'], ], 'First step in test case with two steps fails: status' => [ StepsTest::class, 'testTwoStepsFirstFails', - '$.status', - ['failed'], + fn (object $tr): mixed => $tr->status, + 'failed', ], 'First step in test case with two steps fails: message' => [ StepsTest::class, 'testTwoStepsFirstFails', - '$.statusDetails.message', - ['Failure'], + fn (object $tr): mixed => self::property(self::objectProperty($tr, 'statusDetails'), 'message'), + 'Failure', ], 'First step in test case with two steps fails: step status' => [ StepsTest::class, 'testTwoStepsFirstFails', - '$.steps[*].status', - ['failed'], + fn (object $tr): mixed => self::singleStep($tr)->status, + 'failed', ], 'First step in test case with two steps fails: step name' => [ StepsTest::class, 'testTwoStepsFirstFails', - '$.steps[*].name', - ['step 1 name'], + fn (object $tr): mixed => self::singleStep($tr)->name, + 'step 1 name', ], 'Second step in test case with two steps fails: status' => [ StepsTest::class, 'testTwoStepsSecondFails', - '$.status', - ['failed'], + fn (object $tr): mixed => $tr->status, + 'failed', ], 'Second step in test case with two steps fails: message' => [ StepsTest::class, 'testTwoStepsSecondFails', - '$.statusDetails.message', - ['Failure'], + fn (object $tr): mixed => self::property(self::objectProperty($tr, 'statusDetails'), 'message'), + 'Failure', ], 'Second step in test case with two steps fails: step status' => [ StepsTest::class, 'testTwoStepsSecondFails', - '$.steps[*].status', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'status'), + self::objectListProperty($tr, 'steps'), + ), ['passed', 'failed'], ], 'Second step in test case with two steps fails: step name' => [ StepsTest::class, 'testTwoStepsSecondFails', - '$.steps[*].name', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'name'), + self::objectListProperty($tr, 'steps'), + ), ['step 1 name', 'step 2 name'], ], 'Nested steps: root names' => [ NestedStepsCest::class, 'makeNestedSteps', - '$.steps[*].name', - ['Step 1'], + fn (object $tr): mixed => self::singleStep($tr)->name, + 'Step 1', ], - 'Nested steps: level 1 names' => [ + 'Nested steps: step 1 substep names' => [ NestedStepsCest::class, 'makeNestedSteps', - '$.steps[?(@.name=="Step 1")].steps[*].name', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'name'), + self::objectListProperty(self::singleStep($tr), 'steps'), + ), ['i expect condition 1', 'Step 1.1', 'Step 1.2'], ], - 'Nested steps: level 1.1 names' => [ + 'Nested steps: step 1.1 substep names' => [ NestedStepsCest::class, 'makeNestedSteps', - '$.steps..steps[?(@.name=="Step 1.1")].steps[*].name', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'name'), + self::objectListProperty(self::findStep(self::singleStep($tr), "Step 1.1"), 'steps'), + ), ['i expect condition 1.1', 'Step 1.1.1'], ], 'Nested steps: level 1.1.1 names' => [ NestedStepsCest::class, 'makeNestedSteps', - '$.steps..steps[?(@.name=="Step 1.1.1")].steps[*].name', - ['i expect condition 1.1.1'], + fn (object $tr): mixed => self::singleStep( + self::findStep( + self::findStep( + self::singleStep($tr), + "Step 1.1" + ), + "Step 1.1.1", + ), + )->name, + 'i expect condition 1.1.1', ], 'Nested steps: level 1.2 names' => [ NestedStepsCest::class, 'makeNestedSteps', - '$.steps..steps[?(@.name=="Step 1.2")].steps[*].name', + fn (object $tr): mixed => array_map( + fn (object $s): mixed => self::property($s, 'name'), + self::objectListProperty(self::findStep(self::singleStep($tr), "Step 1.2"), 'steps'), + ), ['i expect condition 1.2'], ], ]; } - private function getJsonPathProcessor(): ProcessorInterface + /** + * @return list + */ + private static function findLabels(object $testResult, string $name): array + { + $labels = self::objectListProperty($testResult, 'labels'); + + $values = array_values( + array_map( + fn (object $label): mixed => $label->value, + array_filter( + $labels, + fn (object $label): bool => $label->name === $name, + ), + ) + ); + + $result = []; + foreach ($values as $value) { + self::assertIsString($value); + $result[] = $value; + } + + return $result; + } + + private static function findLabel(object $testResult, string $name): string + { + $labels = self::findLabels($testResult, $name); + + self::assertCount(1, $labels); + + return $labels[0]; + } + + private static function findStep(object $parent, string $name): object + { + $steps = self::objectListProperty($parent, 'steps'); + + $steps = array_values( + array_filter( + $steps, + fn (object $step): bool => $step->name === $name, + ), + ); + + self::assertCount(1, $steps); + + return $steps[0]; + } + + private static function singleStep(object $parent): object + { + $steps = self::objectListProperty($parent, 'steps'); + + self::assertCount(1, $steps); + + return $steps[0]; + } + + private static function findParameter(object $testResult, string $name): object { - return $this->jsonPathProcessor ??= Processor::create(); + $parameters = self::objectListProperty($testResult, 'parameters'); + + $parameters = array_values(array_filter( + $parameters, + fn (object $parameter): bool => $parameter->name === $name, + )); + + self::assertCount(1, $parameters); + + return $parameters[0]; } - private function getJsonPathQueryFactory(): QueryFactoryInterface + private static function property(object $value, string $property): mixed { - return $this->jsonPathQueryFactory ??= QueryFactory::create(); + return $value->{$property}; + } + + private static function objectProperty(object $value, string $property): object + { + $result = self::property($value, $property); + self::assertIsObject($result); + + return $result; + } + + /** + * @return list + */ + private static function objectListProperty(object $value, string $property): array + { + $result = self::property($value, $property); + self::assertIsArray($result); + + /** @var list */ + return array_values($result); } } diff --git a/test/codeception/unit/Internal/ArgumentAsStringTest.php b/test/codeception/unit/Internal/ArgumentAsStringTest.php index a77cb87..8aa2411 100644 --- a/test/codeception/unit/Internal/ArgumentAsStringTest.php +++ b/test/codeception/unit/Internal/ArgumentAsStringTest.php @@ -7,7 +7,7 @@ use Codeception\Test\Unit; use Qameta\Allure\Codeception\Internal\ArgumentAsString; -class ArgumentAsStringTest extends Unit +final class ArgumentAsStringTest extends Unit { /** * @dataProvider providerString diff --git a/test/codeception/unit/Internal/CestProviderTest.php b/test/codeception/unit/Internal/CestProviderTest.php index 42913c1..4a799b1 100644 --- a/test/codeception/unit/Internal/CestProviderTest.php +++ b/test/codeception/unit/Internal/CestProviderTest.php @@ -8,7 +8,7 @@ use Codeception\Test\Unit; use Qameta\Allure\Codeception\Internal\CestProvider; -class CestProviderTest extends Unit +final class CestProviderTest extends Unit { public function testFullName(): void {