@serenity-js/core 3.7.2 → 3.9.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/lib/events/ActivityRelatedArtifactArchived.d.ts +1 -1
  3. package/lib/events/ActivityRelatedArtifactArchived.d.ts.map +1 -1
  4. package/lib/events/ActivityRelatedArtifactArchived.js +4 -3
  5. package/lib/events/ActivityRelatedArtifactArchived.js.map +1 -1
  6. package/lib/events/ArtifactArchived.d.ts +2 -1
  7. package/lib/events/ArtifactArchived.d.ts.map +1 -1
  8. package/lib/events/ArtifactArchived.js +5 -2
  9. package/lib/events/ArtifactArchived.js.map +1 -1
  10. package/lib/screenplay/Question.d.ts +18 -5
  11. package/lib/screenplay/Question.d.ts.map +1 -1
  12. package/lib/screenplay/Question.js +22 -20
  13. package/lib/screenplay/Question.js.map +1 -1
  14. package/lib/screenplay/questions/MetaQuestion.d.ts +8 -7
  15. package/lib/screenplay/questions/MetaQuestion.d.ts.map +1 -1
  16. package/lib/stage/crew/artifact-archiver/ArtifactArchiver.d.ts +39 -19
  17. package/lib/stage/crew/artifact-archiver/ArtifactArchiver.d.ts.map +1 -1
  18. package/lib/stage/crew/artifact-archiver/ArtifactArchiver.js +41 -21
  19. package/lib/stage/crew/artifact-archiver/ArtifactArchiver.js.map +1 -1
  20. package/lib/stage/crew/stream-reporter/StreamReporter.d.ts +44 -16
  21. package/lib/stage/crew/stream-reporter/StreamReporter.d.ts.map +1 -1
  22. package/lib/stage/crew/stream-reporter/StreamReporter.js +48 -16
  23. package/lib/stage/crew/stream-reporter/StreamReporter.js.map +1 -1
  24. package/package.json +2 -2
  25. package/src/events/ActivityRelatedArtifactArchived.ts +4 -1
  26. package/src/events/ArtifactArchived.ts +4 -0
  27. package/src/screenplay/Question.ts +67 -8
  28. package/src/screenplay/questions/MetaQuestion.ts +8 -7
  29. package/src/stage/crew/artifact-archiver/ArtifactArchiver.ts +43 -19
  30. package/src/stage/crew/stream-reporter/StreamReporter.ts +51 -16
@@ -5,7 +5,7 @@ import type { Stage } from '../../Stage';
5
5
  import type { StageCrewMember } from '../../StageCrewMember';
