@@ -70,12 +70,14 @@ func (s *suite) matchStep(step *messages.PickleStep) *models.StepDefinition {
70
70
return def
71
71
}
72
72
73
- func (s * suite ) runStep (ctx context.Context , pickle * Scenario , step * Step , prevStepErr error ) (rctx context.Context , err error ) {
73
+ func (s * suite ) runStep (ctx context.Context , pickle * Scenario , step * Step , prevStepErr error , isFirst , isLast bool ) (rctx context.Context , err error ) {
74
74
var (
75
75
match * models.StepDefinition
76
76
sr = models.PickleStepResult {Status : models .Undefined }
77
77
)
78
78
79
+ rctx = ctx
80
+
79
81
// user multistep definitions may panic
80
82
defer func () {
81
83
if e := recover (); e != nil {
@@ -87,20 +89,7 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
87
89
88
90
defer func () {
89
91
// run after step handlers
90
- for _ , f := range s .afterStepHandlers {
91
- hctx , herr := f (rctx , step , sr .Status , err )
92
-
93
- // Adding hook error to resulting error without breaking hooks loop.
94
- if herr != nil {
95
- if err == nil {
96
- err = herr
97
- } else {
98
- err = fmt .Errorf ("%v: %w" , herr , err )
99
- }
100
- }
101
-
102
- rctx = hctx
103
- }
92
+ rctx , err = s .runAfterStepHooks (ctx , step , sr .Status , err )
104
93
}()
105
94
106
95
if prevStepErr != nil {
@@ -113,6 +102,11 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
113
102
114
103
sr = models .NewStepResult (pickle .Id , step .Id , match )
115
104
105
+ // Trigger after scenario on failing or last step to attach possible hook error to step.
106
+ if (err == nil && isLast ) || err != nil {
107
+ rctx , err = s .runAfterScenarioHooks (rctx , pickle , err )
108
+ }
109
+
116
110
switch err {
117
111
case nil :
118
112
sr .Status = models .Passed
@@ -133,18 +127,26 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
133
127
}
134
128
}()
135
129
136
- // run before step handlers
137
- for _ , f := range s .beforeStepHandlers {
138
- ctx , err = f (ctx , step )
139
- if err != nil {
140
- return ctx , err
141
- }
130
+ // run before scenario handlers
131
+ if isFirst {
132
+ ctx , err = s .runBeforeScenarioHooks (ctx , pickle )
142
133
}
143
134
144
135
match = s .matchStep (step )
145
136
s .storage .MustInsertStepDefintionMatch (step .AstNodeIds [0 ], match )
146
137
s .fmt .Defined (pickle , step , match .GetInternalStepDefinition ())
147
138
139
+ // run before step handlers
140
+ ctx , err = s .runBeforeStepHooks (ctx , step , err )
141
+
142
+ if err != nil {
143
+ sr = models .NewStepResult (pickle .Id , step .Id , match )
144
+ sr .Status = models .Failed
145
+ s .storage .MustInsertPickleStepResult (sr )
146
+
147
+ return ctx , err
148
+ }
149
+
148
150
if ctx , undef , err := s .maybeUndefined (ctx , step .Text , step .Argument ); err != nil {
149
151
return ctx , err
150
152
} else if len (undef ) > 0 {
@@ -183,6 +185,118 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
183
185
return ctx , err
184
186
}
185
187
188
+ func (s * suite ) runBeforeStepHooks (ctx context.Context , step * Step , err error ) (context.Context , error ) {
189
+ hooksFailed := false
190
+
191
+ for _ , f := range s .beforeStepHandlers {
192
+ hctx , herr := f (ctx , step )
193
+ if herr != nil {
194
+ hooksFailed = true
195
+
196
+ if err == nil {
197
+ err = herr
198
+ } else {
199
+ err = fmt .Errorf ("%v, %w" , herr , err )
200
+ }
201
+ }
202
+
203
+ if hctx != nil {
204
+ ctx = hctx
205
+ }
206
+ }
207
+
208
+ if hooksFailed {
209
+ err = fmt .Errorf ("before step hook failed: %w" , err )
210
+ }
211
+
212
+ return ctx , err
213
+ }
214
+
215
+ func (s * suite ) runAfterStepHooks (ctx context.Context , step * Step , status StepResultStatus , err error ) (context.Context , error ) {
216
+ for _ , f := range s .afterStepHandlers {
217
+ hctx , herr := f (ctx , step , status , err )
218
+
219
+ // Adding hook error to resulting error without breaking hooks loop.
220
+ if herr != nil {
221
+ if err == nil {
222
+ err = herr
223
+ } else {
224
+ err = fmt .Errorf ("%v, %w" , herr , err )
225
+ }
226
+ }
227
+
228
+ if hctx != nil {
229
+ ctx = hctx
230
+ }
231
+ }
232
+
233
+ return ctx , err
234
+ }
235
+
236
+ func (s * suite ) runBeforeScenarioHooks (ctx context.Context , pickle * messages.Pickle ) (context.Context , error ) {
237
+ var err error
238
+
239
+ // run before scenario handlers
240
+ for _ , f := range s .beforeScenarioHandlers {
241
+ hctx , herr := f (ctx , pickle )
242
+ if herr != nil {
243
+ if err == nil {
244
+ err = herr
245
+ } else {
246
+ err = fmt .Errorf ("%v, %w" , herr , err )
247
+ }
248
+ }
249
+
250
+ if hctx != nil {
251
+ ctx = hctx
252
+ }
253
+ }
254
+
255
+ if err != nil {
256
+ err = fmt .Errorf ("before scenario hook failed: %w" , err )
257
+ }
258
+
259
+ return ctx , err
260
+ }
261
+
262
+ func (s * suite ) runAfterScenarioHooks (ctx context.Context , pickle * messages.Pickle , lastStepErr error ) (context.Context , error ) {
263
+ err := lastStepErr
264
+
265
+ hooksFailed := false
266
+ isStepErr := true
267
+
268
+ // run after scenario handlers
269
+ for _ , f := range s .afterScenarioHandlers {
270
+ hctx , herr := f (ctx , pickle , err )
271
+
272
+ // Adding hook error to resulting error without breaking hooks loop.
273
+ if herr != nil {
274
+ hooksFailed = true
275
+
276
+ if err == nil {
277
+ isStepErr = false
278
+ err = herr
279
+ } else {
280
+ if isStepErr {
281
+ err = fmt .Errorf ("step error: %w" , err )
282
+ isStepErr = false
283
+ }
284
+ err = fmt .Errorf ("%v, %w" , herr , err )
285
+ }
286
+ }
287
+
288
+ if hctx != nil {
289
+ ctx = hctx
290
+ }
291
+ }
292
+
293
+ if hooksFailed {
294
+ err = fmt .Errorf ("after scenario hook failed: %w" , err )
295
+ }
296
+
297
+ return ctx , err
298
+ }
299
+
186
300
func (s * suite ) maybeUndefined (ctx context.Context , text string , arg interface {}) (context.Context , []string , error ) {
187
301
step := s .matchStepText (text )
188
302
if nil == step {
@@ -271,8 +385,10 @@ func (s *suite) runSteps(ctx context.Context, pickle *Scenario, steps []*Step) (
271
385
stepErr , err error
272
386
)
273
387
274
- for _ , step := range steps {
275
- ctx , stepErr = s .runStep (ctx , pickle , step , err )
388
+ for i , step := range steps {
389
+ isLast := i == len (steps )- 1
390
+ isFirst := i == 0
391
+ ctx , stepErr = s .runStep (ctx , pickle , step , err , isFirst , isLast )
276
392
switch stepErr {
277
393
case ErrUndefined :
278
394
// do not overwrite failed error
@@ -326,13 +442,8 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
326
442
return ErrUndefined
327
443
}
328
444
329
- // run before scenario handlers
330
- for _ , f := range s .beforeScenarioHandlers {
331
- ctx , err = f (ctx , pickle )
332
- if err != nil {
333
- return err
334
- }
335
- }
445
+ // Before scenario hooks are aclled in context of first evaluated step
446
+ // so that error from handler can be added to step.
336
447
337
448
pr := models.PickleResult {PickleID : pickle .Id , StartedAt : utils .TimeNowFunc ()}
338
449
s .storage .MustInsertPickleResult (pr )
@@ -342,21 +453,8 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
342
453
// scenario
343
454
ctx , err = s .runSteps (ctx , pickle , pickle .Steps )
344
455
345
- // run after scenario handlers
346
- for _ , f := range s .afterScenarioHandlers {
347
- hctx , herr := f (ctx , pickle , err )
348
-
349
- // Adding hook error to resulting error without breaking hooks loop.
350
- if herr != nil {
351
- if err == nil {
352
- err = herr
353
- } else {
354
- err = fmt .Errorf ("%v: %w" , herr , err )
355
- }
356
- }
357
-
358
- ctx = hctx
359
- }
456
+ // After scenario handlers are called in context of last evaluated step
457
+ // so that error from handler can be added to step.
360
458
361
459
return err
362
460
}
0 commit comments