@serenity-js/core 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/lib/Serenity.d.ts +2 -2
- package/lib/Serenity.d.ts.map +1 -1
- package/lib/events/EmitsDomainEvents.d.ts +11 -0
- package/lib/events/EmitsDomainEvents.d.ts.map +1 -0
- package/lib/events/EmitsDomainEvents.js +3 -0
- package/lib/events/EmitsDomainEvents.js.map +1 -0
- package/lib/events/index.d.ts +1 -0
- package/lib/events/index.d.ts.map +1 -1
- package/lib/events/index.js +1 -0
- package/lib/events/index.js.map +1 -1
- package/lib/io/asyncMap.js +2 -2
- package/lib/io/asyncMap.js.map +1 -1
- package/lib/screenplay/Actor.d.ts +3 -3
- package/lib/screenplay/Actor.d.ts.map +1 -1
- package/lib/screenplay/Actor.js +14 -86
- package/lib/screenplay/Actor.js.map +1 -1
- package/lib/screenplay/abilities/AnswerQuestions.d.ts +21 -0
- package/lib/screenplay/abilities/AnswerQuestions.d.ts.map +1 -0
- package/lib/screenplay/abilities/AnswerQuestions.js +37 -0
- package/lib/screenplay/abilities/AnswerQuestions.js.map +1 -0
- package/lib/screenplay/abilities/PerformActivities.d.ts +28 -0
- package/lib/screenplay/abilities/PerformActivities.d.ts.map +1 -0
- package/lib/screenplay/abilities/PerformActivities.js +66 -0
- package/lib/screenplay/abilities/PerformActivities.js.map +1 -0
- package/lib/screenplay/abilities/index.d.ts +2 -0
- package/lib/screenplay/abilities/index.d.ts.map +1 -1
- package/lib/screenplay/abilities/index.js +2 -0
- package/lib/screenplay/abilities/index.js.map +1 -1
- package/lib/screenplay/time/abilities/ScheduleWork.d.ts +2 -4
- package/lib/screenplay/time/abilities/ScheduleWork.d.ts.map +1 -1
- package/lib/screenplay/time/abilities/ScheduleWork.js +0 -6
- package/lib/screenplay/time/abilities/ScheduleWork.js.map +1 -1
- package/lib/screenplay/time/models/Clock.d.ts +24 -0
- package/lib/screenplay/time/models/Clock.d.ts.map +1 -1
- package/lib/screenplay/time/models/Clock.js +41 -1
- package/lib/screenplay/time/models/Clock.js.map +1 -1
- package/lib/screenplay/time/models/Scheduler.d.ts +1 -10
- package/lib/screenplay/time/models/Scheduler.d.ts.map +1 -1
- package/lib/screenplay/time/models/Scheduler.js +65 -103
- package/lib/screenplay/time/models/Scheduler.js.map +1 -1
- package/lib/stage/Stage.d.ts +2 -2
- package/lib/stage/Stage.d.ts.map +1 -1
- package/lib/stage/Stage.js +4 -2
- package/lib/stage/Stage.js.map +1 -1
- package/package.json +6 -6
- package/src/Serenity.ts +2 -2
- package/src/events/EmitsDomainEvents.ts +11 -0
- package/src/events/index.ts +1 -0
- package/src/io/asyncMap.ts +2 -2
- package/src/screenplay/Actor.ts +32 -131
- package/src/screenplay/abilities/AnswerQuestions.ts +41 -0
- package/src/screenplay/abilities/PerformActivities.ts +88 -0
- package/src/screenplay/abilities/index.ts +2 -0
- package/src/screenplay/time/abilities/ScheduleWork.ts +2 -10
- package/src/screenplay/time/models/Clock.ts +47 -1
- package/src/screenplay/time/models/Scheduler.ts +89 -136
- package/src/stage/Stage.ts +15 -7
|
@@ -10,11 +10,7 @@ import { Timestamp } from './Timestamp';
|
|
|
10
10
|
*/
|
|
11
11
|
export class Scheduler {
|
|
12
12
|
|
|
13
|
-
private
|
|
14
|
-
private completedCallbacks: Map<DelayedCallback<unknown>, CallbackInfo<unknown>> = new Map();
|
|
15
|
-
private failedCallbacks: Map<DelayedCallback<unknown>, CallbackInfo<unknown>> = new Map();
|
|
16
|
-
|
|
17
|
-
private timer: NodeJS.Timer;
|
|
13
|
+
private scheduledOperations: Array<ScheduledOperation<unknown>> = [];
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
16
|
* @param clock
|
|
@@ -39,7 +35,7 @@ export class Scheduler {
|
|
|
39
35
|
{
|
|
40
36
|
maxInvocations: 1,
|
|
41
37
|
delayBetweenInvocations: () => delay,
|
|
42
|
-
timeout: this.interactionTimeout,
|
|
38
|
+
timeout: this.interactionTimeout.plus(delay),
|
|
43
39
|
},
|
|
44
40
|
);
|
|
45
41
|
}
|
|
@@ -81,154 +77,111 @@ export class Scheduler {
|
|
|
81
77
|
errorHandler = rethrowErrors,
|
|
82
78
|
} = limits;
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
const operation = new ScheduledOperation(
|
|
81
|
+
this.clock,
|
|
82
|
+
callback,
|
|
83
|
+
{
|
|
84
|
+
exitCondition,
|
|
85
|
+
maxInvocations,
|
|
86
|
+
delayBetweenInvocations,
|
|
87
|
+
timeout,
|
|
88
|
+
errorHandler,
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
this.scheduledOperations.push(operation);
|
|
93
|
+
return operation.start()
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
() => this.invokeCallbacksScheduledUntil(this.clock.now()),
|
|
102
|
-
100
|
|
103
|
-
)
|
|
96
|
+
stop(): void {
|
|
97
|
+
for (const operation of this.scheduledOperations) {
|
|
98
|
+
operation.cancel();
|
|
104
99
|
}
|
|
105
100
|
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class ScheduledOperation<Result> {
|
|
104
|
+
private currentInvocation = 0;
|
|
105
|
+
private invocationsLeft = 0;
|
|
106
|
+
private startedAt: Timestamp;
|
|
107
|
+
private lastResult: Result;
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
private isCancelled = false;
|
|
110
|
+
|
|
111
|
+
constructor(
|
|
112
|
+
private readonly clock: Clock,
|
|
113
|
+
private readonly callback: DelayedCallback<Result>,
|
|
114
|
+
private readonly limits: RepeatUntilLimits<Result> = {},
|
|
115
|
+
) {
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.remainingCallbacks.delete(callback);
|
|
118
|
-
this.failedCallbacks.set(callback, {
|
|
119
|
-
...info,
|
|
120
|
-
error: new OperationInterruptedError(`Scheduler stopped before executing callback ${ callback }`)
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
118
|
+
async start(): Promise<Result> {
|
|
119
|
+
this.currentInvocation = 0;
|
|
120
|
+
this.invocationsLeft = this.limits.maxInvocations;
|
|
121
|
+
this.startedAt = this.clock.now();
|
|
122
|
+
|
|
123
|
+
return await this.poll();
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
this.
|
|
128
|
-
|
|
129
|
-
)
|
|
126
|
+
private async poll(): Promise<Result> {
|
|
127
|
+
await this.clock.waitFor(this.limits.delayBetweenInvocations(this.currentInvocation));
|
|
128
|
+
|
|
129
|
+
if (this.isCancelled) {
|
|
130
|
+
throw new OperationInterruptedError('Scheduler stopped before executing callback');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const receipt = await this.invoke();
|
|
134
|
+
|
|
135
|
+
if (receipt.hasCompleted) {
|
|
136
|
+
return receipt.result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.currentInvocation++;
|
|
140
|
+
this.invocationsLeft--;
|
|
141
|
+
|
|
142
|
+
return await this.poll();
|
|
130
143
|
}
|
|
131
144
|
|
|
132
|
-
private
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
private async invoke(): Promise<{ result?: Result, error?: Error, hasCompleted: boolean }> {
|
|
146
|
+
|
|
147
|
+
const timeoutExpired = this.startedAt.plus(this.limits.timeout).isBefore(this.clock.now());
|
|
148
|
+
const isLastInvocation = this.invocationsLeft === 1;
|
|
149
|
+
|
|
150
|
+
if (this.invocationsLeft === 0) {
|
|
151
|
+
return {
|
|
152
|
+
result: this.lastResult,
|
|
153
|
+
hasCompleted: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
136
156
|
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
try {
|
|
158
|
+
if (timeoutExpired) {
|
|
159
|
+
throw new TimeoutExpiredError(`Timeout of ${ this.limits.timeout } has expired`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.lastResult = await this.callback({ currentTime: this.clock.now(), i: this.currentInvocation });
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
result: this.lastResult,
|
|
166
|
+
hasCompleted: this.limits.exitCondition(this.lastResult) || isLastInvocation,
|
|
139
167
|
}
|
|
140
168
|
}
|
|
141
|
-
|
|
169
|
+
catch(error) {
|
|
142
170
|
|
|
143
|
-
|
|
144
|
-
const info = this.remainingCallbacks.get(callback);
|
|
145
|
-
|
|
146
|
-
this.remainingCallbacks.delete(callback);
|
|
147
|
-
|
|
148
|
-
Promise.resolve()
|
|
149
|
-
.then(async () => {
|
|
150
|
-
const timeoutExpired = info.startedAt.plus(info.timeout).isBefore(this.clock.now());
|
|
151
|
-
const isLastInvocation = info.invocationsLeft === 1;
|
|
152
|
-
|
|
153
|
-
if (info.invocationsLeft === 0) {
|
|
154
|
-
return {
|
|
155
|
-
hasCompleted: true,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
if (timeoutExpired) {
|
|
161
|
-
throw new TimeoutExpiredError(`Timeout of ${ info.timeout } has expired`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const result = await callback({ currentTime: this.clock.now(), i: info.currentInvocation });
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
result,
|
|
168
|
-
hasCompleted: info.exitCondition(result) || isLastInvocation,
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
catch(error) {
|
|
172
|
-
info.errorHandler(error, info.result);
|
|
173
|
-
|
|
174
|
-
// if the errorHandler didn't throw, it's a recoverable error
|
|
175
|
-
return {
|
|
176
|
-
error,
|
|
177
|
-
hasCompleted: isLastInvocation,
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
.then(({ result, error, hasCompleted }) => {
|
|
182
|
-
if (hasCompleted) {
|
|
183
|
-
this['completedCallbacks'].set(callback, {
|
|
184
|
-
...info,
|
|
185
|
-
result: result ?? info.result,
|
|
186
|
-
error,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
this['remainingCallbacks'].set(callback, {
|
|
191
|
-
...info,
|
|
192
|
-
currentInvocation: info.currentInvocation + 1,
|
|
193
|
-
invocationsLeft: info.invocationsLeft - 1,
|
|
194
|
-
result: result ?? info.result,
|
|
195
|
-
error,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
.catch(error => {
|
|
200
|
-
this.failedCallbacks.set(callback, { ...info, error });
|
|
201
|
-
});
|
|
202
|
-
}
|
|
171
|
+
this.limits.errorHandler(error, this.lastResult);
|
|
203
172
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (this.completedCallbacks.has(callback)) {
|
|
214
|
-
clearInterval(timer);
|
|
215
|
-
return resolve(this.completedCallbacks.get(callback).result as Result);
|
|
216
|
-
}
|
|
217
|
-
}, 25);
|
|
218
|
-
})
|
|
173
|
+
// if the errorHandler didn't throw, it's a recoverable error
|
|
174
|
+
return {
|
|
175
|
+
result: this.lastResult,
|
|
176
|
+
error,
|
|
177
|
+
hasCompleted: isLastInvocation,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
219
180
|
}
|
|
220
|
-
}
|
|
221
181
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
invocationsLeft: number,
|
|
226
|
-
delayBetweenInvocations: (i: number) => Duration,
|
|
227
|
-
timeout: Duration
|
|
228
|
-
startedAt: Timestamp,
|
|
229
|
-
errorHandler: (error: Error, result: Result) => void,
|
|
230
|
-
result?: Result,
|
|
231
|
-
error?: Error,
|
|
182
|
+
cancel(): void {
|
|
183
|
+
this.isCancelled = true;
|
|
184
|
+
}
|
|
232
185
|
}
|
|
233
186
|
|
|
234
187
|
function noDelay() {
|
package/src/stage/Stage.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { ensure, isDefined } from 'tiny-types';
|
|
2
2
|
|
|
3
3
|
import { ConfigurationError, ErrorFactory, ErrorOptions, LogicError, RaiseErrors, RuntimeError } from '../errors';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AsyncOperationAttempted,
|
|
6
|
+
AsyncOperationCompleted,
|
|
7
|
+
AsyncOperationFailed,
|
|
8
|
+
DomainEvent,
|
|
9
|
+
EmitsDomainEvents,
|
|
10
|
+
SceneFinishes,
|
|
11
|
+
SceneStarts,
|
|
12
|
+
TestRunFinishes
|
|
13
|
+
} from '../events';
|
|
5
14
|
import { ActivityDetails, CorrelationId, Description, Name } from '../model';
|
|
6
15
|
import { Actor, Clock, Duration, ScheduleWork, Timestamp } from '../screenplay';
|
|
7
16
|
import { ListensToDomainEvents } from '../stage';
|
|
@@ -23,7 +32,7 @@ import { StageManager } from './StageManager';
|
|
|
23
32
|
*
|
|
24
33
|
* @group Stage
|
|
25
34
|
*/
|
|
26
|
-
export class Stage {
|
|
35
|
+
export class Stage implements EmitsDomainEvents {
|
|
27
36
|
|
|
28
37
|
private static readonly unknownSceneId = new CorrelationId('unknown')
|
|
29
38
|
|
|
@@ -91,11 +100,10 @@ export class Stage {
|
|
|
91
100
|
if (! this.instantiatedActorCalled(name)) {
|
|
92
101
|
let actor;
|
|
93
102
|
try {
|
|
94
|
-
const newActor = new Actor(name, this
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
103
|
+
const newActor = new Actor(name, this, [
|
|
104
|
+
new RaiseErrors(this),
|
|
105
|
+
new ScheduleWork(this.clock, this.interactionTimeout)
|
|
106
|
+
]);
|
|
99
107
|
|
|
100
108
|
actor = this.cast.prepare(newActor);
|
|
101
109
|
|