diff --git a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php index b51ebda0f..c0bf78f19 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -23,7 +23,8 @@ class TestContextExtension extends \Codeception\Extension */ public static $events = [ Events::TEST_FAIL => 'testFail', - Events::STEP_AFTER => 'afterStep' + Events::STEP_AFTER => 'afterStep', + Events::TEST_END => 'testError' ]; /** @@ -38,22 +39,61 @@ public function testFail(\Codeception\Event\FailEvent $e) // Do not attempt to run _after if failure was in the _after block // Try to run _after but catch exceptions to prevent them from overwriting original failure. if ($context != TestContextExtension::TEST_PHASE_AFTER) { - try { - $actorClass = $e->getTest()->getMetadata()->getCurrent('actor'); - $I = new $actorClass($cest->getScenario()); - call_user_func(\Closure::bind( - function () use ($cest, $I) { - $cest->executeHook($I, 'after'); - }, - null, - $cest - )); - } catch (\Exception $e) { - // Do not rethrow Exception + $this->runAfterBlock($e, $cest); + } + } + + /** + * Codeception event listener function, triggered on test error. + * @param \Codeception\Event\TestEvent $e + * @return void + */ + public function testError(\Codeception\Event\TestEvent $e) + { + $cest = $e->getTest(); + + //Access private TestResultObject to find stack and if there are any errors (as opposed to failures) + $testResultObject = call_user_func(\Closure::bind( + function () use ($cest) { + return $cest->getTestResultObject(); + }, + $cest + )); + $errors = $testResultObject->errors(); + if (!empty($errors)) { + $stack = $errors[0]->thrownException()->getTrace(); + $context = $this->extractContext($stack, $cest->getTestMethod()); + // Do not attempt to run _after if failure was in the _after block + // Try to run _after but catch exceptions to prevent them from overwriting original failure. + if ($context != TestContextExtension::TEST_PHASE_AFTER) { + $this->runAfterBlock($e, $cest); } } } + /** + * Runs cest's after block, if necessary. + * @param Symfony\Component\EventDispatcher\Event $e + * @param \Codeception\TestInterface $cest + * @return void + */ + private function runAfterBlock($e, $cest) + { + try { + $actorClass = $e->getTest()->getMetadata()->getCurrent('actor'); + $I = new $actorClass($cest->getScenario()); + call_user_func(\Closure::bind( + function () use ($cest, $I) { + $cest->executeHook($I, 'after'); + }, + null, + $cest + )); + } catch (\Exception $e) { + // Do not rethrow Exception + } + } + /** * Extracts hook method from trace, looking specifically for the cest class given. * @param array $trace