6
6
  /**
7
7
  * Serialises all the {@apilink DomainEvent} objects it receives and streams
8
- * them as [ndjson](http://ndjson.org/) to the output stream.
8
+ * them as [ndjson](http://ndjson.org/) to the output stream or file.
9
9
  *
10
10
  * Useful when debugging issues related to custom Serenity/JS test runner adapters.
11
11
  *
@@ -34,43 +34,61 @@ import type { StageCrewMember } from '../../StageCrewMember';
34
34
  * })
35
35
  * ```
36
36
  *
37
- * ## Registering `StreamReporter` using Protractor configuration
37
+ * ## Using `StreamReporter` with Playwright Test
38
+ *
39
+ * ```ts
40
+ * // playwright.config.ts
41
+ * import type { PlaywrightTestConfig } from '@serenity-js/playwright-test'
42
+ *
43
+ * const config: PlaywrightTestConfig = {
44
+ * testDir: './spec',
45
+ *
46
+ * reporter: [
47
+ * [ '@serenity-js/playwright-test', {
48
+ * crew: [
49
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
50
+ * ]
51
+ * // other Serenity/JS config
52
+ * }]
53
+ * ],
54
+ * // other Playwright Test config
55
+ * }
56
+ * ```
57
+ *
58
+ * ## Using `StreamReporter` with Protractor
38
59
  *
39
60
  * ```js
40
61
  * // protractor.conf.js
41
- * const { StreamReporter } = require('@serenity-js/core');
42
- *
43
62
  * exports.config = {
44
63
  * framework: 'custom',
45
64
  * frameworkPath: require.resolve('@serenity-js/protractor/adapter'),
46
65
  *
47
66
  * serenity: {
48
67
  * crew: [
49
- * new StreamReporter(process.stdout),
68
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
50
69
  * ],
51
70
  * // other Serenity/JS config
52
71
  * },
53
72
  * // other Protractor config
54
- * };
73
+ * }
55
74
  * ```
56
75
  *
57
- * ## Registering `StreamReporter` using WebdriverIO configuration
76
+ * ## Using `StreamReporter` with WebdriverIO
58
77
  *
59
78
  * ```ts
60
- * // wdio.conf.js
61
- * import { StreamReporter } from '@serenity-js/core'
79
+ * // wdio.conf.ts
62
80
  * import { WebdriverIOConfig } from '@serenity-js/webdriverio'
63
81
  *
64
82
  * export const config: WebdriverIOConfig = {
65
83
  *
66
- * framework: '@serenity-js/webdriverio',
84
+ * framework: '@serenity-js/webdriverio',
67
85
  *
68
- * serenity: {
69
- * crew: [
70
- * new StreamReporter(process.stdout),
71
- * ]
72
- * // other Serenity/JS config
73
- * },
86
+ * serenity: {
87
+ * crew: [
88
+ * '@serenity-js/serenity-bdd',
89
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
90
+ * ]
91
+ * // other Serenity/JS config
74
92
  * },
75
93
  * // other WebdriverIO config
76
94
  * }
@@ -81,6 +99,16 @@ import type { StageCrewMember } from '../../StageCrewMember';
81
99
  export declare class StreamReporter implements StageCrewMember {
82
100
  private readonly output;
83
101
  private readonly stage?;
102
+ /**
103
+ * Instantiates a `StreamReporter` outputting a stream of {@apilink DomainEvent|domain events}
104
+ * to an `outputFile` at the given location.
105
+ *
106
+ * @param config
107
+ */
108
+ static fromJSON(config: {
109
+ outputFile: string;
110
+ cwd?: string;
111
+ }): StageCrewMember;
84
112
  /**
85
113
  * @param {stream~Writable} output
86
114
  * A Writable stream that should receive the output
@@ -1 +1 @@
1
- {"version":3,"file":"StreamReporter.d.ts","sourceRoot":"","sources":["../../../../src/stage/crew/stream-reporter/StreamReporter.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,qBAAa,cAAe,YAAW,eAAe;IAU9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAT3B;;;;;;OAMG;gBAEkB,MAAM,GAAE,QAAyB,EACjC,KAAK,CAAC,EAAE,KAAK;IAIlC;;;;;;;;OAQG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe;IAIzC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;CAKrC"}
1
+ {"version":3,"file":"StreamReporter.d.ts","sourceRoot":"","sources":["../../../../src/stage/crew/stream-reporter/StreamReporter.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AACH,qBAAa,cAAe,YAAW,eAAe;IAyB9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAxB3B;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe;IAS9E;;;;;;OAMG;gBAEkB,MAAM,GAAE,QAAyB,EACjC,KAAK,CAAC,EAAE,KAAK;IAIlC;;;;;;;;OAQG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,eAAe;IAIzC;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;CAKrC"}
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StreamReporter = void 0;
4
+ const tiny_types_1 = require("tiny-types");
5
+ const io_1 = require("../../../io");
4
6
  /**
5
7
  * Serialises all the {@apilink DomainEvent} objects it receives and streams
6
- * them as [ndjson](http://ndjson.org/) to the output stream.
8
+ * them as [ndjson](http://ndjson.org/) to the output stream or file.
7
9
  *
8
10
  * Useful when debugging issues related to custom Serenity/JS test runner adapters.
9
11
  *
@@ -32,43 +34,61 @@ exports.StreamReporter = void 0;
32
34
  * })
33
35
  * ```
34
36
  *
35
- * ## Registering `StreamReporter` using Protractor configuration
37
+ * ## Using `StreamReporter` with Playwright Test
38
+ *
39
+ * ```ts
40
+ * // playwright.config.ts
41
+ * import type { PlaywrightTestConfig } from '@serenity-js/playwright-test'
42
+ *
43
+ * const config: PlaywrightTestConfig = {
44
+ * testDir: './spec',
45
+ *
46
+ * reporter: [
47
+ * [ '@serenity-js/playwright-test', {
48
+ * crew: [
49
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
50
+ * ]
51
+ * // other Serenity/JS config
52
+ * }]
53
+ * ],
54
+ * // other Playwright Test config
55
+ * }
56
+ * ```
57
+ *
58
+ * ## Using `StreamReporter` with Protractor
36
59
  *
37
60
  * ```js
38
61
  * // protractor.conf.js
39
- * const { StreamReporter } = require('@serenity-js/core');
40
- *
41
62
  * exports.config = {
42
63
  * framework: 'custom',
43
64
  * frameworkPath: require.resolve('@serenity-js/protractor/adapter'),
44
65
  *
45
66
  * serenity: {
46
67
  * crew: [
47
- * new StreamReporter(process.stdout),
68
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
48
69
  * ],
49
70
  * // other Serenity/JS config
50
71
  * },
51
72
  * // other Protractor config
52
- * };
73
+ * }
53
74
  * ```
54
75
  *
55
- * ## Registering `StreamReporter` using WebdriverIO configuration
76
+ * ## Using `StreamReporter` with WebdriverIO
56
77
  *
57
78
  * ```ts
58
- * // wdio.conf.js
59
- * import { StreamReporter } from '@serenity-js/core'
79
+ * // wdio.conf.ts
60
80
  * import { WebdriverIOConfig } from '@serenity-js/webdriverio'
61
81
  *
62
82
  * export const config: WebdriverIOConfig = {
63
83
  *
64
- * framework: '@serenity-js/webdriverio',
84
+ * framework: '@serenity-js/webdriverio',
65
85
  *
66
- * serenity: {
67
- * crew: [
68
- * new StreamReporter(process.stdout),
69
- * ]
70
- * // other Serenity/JS config
71
- * },
86
+ * serenity: {
87
+ * crew: [
88
+ * '@serenity-js/serenity-bdd',
89
+ * [ '@serenity-js/core:StreamReporter', { outputFile: './events.ndjson' }]
90
+ * ]
91
+ * // other Serenity/JS config
72
92
  * },
73
93
  * // other WebdriverIO config
74
94
  * }
@@ -77,6 +97,18 @@ exports.StreamReporter = void 0;
77
97
  * @group Stage
78
98
  */
