1313/**
1414 * Class TestContextExtension
1515 * @SuppressWarnings(PHPMD.UnusedPrivateField)
16+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1617 */
1718class TestContextExtension extends BaseExtension
1819{
1920 const TEST_PHASE_AFTER = "_after " ;
20- const CODECEPT_AFTER_VERSION = "2.3.9 " ;
21+ const TEST_PHASE_BEFORE = "_before " ;
22+
2123 const TEST_FAILED_FILE = 'failed ' ;
24+ const TEST_HOOKS = [
25+ self ::TEST_PHASE_AFTER => 'AfterHook ' ,
26+ self ::TEST_PHASE_BEFORE => 'BeforeHook '
27+ ];
2228
2329 /**
2430 * Codeception Events Mapping to methods
@@ -36,7 +42,6 @@ public function _initialize()
3642 {
3743 $ events = [
3844 Events::TEST_START => 'testStart ' ,
39- Events::TEST_FAIL => 'testFail ' ,
4045 Events::STEP_AFTER => 'afterStep ' ,
4146 Events::TEST_END => 'testEnd ' ,
4247 Events::RESULT_PRINT_AFTER => 'saveFailed '
@@ -57,23 +62,7 @@ public function testStart()
5762 }
5863
5964 /**
60- * Codeception event listener function, triggered on test failure.
61- * @param \Codeception\Event\FailEvent $e
62- * @return void
63- */
64- public function testFail (\Codeception \Event \FailEvent $ e )
65- {
66- $ cest = $ e ->getTest ();
67- $ context = $ this ->extractContext ($ e ->getFail ()->getTrace (), $ cest ->getTestMethod ());
68- // Do not attempt to run _after if failure was in the _after block
69- // Try to run _after but catch exceptions to prevent them from overwriting original failure.
70- if ($ context != TestContextExtension::TEST_PHASE_AFTER ) {
71- $ this ->runAfterBlock ($ e , $ cest );
72- }
73- }
74-
75- /**
76- * Codeception event listener function, triggered on test ending (naturally or by error).
65+ * Codeception event listener function, triggered on test ending naturally or by errors/failures.
7766 * @param \Codeception\Event\TestEvent $e
7867 * @return void
7968 * @throws \Exception
@@ -82,55 +71,33 @@ public function testEnd(\Codeception\Event\TestEvent $e)
8271 {
8372 $ cest = $ e ->getTest ();
8473
85- //Access private TestResultObject to find stack and if there are any errors (as opposed to failures)
74+ //Access private TestResultObject to find stack and if there are any errors/ failures
8675 $ testResultObject = call_user_func (\Closure::bind (
8776 function () use ($ cest ) {
8877 return $ cest ->getTestResultObject ();
8978 },
9079 $ cest
9180 ));
92- $ errors = $ testResultObject ->errors ();
93- if (!empty ($ errors )) {
94- foreach ($ errors as $ error ) {
95- if ($ error ->failedTest ()->getTestMethod () == $ cest ->getName ()) {
96- $ stack = $ errors [0 ]->thrownException ()->getTrace ();
97- $ context = $ this ->extractContext ($ stack , $ cest ->getTestMethod ());
98- // Do not attempt to run _after if failure was in the _after block
99- // Try to run _after but catch exceptions to prevent them from overwriting original failure.
100- if ($ context != TestContextExtension::TEST_PHASE_AFTER ) {
101- $ this ->runAfterBlock ($ e , $ cest );
102- }
103- continue ;
81+
82+ // check for errors in all test hooks and attach in allure
83+ if (!empty ($ testResultObject ->errors ())) {
84+ foreach ($ testResultObject ->errors () as $ error ) {
85+ if ($ error ->failedTest ()->getTestMethod () == $ cest ->getTestMethod ()) {
86+ $ this ->attachExceptionToAllure ($ error ->thrownException (), $ cest ->getTestMethod ());
10487 }
10588 }
10689 }
107- // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true
108- $ this ->getDriver ()->_runAfter ($ e ->getTest ());
109- }
11090
111- /**
112- * Runs cest's after block, if necessary.
113- * @param \Symfony\Component\EventDispatcher\Event $e
114- * @param \Codeception\TestInterface $cest
115- * @return void
116- */
117- private function runAfterBlock ($ e , $ cest )
118- {
119- try {
120- $ actorClass = $ e ->getTest ()->getMetadata ()->getCurrent ('actor ' );
121- $ I = new $ actorClass ($ cest ->getScenario ());
122- if (version_compare (\Codeception \Codecept::VERSION , TestContextExtension::CODECEPT_AFTER_VERSION , "<= " )) {
123- call_user_func (\Closure::bind (
124- function () use ($ cest , $ I ) {
125- $ cest ->executeHook ($ I , 'after ' );
126- },
127- null ,
128- $ cest
129- ));
91+ // check for failures in all test hooks and attach in allure
92+ if (!empty ($ testResultObject ->failures ())) {
93+ foreach ($ testResultObject ->failures () as $ failure ) {
94+ if ($ failure ->failedTest ()->getTestMethod () == $ cest ->getTestMethod ()) {
95+ $ this ->attachExceptionToAllure ($ failure ->thrownException (), $ cest ->getTestMethod ());
96+ }
13097 }
131- } catch (\Exception $ e ) {
132- // Do not rethrow Exception
13398 }
99+ // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true
100+ $ this ->getDriver ()->_runAfter ($ e ->getTest ());
134101 }
135102
136103 /**
@@ -150,6 +117,46 @@ public function extractContext($trace, $class)
150117 return null ;
151118 }
152119
120+ /**
121+ * Attach stack trace of exceptions thrown in each test hook to allure.
122+ * @param \Exception $exception
123+ * @param string $testMethod
124+ * @return mixed
125+ */
126+ public function attachExceptionToAllure ($ exception , $ testMethod )
127+ {
128+ if (is_subclass_of ($ exception , \PHPUnit \Framework \Exception::class)) {
129+ $ trace = $ exception ->getSerializableTrace ();
130+ } else {
131+ $ trace = $ exception ->getTrace ();
132+ }
133+
134+ $ context = $ this ->extractContext ($ trace , $ testMethod );
135+
136+ if (isset (self ::TEST_HOOKS [$ context ])) {
137+ $ context = self ::TEST_HOOKS [$ context ];
138+ } else {
139+ $ context = 'TestMethod ' ;
140+ }
141+
142+ AllureHelper::addAttachmentToCurrentStep ($ exception , $ context . 'Exception ' );
143+
144+ //pop suppressed exceptions and attach to allure
145+ $ change = function () {
146+ if ($ this instanceof \PHPUnit \Framework \ExceptionWrapper) {
147+ return $ this ->previous ;
148+ } else {
149+ return $ this ->getPrevious ();
150+ }
151+ };
152+
153+ $ previousException = $ change ->call ($ exception );
154+
155+ if ($ previousException !== null ) {
156+ $ this ->attachExceptionToAllure ($ previousException , $ testMethod );
157+ }
158+ }
159+
153160 /**
154161 * Codeception event listener function, triggered before step.
155162 * Check if it's a new page.
0 commit comments