@@ -23,7 +23,8 @@ class TestContextExtension extends \Codeception\Extension
2323 */
2424 public static $ events = [
2525 Events::TEST_FAIL => 'testFail ' ,
26- Events::STEP_AFTER => 'afterStep '
26+ Events::STEP_AFTER => 'afterStep ' ,
27+ Events::TEST_END => 'testError '
2728 ];
2829
2930 /**
@@ -38,22 +39,61 @@ public function testFail(\Codeception\Event\FailEvent $e)
3839 // Do not attempt to run _after if failure was in the _after block
3940 // Try to run _after but catch exceptions to prevent them from overwriting original failure.
4041 if ($ context != TestContextExtension::TEST_PHASE_AFTER ) {
41- try {
42- $ actorClass = $ e ->getTest ()->getMetadata ()->getCurrent ('actor ' );
43- $ I = new $ actorClass ($ cest ->getScenario ());
44- call_user_func (\Closure::bind (
45- function () use ($ cest , $ I ) {
46- $ cest ->executeHook ($ I , 'after ' );
47- },
48- null ,
49- $ cest
50- ));
51- } catch (\Exception $ e ) {
52- // Do not rethrow Exception
42+ $ this ->runAfterBlock ($ e , $ cest );
43+ }
44+ }
45+
46+ /**
47+ * Codeception event listener function, triggered on test error.
48+ * @param \Codeception\Event\TestEvent $e
49+ * @return void
50+ */
51+ public function testError (\Codeception \Event \TestEvent $ e )
52+ {
53+ $ cest = $ e ->getTest ();
54+
55+ //Access private TestResultObject to find stack and if there are any errors (as opposed to failures)
56+ $ testResultObject = call_user_func (\Closure::bind (
57+ function () use ($ cest ) {
58+ return $ cest ->getTestResultObject ();
59+ },
60+ $ cest
61+ ));
62+ $ errors = $ testResultObject ->errors ();
63+ if (!empty ($ errors )) {
64+ $ stack = $ errors [0 ]->thrownException ()->getTrace ();
65+ $ context = $ this ->extractContext ($ stack , $ cest ->getTestMethod ());
66+ // Do not attempt to run _after if failure was in the _after block
67+ // Try to run _after but catch exceptions to prevent them from overwriting original failure.
68+ if ($ context != TestContextExtension::TEST_PHASE_AFTER ) {
69+ $ this ->runAfterBlock ($ e , $ cest );
5370 }
5471 }
5572 }
5673
74+ /**
75+ * Runs cest's after block, if necessary.
76+ * @param Symfony\Component\EventDispatcher\Event $e
77+ * @param \Codeception\TestInterface $cest
78+ * @return void
79+ */
80+ private function runAfterBlock ($ e , $ cest )
81+ {
82+ try {
83+ $ actorClass = $ e ->getTest ()->getMetadata ()->getCurrent ('actor ' );
84+ $ I = new $ actorClass ($ cest ->getScenario ());
85+ call_user_func (\Closure::bind (
86+ function () use ($ cest , $ I ) {
87+ $ cest ->executeHook ($ I , 'after ' );
88+ },
89+ null ,
90+ $ cest
91+ ));
92+ } catch (\Exception $ e ) {
93+ // Do not rethrow Exception
94+ }
95+ }
96+
5797 /**
5898 * Extracts hook method from trace, looking specifically for the cest class given.
5999 * @param array $trace
0 commit comments