@serenity-js/assertions 3.0.0-rc.43 → 3.0.0-rc.45

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 CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [3.0.0-rc.45](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.44...v3.0.0-rc.45) (2023-03-22)
7
+
8
+ **Note:** Version bump only for package @serenity-js/assertions
9
+
10
+
11
+
12
+
13
+
14
+ # [3.0.0-rc.44](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.43...v3.0.0-rc.44) (2023-03-19)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **core:** support for NPM 9 ([0493474](https://github.com/serenity-js/serenity-js/commit/0493474a1e28b86b1b60f69ec0d591c1a3265425))
20
+ * **deps:** update dependency tiny-types to ^1.19.1 ([ce335eb](https://github.com/serenity-js/serenity-js/commit/ce335ebca434d1fd0e6e809a65a0882fd10a311a))
21
+
22
+
23
+ ### Features
24
+
25
+ * **assertions:** fault-tolerant interaction to Ensure.eventually ([d6297f7](https://github.com/serenity-js/serenity-js/commit/d6297f7f15c096a51461c484c6a8d1eeb2182b24)), closes [#1522](https://github.com/serenity-js/serenity-js/issues/1522)
26
+ * **core:** introduced a new ability ScheduleWork to enable [#1083](https://github.com/serenity-js/serenity-js/issues/1083) and [#1522](https://github.com/serenity-js/serenity-js/issues/1522) ([b275d18](https://github.com/serenity-js/serenity-js/commit/b275d18434cdedf069c5f1da3b9b359fc7da60fe))
27
+
28
+
29
+
30
+
31
+
6
32
  # [3.0.0-rc.43](https://github.com/serenity-js/serenity-js/compare/v3.0.0-rc.42...v3.0.0-rc.43) (2023-03-10)
7
33
 
8
34
  **Note:** Version bump only for package @serenity-js/assertions
package/lib/Ensure.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Answerable, AnswersQuestions, CollectsArtifacts, Expectation, Interaction, RuntimeError, UsesAbilities } from '@serenity-js/core';
2
+ import { EnsureEventually } from './EnsureEventually';
2
3
  /**
3
4
  * The {@apilink Interaction|interaction} to `Ensure`
4
5
  * verifies if the resolved value of the provided {@apilink Answerable}
@@ -50,6 +51,11 @@ export declare class Ensure<Actual> extends Interaction {
50
51
  protected readonly actual: Answerable<Actual>;
51
52
  protected readonly expectation: Expectation<Actual>;
52
53
  /**
54
+ * Creates an {@apilink Interaction|interaction} to `Ensure`, which
55
+ * verifies if the resolved value of the provided {@apilink Answerable}
56
+ * meets the specified {@apilink Expectation}.
57
+ * If not, it immediately throws an {@apilink AssertionError}.
58
+ *
53
59
  * @param {Answerable<Actual_Type>} actual
54
60
  * An {@apilink Answerable} describing the actual state of the system.
55
61
  *
@@ -59,6 +65,24 @@ export declare class Ensure<Actual> extends Interaction {
59
65
  * @returns {Ensure<Actual_Type>}
60
66
  */
61
67
  static that<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): Ensure<Actual_Type>;
68
+ /**
69
+ * Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
70
+ * verifies if the resolved value of the provided {@apilink Answerable}
71
+ * meets the specified {@apilink Expectation} within the expected timeframe.
72
+ *
73
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
74
+ * `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
75
+ * but rethrows any other errors.
76
+ *
77
+ * @param {Answerable<Actual_Type>} actual
78
+ * An {@apilink Answerable} describing the actual state of the system.
79
+ *
80
+ * @param {Expectation<Actual_Type>} expectation
81
+ * An {@apilink Expectation} you expect the `actual` value to meet
82
+ *
83
+ * @returns {Ensure<Actual_Type>}
84
+ */
85
+ static eventually<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): EnsureEventually<Actual_Type>;
62
86
  /**
63
87
  * @param actual
64
88
  * @param expectation
@@ -1 +1 @@
1
- {"version":3,"file":"Ensure.d.ts","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAIX,WAAW,EAGX,YAAY,EACZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,MAAM,CAAC,MAAM,CAAE,SAAQ,WAAW;IAqBvC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;IApBvD;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IAIrH;;;;OAIG;IACH,OAAO;IAQP;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3F;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW;CAgB7H"}
1
+ {"version":3,"file":"Ensure.d.ts","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,WAAW,EAIX,WAAW,EAGX,YAAY,EACZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,MAAM,CAAC,MAAM,CAAE,SAAQ,WAAW;IA+CvC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;IA9CvD;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC;IAIrH;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,gBAAgB,CAAC,WAAW,CAAC;IAIrI;;;;OAIG;IACH,OAAO;IAQP;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB3F;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW;CAgB7H"}
package/lib/Ensure.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Ensure = void 0;
4
4
  const core_1 = require("@serenity-js/core");
5
+ const EnsureEventually_1 = require("./EnsureEventually");
5
6
  /**
6
7
  * The {@apilink Interaction|interaction} to `Ensure`
7
8
  * verifies if the resolved value of the provided {@apilink Answerable}
@@ -51,6 +52,11 @@ const core_1 = require("@serenity-js/core");
51
52
  */
52
53
  class Ensure extends core_1.Interaction {
53
54
  /**
55
+ * Creates an {@apilink Interaction|interaction} to `Ensure`, which
56
+ * verifies if the resolved value of the provided {@apilink Answerable}
57
+ * meets the specified {@apilink Expectation}.
58
+ * If not, it immediately throws an {@apilink AssertionError}.
59
+ *
54
60
  * @param {Answerable<Actual_Type>} actual
55
61
  * An {@apilink Answerable} describing the actual state of the system.
56
62
  *
@@ -62,6 +68,26 @@ class Ensure extends core_1.Interaction {
62
68
  static that(actual, expectation) {
63
69
  return new Ensure(actual, expectation, core_1.Activity.callerLocation(5));
64
70
  }
71
+ /**
72
+ * Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
73
+ * verifies if the resolved value of the provided {@apilink Answerable}
74
+ * meets the specified {@apilink Expectation} within the expected timeframe.
75
+ *
76
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
77
+ * `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
78
+ * but rethrows any other errors.
79
+ *
80
+ * @param {Answerable<Actual_Type>} actual
81
+ * An {@apilink Answerable} describing the actual state of the system.
82
+ *
83
+ * @param {Expectation<Actual_Type>} expectation
84
+ * An {@apilink Expectation} you expect the `actual` value to meet
85
+ *
86
+ * @returns {Ensure<Actual_Type>}
87
+ */
88
+ static eventually(actual, expectation) {
89
+ return new EnsureEventually_1.EnsureEventually(actual, expectation, core_1.Activity.callerLocation(5));
90
+ }
65
91
  /**
66
92
  * @param actual
67
93
  * @param expectation
package/lib/Ensure.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Ensure.js","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":";;;AAAA,4CAgB2B;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,MAAe,SAAQ,kBAAW;IAE3C;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,CAAc,MAA+B,EAAE,WAAqC;QAC3F,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,eAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACH,YACuB,MAA0B,EAC1B,WAAgC,EACnD,QAA4B;QAE5B,KAAK,CAAC,IAAA,QAAC,EAAA,uBAAwB,MAAO,SAAU,WAAY,EAAE,EAAE,QAAQ,CAAC,CAAC;QAJvD,WAAM,GAAN,MAAM,CAAoB;QAC1B,gBAAW,GAAX,WAAW,CAAqB;IAIvD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,KAA2D;QACvE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAE3E,IAAI,OAAO,YAAY,wBAAiB,EAAE;YACtC,MAAM,iBAAiB,GAAG,IAAA,QAAC,EAAA,GAAI,IAAI,CAAC,MAAO,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,YAAa,iBAAkB,OAAQ,OAAO,CAAC,OAAQ,EAAE,CAAC;YAE1E,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,qBAAc,EAAE;gBAC/C,OAAO;gBACP,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;gBAC5D,QAAQ,EAAE,IAAI,CAAC,qBAAqB,EAAE;aACzC,CAAC,CAAC;SACN;QAED,IAAI,CAAE,CAAC,OAAO,YAAY,qBAAc,CAAC,EAAE;YACvC,MAAM,IAAI,iBAAU,CAAC,IAAA,QAAC,EAAA,wFAAyF,OAAQ,EAAE,CAAC,CAAC;SAC9H;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAwE,EAAE,OAAgB;QACxG,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAE9C,OAAO,kBAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACpD,IAAI;gBACA,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;aAC/B;YACD,OAAO,KAAK,EAAE;gBACV,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE;oBACnD,OAAO,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,KAAK,CAAC,OAAO;oBACjC,QAAQ;oBACR,KAAK,EAAE,KAAK;iBACf,CAAC,CAAC;aACN;QACL,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AA7ED,wBA6EC"}
1
+ {"version":3,"file":"Ensure.js","sourceRoot":"","sources":["../src/Ensure.ts"],"names":[],"mappings":";;;AAAA,4CAgB2B;AAG3B,yDAAsD;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAa,MAAe,SAAQ,kBAAW;IAE3C;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,IAAI,CAAc,MAA+B,EAAE,WAAqC;QAC3F,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,eAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,UAAU,CAAc,MAA+B,EAAE,WAAqC;QACjG,OAAO,IAAI,mCAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,eAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,CAAC;IAED;;;;OAIG;IACH,YACuB,MAA0B,EAC1B,WAAgC,EACnD,QAA4B;QAE5B,KAAK,CAAC,IAAA,QAAC,EAAA,uBAAwB,MAAO,SAAU,WAAY,EAAE,EAAE,QAAQ,CAAC,CAAC;QAJvD,WAAM,GAAN,MAAM,CAAoB;QAC1B,gBAAW,GAAX,WAAW,CAAqB;IAIvD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,SAAS,CAAC,KAA2D;QACvE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAE3E,IAAI,OAAO,YAAY,wBAAiB,EAAE;YACtC,MAAM,iBAAiB,GAAG,IAAA,QAAC,EAAA,GAAI,IAAI,CAAC,MAAO,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,YAAa,iBAAkB,OAAQ,OAAO,CAAC,OAAQ,EAAE,CAAC;YAE1E,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,qBAAc,EAAE;gBAC/C,OAAO;gBACP,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;gBAC5D,QAAQ,EAAE,IAAI,CAAC,qBAAqB,EAAE;aACzC,CAAC,CAAC;SACN;QAED,IAAI,CAAE,CAAC,OAAO,YAAY,qBAAc,CAAC,EAAE;YACvC,MAAM,IAAI,iBAAU,CAAC,IAAA,QAAC,EAAA,wFAAyF,OAAQ,EAAE,CAAC,CAAC;SAC9H;IACL,CAAC;IAED;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAwE,EAAE,OAAgB;QACxG,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAE9C,OAAO,kBAAW,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACpD,IAAI;gBACA,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;aAC/B;YACD,OAAO,KAAK,EAAE;gBACV,MAAM,kBAAW,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE;oBACnD,OAAO,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,KAAK,CAAC,OAAO;oBACjC,QAAQ;oBACR,KAAK,EAAE,KAAK;iBACf,CAAC,CAAC;aACN;QACL,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAvGD,wBAuGC"}
@@ -0,0 +1,97 @@
1
+ import { Answerable, AnswersQuestions, CollectsArtifacts, Duration, Expectation, Interaction, RuntimeError, UsesAbilities } from '@serenity-js/core';
2
+ import { FileSystemLocation } from '@serenity-js/core/lib/io';
3
+ /**
4
+ * The {@apilink Interaction|interaction} to `EnsureEventually`
5
+ * verifies if the resolved value of the provided {@apilink Answerable}
6
+ * meets the specified {@apilink Expectation} within the expected timeframe.
7
+ *
8
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
9
+ * `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
10
+ * but rethrows any other errors.
11
+ *
12
+ * :::tip Use the factory method
13
+ * Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
14
+ * :::
15
+ *
16
+ * ## Basic usage with dynamic values
17
+ * ```ts
18
+ * import { actorCalled } from '@serenity-js/core'
19
+ * import { Ensure, equals } from '@serenity-js/assertions'
20
+ * import { Text, PageElement, By } from '@serenity-js/web'
21
+ *
22
+ * await actorCalled('Erica').attemptsTo(
23
+ * Ensure.eventually(
24
+ * Text.of(PageElement.located(By.css('h1'))),
25
+ * equals('Learn Serenity/JS!')
26
+ * )
27
+ * )
28
+ * ```
29
+ *
30
+ * ## Composing expectations with `and`
31
+ *
32
+ * ```ts
33
+ * import { actorCalled } from '@serenity-js/core'
34
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
35
+ * import { Text, PageElement, By } from '@serenity-js/web'
36
+ *
37
+ * await actorCalled('Erica').attemptsTo(
38
+ * Ensure.eventually(
39
+ * Text.of(PageElement.located(By.css('h1'))),
40
+ * and(startsWith('Serenity'), endsWith('!'))
41
+ * )
42
+ * )
43
+ * ```
44
+ *
45
+ * ## Overriding the type of Error thrown upon assertion failure
46
+ *
47
+ * ```ts
48
+ * import { actorCalled } from '@serenity-js/core'
49
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
50
+ * import { Text, PageElement, By } from '@serenity-js/web'
51
+ *
52
+ * await actorCalled('Erica').attemptsTo(
53
+ * Ensure.eventually(
54
+ * Text.of(PageElement.located(By.css('h1'))),
55
+ * and(startsWith('Serenity'), endsWith('!'))
56
+ * ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
57
+ * )
58
+ * ```
59
+ *
60
+ * @experimental
61
+ *
62
+ * @group Activities
63
+ */
64
+ export declare class EnsureEventually<Actual> extends Interaction {
65
+ protected readonly actual: Answerable<Actual>;
66
+ protected readonly expectation: Expectation<Actual>;
67
+ protected readonly timeout?: Duration;
68
+ /**
69
+ * @param actual
70
+ * @param expectation
71
+ * @param location
72
+ * @param timeout
73
+ */
74
+ constructor(actual: Answerable<Actual>, expectation: Expectation<Actual>, location: FileSystemLocation, timeout?: Duration);
75
+ /**
76
+ * Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
77
+ *
78
+ * @param timeout
79
+ */
80
+ timeoutAfter(timeout: Duration): EnsureEventually<Actual>;
81
+ /**
82
+ * @inheritDoc
83
+ */
84
+ performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void>;
85
+ /**
86
+ * Overrides the default {@apilink AssertionError} thrown when
87
+ * the actual value does not meet the expectation.
88
+ *
89
+ * @param typeOfRuntimeError
90
+ * A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
91
+ *
92
+ * @param message
93
+ * The message explaining the failure
94
+ */
95
+ otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction;
96
+ }
97
+ //# sourceMappingURL=EnsureEventually.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnsureEventually.d.ts","sourceRoot":"","sources":["../src/EnsureEventually.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,gBAAgB,EAEhB,iBAAiB,EAEjB,QAAQ,EACR,WAAW,EAGX,WAAW,EAGX,YAAY,EAGZ,aAAa,EAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,qBAAa,gBAAgB,CAAC,MAAM,CAAE,SAAQ,WAAW;IAQjD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC;IAEnD,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ;IAVzC;;;;;OAKG;gBAEoB,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,EAC1B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EACnD,QAAQ,EAAE,kBAAkB,EACT,OAAO,CAAC,EAAE,QAAQ;IAKzC;;;;OAIG;IACH,YAAY,CAAC,OAAO,EAAE,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAIzD;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsC3F;;;;;;;;;OASG;IACH,iBAAiB,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW;CAe7H"}
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnsureEventually = void 0;
4
+ const core_1 = require("@serenity-js/core");
5
+ /**
6
+ * The {@apilink Interaction|interaction} to `EnsureEventually`
7
+ * verifies if the resolved value of the provided {@apilink Answerable}
8
+ * meets the specified {@apilink Expectation} within the expected timeframe.
9
+ *
10
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
11
+ * `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
12
+ * but rethrows any other errors.
13
+ *
14
+ * :::tip Use the factory method
15
+ * Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
16
+ * :::
17
+ *
18
+ * ## Basic usage with dynamic values
19
+ * ```ts
20
+ * import { actorCalled } from '@serenity-js/core'
21
+ * import { Ensure, equals } from '@serenity-js/assertions'
22
+ * import { Text, PageElement, By } from '@serenity-js/web'
23
+ *
24
+ * await actorCalled('Erica').attemptsTo(
25
+ * Ensure.eventually(
26
+ * Text.of(PageElement.located(By.css('h1'))),
27
+ * equals('Learn Serenity/JS!')
28
+ * )
29
+ * )
30
+ * ```
31
+ *
32
+ * ## Composing expectations with `and`
33
+ *
34
+ * ```ts
35
+ * import { actorCalled } from '@serenity-js/core'
36
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
37
+ * import { Text, PageElement, By } from '@serenity-js/web'
38
+ *
39
+ * await actorCalled('Erica').attemptsTo(
40
+ * Ensure.eventually(
41
+ * Text.of(PageElement.located(By.css('h1'))),
42
+ * and(startsWith('Serenity'), endsWith('!'))
43
+ * )
44
+ * )
45
+ * ```
46
+ *
47
+ * ## Overriding the type of Error thrown upon assertion failure
48
+ *
49
+ * ```ts
50
+ * import { actorCalled } from '@serenity-js/core'
51
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
52
+ * import { Text, PageElement, By } from '@serenity-js/web'
53
+ *
54
+ * await actorCalled('Erica').attemptsTo(
55
+ * Ensure.eventually(
56
+ * Text.of(PageElement.located(By.css('h1'))),
57
+ * and(startsWith('Serenity'), endsWith('!'))
58
+ * ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
59
+ * )
60
+ * ```
61
+ *
62
+ * @experimental
63
+ *
64
+ * @group Activities
65
+ */
66
+ class EnsureEventually extends core_1.Interaction {
67
+ /**
68
+ * @param actual
69
+ * @param expectation
70
+ * @param location
71
+ * @param timeout
72
+ */
73
+ constructor(actual, expectation, location, timeout) {
74
+ super((0, core_1.d) `#actor ensures that ${actual} does eventually ${expectation}`, location);
75
+ this.actual = actual;
76
+ this.expectation = expectation;
77
+ this.timeout = timeout;
78
+ }
79
+ /**
80
+ * Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
81
+ *
82
+ * @param timeout
83
+ */
84
+ timeoutAfter(timeout) {
85
+ return new EnsureEventually(this.actual, this.expectation, this.instantiationLocation(), timeout);
86
+ }
87
+ /**
88
+ * @inheritDoc
89
+ */
90
+ async performAs(actor) {
91
+ await core_1.ScheduleWork.as(actor).repeatUntil(() => actor.answer(this.expectation.isMetFor(this.actual)), {
92
+ exitCondition: outcome => outcome instanceof core_1.ExpectationMet,
93
+ delayBetweenInvocations: (invocation) => invocation === 0
94
+ ? core_1.Duration.ofMilliseconds(0) // perform the first evaluation straight away
95
+ : core_1.Duration.ofMilliseconds(2 ** invocation * 100),
96
+ timeout: this.timeout,
97
+ errorHandler: (error, outcome) => {
98
+ if (error instanceof core_1.ListItemNotFoundError) {
99
+ return; // ignore, lists might get populated later
100
+ }
101
+ if (error instanceof core_1.TimeoutExpiredError) {
102
+ const actualDescription = (0, core_1.d) `${this.actual}`;
103
+ const message = outcome ? `Expected ${actualDescription} to eventually ${outcome === null || outcome === void 0 ? void 0 : outcome.message}` : error.message;
104
+ throw core_1.RaiseErrors.as(actor).create(core_1.AssertionError, {
105
+ message,
106
+ expectation: outcome === null || outcome === void 0 ? void 0 : outcome.expectation,
107
+ diff: outcome && { expected: outcome === null || outcome === void 0 ? void 0 : outcome.expected, actual: outcome === null || outcome === void 0 ? void 0 : outcome.actual },
108
+ location: this.instantiationLocation(),
109
+ cause: error,
110
+ });
111
+ }
112
+ throw error;
113
+ },
114
+ });
115
+ }
116
+ /**
117
+ * Overrides the default {@apilink AssertionError} thrown when
118
+ * the actual value does not meet the expectation.
119
+ *
120
+ * @param typeOfRuntimeError
121
+ * A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
122
+ *
123
+ * @param message
124
+ * The message explaining the failure
125
+ */
126
+ otherwiseFailWith(typeOfRuntimeError, message) {
127
+ const location = this.instantiationLocation();
128
+ return core_1.Interaction.where(this.toString(), async (actor) => {
129
+ try {
130
+ await this.performAs(actor);
131
+ }
132
+ catch (error) {
133
+ throw core_1.RaiseErrors.as(actor).create(typeOfRuntimeError, {
134
+ message: message !== null && message !== void 0 ? message : error.message,
135
+ location,
136
+ cause: error,
137
+ });
138
+ }
139
+ });
140
+ }
141
+ }
142
+ exports.EnsureEventually = EnsureEventually;
143
+ //# sourceMappingURL=EnsureEventually.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnsureEventually.js","sourceRoot":"","sources":["../src/EnsureEventually.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './Ensure';
2
+ export * from './EnsureEventually';
2
3
  export * from './expectations';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
package/lib/index.js CHANGED
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./Ensure"), exports);
18
+ __exportStar(require("./EnsureEventually"), exports);
18
19
  __exportStar(require("./expectations"), exports);
19
20
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,iDAA+B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,qDAAmC;AACnC,iDAA+B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serenity-js/assertions",
3
- "version": "3.0.0-rc.43",
3
+ "version": "3.0.0-rc.45",
4
4
  "description": "Screenplay-style assertion library",
5
5
  "author": {
6
6
  "name": "Jan Molak",
@@ -26,8 +26,8 @@
26
26
  "testing"
27
27
  ],
28
28
  "scripts": {
29
- "clean": "rimraf '../../target/coverage/assertions'",
30
- "test": "nyc mocha --config ../../.mocharc.yml 'spec/**/*.spec.*'",
29
+ "clean": "rimraf target",
30
+ "test": "nyc mocha --config ../../.mocharc.yml --reporter-options 'mocha-multi=.mocha-reporters.json' 'spec/**/*.spec.*'",
31
31
  "compile": "rimraf lib && tsc --project tsconfig.build.json"
32
32
  },
33
33
  "repository": {
@@ -40,20 +40,21 @@
40
40
  },
41
41
  "engines": {
42
42
  "node": "^14 || ^16 || ^18",
43
- "npm": "^6 || ^7 || ^8"
43
+ "npm": "^6 || ^7 || ^8 || ^9"
44
44
  },
45
45
  "dependencies": {
46
- "@serenity-js/core": "3.0.0-rc.43",
47
- "tiny-types": "^1.19.0"
46
+ "@serenity-js/core": "3.0.0-rc.45",
47
+ "tiny-types": "^1.19.1"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@integration/testing-tools": "3.0.0",
51
51
  "@types/chai": "^4.3.4",
52
52
  "@types/mocha": "^10.0.1",
53
53
  "mocha": "^10.2.0",
54
+ "mocha-multi": "^1.1.7",
54
55
  "nyc": "15.1.0",
55
56
  "ts-node": "^10.9.1",
56
57
  "typescript": "^4.9.5"
57
58
  },
58
- "gitHead": "683d2fc1fd69444369b666d4318da95f8a5951fc"
59
+ "gitHead": "4c5c6cdb30f726b51d1567862074bb6fa9b5aa66"
59
60
  }
package/src/Ensure.ts CHANGED
@@ -17,6 +17,8 @@ import {
17
17
  } from '@serenity-js/core';
18
18
  import { FileSystemLocation } from '@serenity-js/core/lib/io';
19
19
 
20
+ import { EnsureEventually } from './EnsureEventually';
21
+
20
22
  /**
21
23
  * The {@apilink Interaction|interaction} to `Ensure`
22
24
  * verifies if the resolved value of the provided {@apilink Answerable}
@@ -67,6 +69,11 @@ import { FileSystemLocation } from '@serenity-js/core/lib/io';
67
69
  export class Ensure<Actual> extends Interaction {
68
70
 
69
71
  /**
72
+ * Creates an {@apilink Interaction|interaction} to `Ensure`, which
73
+ * verifies if the resolved value of the provided {@apilink Answerable}
74
+ * meets the specified {@apilink Expectation}.
75
+ * If not, it immediately throws an {@apilink AssertionError}.
76
+ *
70
77
  * @param {Answerable<Actual_Type>} actual
71
78
  * An {@apilink Answerable} describing the actual state of the system.
72
79
  *
@@ -79,6 +86,27 @@ export class Ensure<Actual> extends Interaction {
79
86
  return new Ensure(actual, expectation, Activity.callerLocation(5));
80
87
  }
81
88
 
89
+ /**
90
+ * Creates an {@apilink Interaction|interaction} to {@apilink EnsureEventually}, which
91
+ * verifies if the resolved value of the provided {@apilink Answerable}
92
+ * meets the specified {@apilink Expectation} within the expected timeframe.
93
+ *
94
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
95
+ * `EnsureEventually` ignores retries the evaluation if resolving the `actual` results in an {@apilink OptionalNotPresentError},
96
+ * but rethrows any other errors.
97
+ *
98
+ * @param {Answerable<Actual_Type>} actual
99
+ * An {@apilink Answerable} describing the actual state of the system.
100
+ *
101
+ * @param {Expectation<Actual_Type>} expectation
102
+ * An {@apilink Expectation} you expect the `actual` value to meet
103
+ *
104
+ * @returns {Ensure<Actual_Type>}
105
+ */
106
+ static eventually<Actual_Type>(actual: Answerable<Actual_Type>, expectation: Expectation<Actual_Type>): EnsureEventually<Actual_Type> {
107
+ return new EnsureEventually(actual, expectation, Activity.callerLocation(5));
108
+ }
109
+
82
110
  /**
83
111
  * @param actual
84
112
  * @param expectation
@@ -0,0 +1,173 @@
1
+ import {
2
+ Answerable,
3
+ AnswersQuestions,
4
+ AssertionError,
5
+ CollectsArtifacts,
6
+ d,
7
+ Duration,
8
+ Expectation,
9
+ ExpectationMet,
10
+ ExpectationOutcome,
11
+ Interaction,
12
+ ListItemNotFoundError,
13
+ RaiseErrors,
14
+ RuntimeError,
15
+ ScheduleWork,
16
+ TimeoutExpiredError,
17
+ UsesAbilities,
18
+ } from '@serenity-js/core';
19
+ import { FileSystemLocation } from '@serenity-js/core/lib/io';
20
+
21
+ /**
22
+ * The {@apilink Interaction|interaction} to `EnsureEventually`
23
+ * verifies if the resolved value of the provided {@apilink Answerable}
24
+ * meets the specified {@apilink Expectation} within the expected timeframe.
25
+ *
26
+ * If the expectation is not met by the time the timeout expires, the interaction throws an {@apilink AssertionError}.
27
+ * `EnsureEventually` retries the evaluation if resolving the `actual` results in an {@apilink ListItemNotFoundError},
28
+ * but rethrows any other errors.
29
+ *
30
+ * :::tip Use the factory method
31
+ * Use the factory method {@apilink Ensure.eventually} to instantiate this interaction.
32
+ * :::
33
+ *
34
+ * ## Basic usage with dynamic values
35
+ * ```ts
36
+ * import { actorCalled } from '@serenity-js/core'
37
+ * import { Ensure, equals } from '@serenity-js/assertions'
38
+ * import { Text, PageElement, By } from '@serenity-js/web'
39
+ *
40
+ * await actorCalled('Erica').attemptsTo(
41
+ * Ensure.eventually(
42
+ * Text.of(PageElement.located(By.css('h1'))),
43
+ * equals('Learn Serenity/JS!')
44
+ * )
45
+ * )
46
+ * ```
47
+ *
48
+ * ## Composing expectations with `and`
49
+ *
50
+ * ```ts
51
+ * import { actorCalled } from '@serenity-js/core'
52
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
53
+ * import { Text, PageElement, By } from '@serenity-js/web'
54
+ *
55
+ * await actorCalled('Erica').attemptsTo(
56
+ * Ensure.eventually(
57
+ * Text.of(PageElement.located(By.css('h1'))),
58
+ * and(startsWith('Serenity'), endsWith('!'))
59
+ * )
60
+ * )
61
+ * ```
62
+ *
63
+ * ## Overriding the type of Error thrown upon assertion failure
64
+ *
65
+ * ```ts
66
+ * import { actorCalled } from '@serenity-js/core'
67
+ * import { and, Ensure, startsWith, endsWith } from '@serenity-js/assertions'
68
+ * import { Text, PageElement, By } from '@serenity-js/web'
69
+ *
70
+ * await actorCalled('Erica').attemptsTo(
71
+ * Ensure.eventually(
72
+ * Text.of(PageElement.located(By.css('h1'))),
73
+ * and(startsWith('Serenity'), endsWith('!'))
74
+ * ).otherwiseFailWith(LogicError, `Looks like we're not on the right page`)
75
+ * )
76
+ * ```
77
+ *
78
+ * @experimental
79
+ *
80
+ * @group Activities
81
+ */
82
+ export class EnsureEventually<Actual> extends Interaction {
83
+ /**
84
+ * @param actual
85
+ * @param expectation
86
+ * @param location
87
+ * @param timeout
88
+ */
89
+ constructor(
90
+ protected readonly actual: Answerable<Actual>,
91
+ protected readonly expectation: Expectation<Actual>,
92
+ location: FileSystemLocation,
93
+ protected readonly timeout?: Duration,
94
+ ) {
95
+ super(d`#actor ensures that ${ actual } does eventually ${ expectation }`, location);
96
+ }
97
+
98
+ /**
99
+ * Override the default timeout set via {@apilink SerenityConfig.interactionTimeout}.
100
+ *
101
+ * @param timeout
102
+ */
103
+ timeoutAfter(timeout: Duration): EnsureEventually<Actual> {
104
+ return new EnsureEventually<Actual>(this.actual, this.expectation, this.instantiationLocation(), timeout);
105
+ }
106
+
107
+ /**
108
+ * @inheritDoc
109
+ */
110
+ async performAs(actor: UsesAbilities & AnswersQuestions & CollectsArtifacts): Promise<void> {
111
+ await ScheduleWork.as(actor).repeatUntil<ExpectationOutcome>(
112
+ () => actor.answer(this.expectation.isMetFor(this.actual)),
113
+ {
114
+ exitCondition: outcome =>
115
+ outcome instanceof ExpectationMet,
116
+
117
+ delayBetweenInvocations: (invocation) => invocation === 0
118
+ ? Duration.ofMilliseconds(0) // perform the first evaluation straight away
119
+ : Duration.ofMilliseconds(2 ** invocation * 100), // use simple exponential backoff strategy for subsequent calls
120
+
121
+ timeout: this.timeout,
122
+
123
+ errorHandler: (error, outcome) => {
124
+ if (error instanceof ListItemNotFoundError) {
125
+ return; // ignore, lists might get populated later
126
+ }
127
+
128
+ if (error instanceof TimeoutExpiredError) {
129
+
130
+ const actualDescription = d`${ this.actual }`;
131
+ const message = outcome ? `Expected ${ actualDescription } to eventually ${ outcome?.message }` : error.message;
132
+
133
+ throw RaiseErrors.as(actor).create(AssertionError, {
134
+ message,
135
+ expectation: outcome?.expectation,
136
+ diff: outcome && { expected: outcome?.expected, actual: outcome?.actual },
137
+ location: this.instantiationLocation(),
138
+ cause: error,
139
+ });
140
+ }
141
+
142
+ throw error;
143
+ },
144
+ },
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Overrides the default {@apilink AssertionError} thrown when
150
+ * the actual value does not meet the expectation.
151
+ *
152
+ * @param typeOfRuntimeError
153
+ * A constructor function producing a subtype of {@apilink RuntimeError} to throw, e.g. {@apilink TestCompromisedError}
154
+ *
155
+ * @param message
156
+ * The message explaining the failure
157
+ */
158
+ otherwiseFailWith(typeOfRuntimeError: new (message: string, cause?: Error) => RuntimeError, message?: string): Interaction {
159
+ const location = this.instantiationLocation();
160
+
161
+ return Interaction.where(this.toString(), async actor => {
162
+ try {
163
+ await this.performAs(actor);
164
+ } catch (error) {
165
+ throw RaiseErrors.as(actor).create(typeOfRuntimeError, {
166
+ message: message ?? error.message,
167
+ location,
168
+ cause: error,
169
+ });
170
+ }
171
+ });
172
+ }
173
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './Ensure';
2
+ export * from './EnsureEventually';
2
3
  export * from './expectations';