79
99
  class StreamReporter {
100
+ /**
101
+ * Instantiates a `StreamReporter` outputting a stream of {@apilink DomainEvent|domain events}
102
+ * to an `outputFile` at the given location.
103
+ *
104
+ * @param config
105
+ */
106
+ static fromJSON(config) {
107
+ const outputFile = (0, tiny_types_1.ensure)('outputFile', config?.outputFile, (0, tiny_types_1.isDefined)(), (0, tiny_types_1.isString)());
108
+ const cwd = config.cwd || process.cwd();
109
+ const fs = new io_1.FileSystem(io_1.Path.from(cwd));
110
+ return new StreamReporter(fs.createWriteStreamTo(io_1.Path.from(outputFile)));
111
+ }
80
112
  /**
81
113
  * @param {stream~Writable} output
82
114
  * A Writable stream that should receive the output
@@ -1 +1 @@
1
- {"version":3,"file":"StreamReporter.js","sourceRoot":"","sources":["../../../../src/stage/crew/stream-reporter/StreamReporter.ts"],"names":[],"mappings":";;;AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,MAAa,cAAc;IAEvB;;;;;;OAMG;IACH,YACqB,SAAmB,OAAO,CAAC,MAAM,EACjC,KAAa;QADb,WAAM,GAAN,MAAM,CAA2B;QACjC,UAAK,GAAL,KAAK,CAAQ;IAElC,CAAC;IAED;;;;;;;;OAQG;IACH,UAAU,CAAC,KAAY;QACnB,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,KAAkB;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CACb,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CACjF,CAAC;IACN,CAAC;CACJ;AAxCD,wCAwCC"}
1
+ {"version":3,"file":"StreamReporter.js","sourceRoot":"","sources":["../../../../src/stage/crew/stream-reporter/StreamReporter.ts"],"names":[],"mappings":";;;AACA,2CAAyD;AAGzD,oCAA+C;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4FG;AACH,MAAa,cAAc;IAEvB;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,CAAC,MAA4C;QACxD,MAAM,UAAU,GAAG,IAAA,mBAAM,EAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,IAAA,sBAAS,GAAE,EAAE,IAAA,qBAAQ,GAAE,CAAC,CAAC;QACrF,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAExC,MAAM,EAAE,GAAG,IAAI,eAAU,CAAC,SAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAEzC,OAAO,IAAI,cAAc,CAAC,EAAE,CAAC,mBAAmB,CAAC,SAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;OAMG;IACH,YACqB,SAAmB,OAAO,CAAC,MAAM,EACjC,KAAa;QADb,WAAM,GAAN,MAAM,CAA2B;QACjC,UAAK,GAAL,KAAK,CAAQ;IAElC,CAAC;IAED;;;;;;;;OAQG;IACH,UAAU,CAAC,KAAY;QACnB,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACH,QAAQ,CAAC,KAAkB;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CACb,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CACjF,CAAC;IACN,CAAC;CACJ;AAvDD,wCAuDC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serenity-js/core",
3
- "version": "3.7.2",
3
+ "version": "3.9.0",
4
4
  "description": "Serenity/JS Screenplay, reporting engine and core interfaces.",
5
5
  "author": {
6
6
  "name": "Jan Molak",
@@ -71,5 +71,5 @@
71
71
  "engines": {
72
72
  "node": "^16.13 || ^18.12 || ^20"
73
73
  },
74
- "gitHead": "1803ef1287e21b4b6239f0800788483fe4e9d580"
74
+ "gitHead": "9e882e469e9c6b1617e6f645b999036ba249478b"
75
75
  }
@@ -22,6 +22,7 @@ export class ActivityRelatedArtifactArchived extends ArtifactArchived {
22
22
  Name.fromJSON(o.name as string),
23
23
  Artifact.ofType(o.type as string),
24
24
  Path.fromJSON(o.path as string),
25
+ Timestamp.fromJSON(o.artifactTimestamp as string),
25
26
  Timestamp.fromJSON(o.timestamp as string),
26
27
  );
27
28
  }
@@ -32,9 +33,10 @@ export class ActivityRelatedArtifactArchived extends ArtifactArchived {
32
33
  name: Name,
33
34
  type: ArtifactType,
34
35
  path: Path,
36
+ artifactTimestamp: Timestamp,
35
37
  timestamp?: Timestamp,
36
38
  ) {
37
- super(sceneId, name, type, path, timestamp);
39
+ super(sceneId, name, type, path, artifactTimestamp, timestamp);
38
40
  ensure('activityId', activityId, isDefined());
39
41
  }
40
42
 
@@ -45,6 +47,7 @@ export class ActivityRelatedArtifactArchived extends ArtifactArchived {
45
47
  name: this.name.toJSON(),
46
48
  type: this.type.name,
47
49
  path: this.path.toJSON(),
50
+ artifactTimestamp: this.artifactTimestamp.toJSON(),
48
51
  timestamp: this.timestamp.toJSON(),
49
52
  };
50
53
  }
@@ -17,6 +17,7 @@ export class ArtifactArchived extends DomainEvent {
17
17
  Name.fromJSON(o.name as string),
18
18
  Artifact.ofType(o.type as string),
19
19
  Path.fromJSON(o.path as string),
20
+ Timestamp.fromJSON(o.artifactTimestamp as string),
20
21
  Timestamp.fromJSON(o.timestamp as string),
21
22
  );
22
23
  }
@@ -26,6 +27,7 @@ export class ArtifactArchived extends DomainEvent {
26
27
  public readonly name: Name,
27
28
  public readonly type: ArtifactType,
28
29
  public readonly path: Path,
30
+ public readonly artifactTimestamp: Timestamp,
29
31
  timestamp?: Timestamp,
30
32
  ) {
31
33
  super(timestamp);
@@ -34,6 +36,7 @@ export class ArtifactArchived extends DomainEvent {
34
36
  ensure('name', name, isDefined());
35
37
  ensure('type', type, isDefined());
36
38
  ensure('path', path, isDefined());
39
+ ensure('artifactTimestamp', artifactTimestamp, isDefined());
37
40
  }
38
41
 
39
42
  toJSON(): JSONObject {
@@ -41,6 +44,7 @@ export class ArtifactArchived extends DomainEvent {
41
44
  name: this.name.toJSON(),
42
45
  type: this.type.name,
43
46
  path: this.path.toJSON(),
47
+ createdAt: this.artifactTimestamp.toJSON(),
44
48
  timestamp: this.timestamp.toJSON(),
45
49
  };
46
50
  }
@@ -2,12 +2,14 @@ import { isRecord } from 'tiny-types/lib/objects';
2
2
  import * as util from 'util'; // eslint-disable-line unicorn/import-style
3
3
 
4
4
  import { LogicError } from '../errors';
5
- import { f, inspectedObject } from '../io';
5
+ import type { FileSystemLocation } from '../io';
6
+ import { d, f, inspectedObject } from '../io';
6
7
  import type { UsesAbilities } from './abilities';
7
8
  import type { Answerable } from './Answerable';
8
9
  import { Interaction } from './Interaction';
9
10
  import type { Optional } from './Optional';
10
11
  import type { AnswersQuestions } from './questions/AnswersQuestions';
12
+ import type { MetaQuestion } from './questions/MetaQuestion';
11
13
  import { Unanswered } from './questions/Unanswered';
12
14
  import type { RecursivelyAnswered } from './RecursivelyAnswered';
13
15
  import type { WithAnswerableProperties } from './WithAnswerableProperties';
@@ -123,9 +125,30 @@ export abstract class Question<T> {
123
125
  *
124
126
  * @param description
125
127
  * @param body
128
+ * @param [metaQuestionBody]
126
129
  */
127
- static about<Result_Type>(description: string, body: (actor: AnswersQuestions & UsesAbilities) => Promise<Result_Type> | Result_Type): QuestionAdapter<Awaited<Result_Type>> {
128
- return Question.createAdapter(new QuestionStatement(description, body));
130
+ static about<Answer_Type, Supported_Context_Type>(
131
+ description: string,
132
+ body: (actor: AnswersQuestions & UsesAbilities) => Promise<Answer_Type> | Answer_Type,
133
+ metaQuestionBody: (answerable: Answerable<Supported_Context_Type>) => Question<Promise<Answer_Type>> | Question<Answer_Type>,
134
+ ): MetaQuestionAdapter<Supported_Context_Type, Awaited<Answer_Type>>
135
+
136
+ static about<Answer_Type>(
137
+ description: string,
138
+ body: (actor: AnswersQuestions & UsesAbilities) => Promise<Answer_Type> | Answer_Type
139
+ ): QuestionAdapter<Awaited<Answer_Type>>
140
+
141
+ static about<Answer_Type, Supported_Context_Type extends Answerable<any>>(
142
+ description: string,
143
+ body: (actor: AnswersQuestions & UsesAbilities) => Promise<Answer_Type> | Answer_Type,
144
+ metaQuestionBody?: (answerable: Supported_Context_Type) => QuestionAdapter<Answer_Type>,
145
+ ): any
146
+ {
147
+ const statement = typeof metaQuestionBody === 'function'
148
+ ? new MetaQuestionStatement(description, body, metaQuestionBody)
149
+ : new QuestionStatement(description, body);
150
+
151
+ return Question.createAdapter(statement);
129
152
  }
130
153
 
131
154
  /**
@@ -429,13 +452,25 @@ export type QuestionAdapterFieldDecorator<Original_Type> = {
429
452
  *
430
453
  * @group Questions
431
454
  */
432
- export type QuestionAdapter<T> =
433
- & Question<Promise<T>>
455
+ export type QuestionAdapter<Answer_Type> =
456
+ & Question<Promise<Answer_Type>>
434
457
  & Interaction
435
458
  & { isPresent(): Question<Promise<boolean>>; } // more specialised Optional
436
- & QuestionAdapterFieldDecorator<T>;
459
+ & QuestionAdapterFieldDecorator<Answer_Type>;
437
460
 
438
- /** @package */
461
+ /**
462
+ * An extension of {@apilink QuestionAdapter}, that in addition to proxying methods and fields
463
+ * of the wrapped object can also act as a {@apilink MetaQuestion}.
464
+ *
465
+ * @group Questions
466
+ */
467
+ export type MetaQuestionAdapter<Context_Type, Answer_Type> =
468
+ & QuestionAdapter<Answer_Type>
469
+ & MetaQuestion<Context_Type, Answer_Type>
470
+
471
+ /**
472
+ * @package
473
+ */
439
474
  class QuestionStatement<Answer_Type> extends Interaction implements Question<Promise<Answer_Type>>, Optional {
440
475
 
441
476
  private answer: Answer_Type | Unanswered = new Unanswered();
@@ -443,8 +478,9 @@ class QuestionStatement<Answer_Type> extends Interaction implements Question<Pro
443
478
  constructor(
444
479
  private subject: string,
445
480
  private readonly body: (actor: AnswersQuestions & UsesAbilities, ...Parameters) => Promise<Answer_Type> | Answer_Type,
481
+ location: FileSystemLocation = QuestionStatement.callerLocation(4),
446
482
  ) {
447
- super(subject, QuestionStatement.callerLocation(4));
483
+ super(subject, location);
448
484
  }
449
485
 
450
486
  /**
@@ -490,6 +526,29 @@ class QuestionStatement<Answer_Type> extends Interaction implements Question<Pro
490
526
  }
491
527
  }
492
528
 
529
+ /**
530
+ * @package
531
+ */
532
+ class MetaQuestionStatement<Answer_Type, Supported_Context_Type extends Answerable<any>>
533
+ extends QuestionStatement<Answer_Type>
534
+ implements MetaQuestion<Supported_Context_Type, Answer_Type>
535
+ {
536
+ constructor(
537
+ subject: string,
538
+ body: (actor: AnswersQuestions & UsesAbilities, ...Parameters) => Promise<Answer_Type> | Answer_Type,
539
+ private readonly metaQuestionBody: (answerable: Answerable<Supported_Context_Type>) => QuestionAdapter<Answer_Type>,
540
+ ) {
541
+ super(subject, body);
542
+ }
543
+
544
+ of(answerable: Answerable<Supported_Context_Type>): Question<Promise<Answer_Type>> | Question<Answer_Type> {
545
+ return Question.about(
546
+ d`${ this.toString() } of ${ answerable }`,
547
+ actor => actor.answer(this.metaQuestionBody(answerable))
548
+ );
549
+ }
550
+ }
551
+
493
552
  /**
494
553
  * @package
495
554
  */
@@ -2,11 +2,12 @@ import type { Answerable } from '../Answerable';
2
2
  import type { Question } from '../Question';
3
3
 
4
4
  /**
5
- * A "meta question" is a {@apilink Question} about another {@apilink Answerable},
6
- * used to retrieve a transformed version of the value that answerable holds.
5
+ * A meta-question is a {@apilink Question} that can be answered
6
+ * in the context of another {@apilink Answerable},
7
+ * typically to transform its value.
7
8
  *
8
- * For example, the question {@apilink Text.of}
9
- * returns text content of a {@apilink PageElement}.
9
+ * For example, the question {@apilink Text.of} can be answered in the context
10
+ * of a {@apilink PageElement} to return its text content.
10
11
  *
11
12
  * {@apilink MetaQuestion|Meta questions} are typically used when filtering a {@apilink List}.
12
13
  *
@@ -15,15 +16,15 @@ import type { Question } from '../Question';
15
16
  *
16
17
  * @group Questions
17
18
  */
18
- export interface MetaQuestion<Supported_Answerable_Type extends Answerable<any>, Answer> {
19
+ export interface MetaQuestion<Supported_Context_Type, Answer_Type> {
19
20
 
20
21
  /**
21
- * Transforms a given `answerable` to another {@apilink Question}.
22
+ * Answers the given `MetaQuestion` in the context of another {@apilink Answerable}.
22
23
  *
23
24
  * #### Learn more
24
25
  * - {@apilink List}
25
26
  */
26
- of(answerable: Supported_Answerable_Type): Question<Answer>;
27
+ of(context: Answerable<Supported_Context_Type>): Question<Promise<Answer_Type>> | Question<Answer_Type>;
27
28
 
28
29
  /**
29
30
  * Human-readable description of this {@apilink MetaQuestion},
@@ -20,7 +20,7 @@ import type { StageCrewMember } from '../../StageCrewMember';
20
20
  import { Hash } from './Hash';
21
21
 
22
22
  /**
23
- * Stores any {@apilink Artifact|artifacts} emitted via {@apilink ArtifactGenerated} events on the {@apilink FileSystem}
23
+ * Stores any {@apilink Artifact|artifacts} emitted via {@apilink ArtifactGenerated} events on the {@apilink FileSystem}.
24
24
  *
25
25
  * ## Registering `ArtifactArchiver` programmatically
26
26
  *
@@ -35,49 +35,69 @@ import { Hash } from './Hash';
35
35
  * })
36
36
  * ```
37
37
  *
38
- * ## Registering `ArtifactArchiver` using Protractor configuration
38
+ * ## Using `ArtifactArchiver` with Playwright Test
39
+ *
40
+ * ```ts
41
+ * // playwright.config.ts
42
+ * import type { PlaywrightTestConfig } from '@serenity-js/playwright-test'
43
+ *
44
+ * const config: PlaywrightTestConfig = {
45
+ * testDir: './spec',
46
+ *
47
+ * reporter: [
48
+ * [ '@serenity-js/playwright-test', {
49
+ * crew: [
50
+ * '@serenity-js/serenity-bdd',
51
+ * [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
52
+ * [ '@serenity-js/core:StreamReporter', { outputFile: 'target/events.ndjson' }]
53
+ * ]
54
+ * // other Serenity/JS config
55
+ * }]
56
+ * ],
57
+ * // other Playwright Test config
58
+ * }
59
+ * ```
60
+ *
61
+ * ## Using `ArtifactArchiver` with Protractor
39
62
  *
40
63
  * ```js
41
64
  * // protractor.conf.js
42
- * const { ArtifactArchiver } = require('@serenity-js/core')
43
- *
44
65
  * exports.config = {
45
66
  * framework: 'custom',
46
67
  * frameworkPath: require.resolve('@serenity-js/protractor/adapter'),
47
68
  *
48
69
  * serenity: {
49
70
  * crew: [
50
- * ArtifactArchiver.storingArtifactsAt('./target/site/serenity'),
71
+ * '@serenity-js/serenity-bdd',
72
+ * [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
51
73
  * ],
52
74
  * // other Serenity/JS config
53
75
  * },
54
76
  * // other Protractor config
55
- * };
77
+ * }
56
78
  * ```
57
79
  *
58
- * ## Registering `ArtifactArchiver` using WebdriverIO configuration
80
+ * ## Using `ArtifactArchiver` with WebdriverIO
59
81
  *
60
82
  * ```ts
61
- * // wdio.conf.js
62
- * import { ArtifactArchiver } from '@serenity-js/core'
83
+ * // wdio.conf.ts
63
84
  * import { WebdriverIOConfig } from '@serenity-js/webdriverio'
64
85
  *
65
86
  * export const config: WebdriverIOConfig = {
66
87
  *
67
- * framework: '@serenity-js/webdriverio',
88
+ * framework: '@serenity-js/webdriverio',
68
89
  *
69
- * serenity: {
70
- * crew: [
71
- * ArtifactArchiver.storingArtifactsAt(`/target/site/serenity`),
72
- * ]
73
- * // other Serenity/JS config
74
- * },
75
- * // other WebdriverIO config
90
+ * serenity: {
91
+ * crew: [
92
+ * '@serenity-js/serenity-bdd',
93
+ * [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
94
+ * ]
95
+ * // other Serenity/JS config
96
+ * },
97
+ * // other WebdriverIO config
76
98
  * }
77
99
  * ```
78
100
  *
79
- * [ '@serenity-js/core:ArtifactArchiver', { outputDirectory: 'target/site/serenity' } ],
80
- *
81
101
  * @group Stage
82
102
  */
83
103
  export class ArtifactArchiver implements StageCrewMember {
@@ -209,6 +229,8 @@ export class ArtifactArchiver implements StageCrewMember {
209
229
  event.name,
210
230
  event.artifact.constructor as ArtifactType,
211
231
  relativePathToArtifact,
232
+ event.timestamp,
233
+ this.stage.currentTime(),
212
234
  ));
213
235
  } else if (event instanceof ArtifactGenerated) {
214
236
  this.stage.announce(new ArtifactArchived(
@@ -216,6 +238,8 @@ export class ArtifactArchiver implements StageCrewMember {
216
238
  event.name,
217
239
  event.artifact.constructor as ArtifactType,
218
240
  relativePathToArtifact,
241
+ event.timestamp,
242
+ this.stage.currentTime(),
219
243
  ));
220
244
  }
221
245
  };