@serenity-js/playwright-test 3.41.1 → 3.42.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 (146) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/esm/api/PerformActivitiesAsPlaywrightSteps.d.ts +14 -0
  3. package/esm/api/PerformActivitiesAsPlaywrightSteps.d.ts.map +1 -0
  4. package/esm/api/PerformActivitiesAsPlaywrightSteps.js +40 -0
  5. package/esm/api/PerformActivitiesAsPlaywrightSteps.js.map +1 -0
  6. package/esm/api/PlaywrightTestConfig.d.ts +42 -0
  7. package/esm/api/PlaywrightTestConfig.d.ts.map +1 -0
  8. package/esm/api/PlaywrightTestConfig.js +2 -0
  9. package/esm/api/PlaywrightTestConfig.js.map +1 -0
  10. package/esm/api/WorkerEventStreamReader.d.ts +13 -0
  11. package/esm/api/WorkerEventStreamReader.d.ts.map +1 -0
  12. package/esm/api/WorkerEventStreamReader.js +18 -0
  13. package/esm/api/WorkerEventStreamReader.js.map +1 -0
  14. package/esm/api/WorkerEventStreamWriter.d.ts +24 -0
  15. package/esm/api/WorkerEventStreamWriter.d.ts.map +1 -0
  16. package/esm/api/WorkerEventStreamWriter.js +79 -0
  17. package/esm/api/WorkerEventStreamWriter.js.map +1 -0
  18. package/esm/api/index.d.ts +4 -0
  19. package/esm/api/index.d.ts.map +1 -0
  20. package/esm/api/index.js +4 -0
  21. package/esm/api/index.js.map +1 -0
  22. package/esm/api/serenity-fixtures.d.ts +452 -0
  23. package/esm/api/serenity-fixtures.d.ts.map +1 -0
  24. package/esm/api/serenity-fixtures.js +2 -0
  25. package/esm/api/serenity-fixtures.js.map +1 -0
  26. package/esm/api/test-api.d.ts +387 -0
  27. package/esm/api/test-api.d.ts.map +1 -0
  28. package/esm/api/test-api.js +521 -0
  29. package/esm/api/test-api.js.map +1 -0
  30. package/esm/events/EventFactory.d.ts +16 -0
  31. package/esm/events/EventFactory.d.ts.map +1 -0
  32. package/esm/events/EventFactory.js +90 -0
  33. package/esm/events/EventFactory.js.map +1 -0
  34. package/esm/events/PlaywrightSceneId.d.ts +7 -0
  35. package/esm/events/PlaywrightSceneId.d.ts.map +1 -0
  36. package/esm/events/PlaywrightSceneId.js +15 -0
  37. package/esm/events/PlaywrightSceneId.js.map +1 -0
  38. package/esm/events/index.d.ts +3 -0
  39. package/esm/events/index.d.ts.map +1 -0
  40. package/esm/events/index.js +3 -0
  41. package/esm/events/index.js.map +1 -0
  42. package/esm/index.d.ts +3 -0
  43. package/esm/index.d.ts.map +1 -0
  44. package/esm/index.js +3 -0
  45. package/esm/index.js.map +1 -0
  46. package/esm/reporter/PlaywrightErrorParser.d.ts +7 -0
  47. package/esm/reporter/PlaywrightErrorParser.d.ts.map +1 -0
  48. package/esm/reporter/PlaywrightErrorParser.js +23 -0
  49. package/esm/reporter/PlaywrightErrorParser.js.map +1 -0
  50. package/esm/reporter/PlaywrightEventBuffer.d.ts +25 -0
  51. package/esm/reporter/PlaywrightEventBuffer.d.ts.map +1 -0
  52. package/esm/reporter/PlaywrightEventBuffer.js +143 -0
  53. package/esm/reporter/PlaywrightEventBuffer.js.map +1 -0
  54. package/esm/reporter/PlaywrightStepReporter.d.ts +14 -0
  55. package/esm/reporter/PlaywrightStepReporter.d.ts.map +1 -0
  56. package/esm/reporter/PlaywrightStepReporter.js +38 -0
  57. package/esm/reporter/PlaywrightStepReporter.js.map +1 -0
  58. package/esm/reporter/PlaywrightTestSceneIdFactory.d.ts +8 -0
  59. package/esm/reporter/PlaywrightTestSceneIdFactory.d.ts.map +1 -0
  60. package/esm/reporter/PlaywrightTestSceneIdFactory.js +11 -0
  61. package/esm/reporter/PlaywrightTestSceneIdFactory.js.map +1 -0
  62. package/esm/reporter/SerenityReporterForPlaywrightTest.d.ts +58 -0
  63. package/esm/reporter/SerenityReporterForPlaywrightTest.d.ts.map +1 -0
  64. package/esm/reporter/SerenityReporterForPlaywrightTest.js +112 -0
  65. package/esm/reporter/SerenityReporterForPlaywrightTest.js.map +1 -0
  66. package/esm/reporter/index.d.ts +3 -0
  67. package/esm/reporter/index.d.ts.map +1 -0
  68. package/esm/reporter/index.js +3 -0
  69. package/esm/reporter/index.js.map +1 -0
  70. package/lib/api/PerformActivitiesAsPlaywrightSteps.d.ts +1 -1
  71. package/lib/api/PerformActivitiesAsPlaywrightSteps.d.ts.map +1 -1
  72. package/lib/api/PerformActivitiesAsPlaywrightSteps.js +2 -2
  73. package/lib/api/PerformActivitiesAsPlaywrightSteps.js.map +1 -1
  74. package/lib/api/PlaywrightTestConfig.d.ts +1 -1
  75. package/lib/api/PlaywrightTestConfig.d.ts.map +1 -1
  76. package/lib/api/WorkerEventStreamReader.d.ts +1 -1
  77. package/lib/api/WorkerEventStreamReader.d.ts.map +1 -1
  78. package/lib/api/WorkerEventStreamReader.js +1 -1
  79. package/lib/api/WorkerEventStreamReader.js.map +1 -1
  80. package/lib/api/WorkerEventStreamWriter.d.ts +2 -2
  81. package/lib/api/WorkerEventStreamWriter.d.ts.map +1 -1
  82. package/lib/api/WorkerEventStreamWriter.js +1 -1
  83. package/lib/api/WorkerEventStreamWriter.js.map +1 -1
  84. package/lib/api/index.d.ts +3 -3
  85. package/lib/api/index.d.ts.map +1 -1
  86. package/lib/api/index.js +3 -3
  87. package/lib/api/index.js.map +1 -1
  88. package/lib/api/test-api.d.ts +3 -3
  89. package/lib/api/test-api.d.ts.map +1 -1
  90. package/lib/api/test-api.js +12 -12
  91. package/lib/api/test-api.js.map +1 -1
  92. package/lib/events/EventFactory.d.ts +3 -3
  93. package/lib/events/EventFactory.d.ts.map +1 -1
  94. package/lib/events/EventFactory.js +6 -6
  95. package/lib/events/EventFactory.js.map +1 -1
  96. package/lib/events/PlaywrightSceneId.d.ts +1 -1
  97. package/lib/events/PlaywrightSceneId.d.ts.map +1 -1
  98. package/lib/events/PlaywrightSceneId.js +1 -1
  99. package/lib/events/PlaywrightSceneId.js.map +1 -1
  100. package/lib/events/index.d.ts +2 -2
  101. package/lib/events/index.d.ts.map +1 -1
  102. package/lib/events/index.js +2 -2
  103. package/lib/events/index.js.map +1 -1
  104. package/lib/index.d.ts +2 -2
  105. package/lib/index.d.ts.map +1 -1
  106. package/lib/index.js +3 -3
  107. package/lib/index.js.map +1 -1
  108. package/lib/package.json +1 -0
  109. package/lib/reporter/PlaywrightEventBuffer.d.ts +1 -1
  110. package/lib/reporter/PlaywrightEventBuffer.d.ts.map +1 -1
  111. package/lib/reporter/PlaywrightEventBuffer.js +12 -12
  112. package/lib/reporter/PlaywrightEventBuffer.js.map +1 -1
  113. package/lib/reporter/PlaywrightStepReporter.d.ts +1 -1
  114. package/lib/reporter/PlaywrightStepReporter.d.ts.map +1 -1
  115. package/lib/reporter/PlaywrightStepReporter.js +2 -2
  116. package/lib/reporter/PlaywrightStepReporter.js.map +1 -1
  117. package/lib/reporter/PlaywrightTestSceneIdFactory.d.ts +2 -2
  118. package/lib/reporter/PlaywrightTestSceneIdFactory.d.ts.map +1 -1
  119. package/lib/reporter/PlaywrightTestSceneIdFactory.js +1 -1
  120. package/lib/reporter/PlaywrightTestSceneIdFactory.js.map +1 -1
  121. package/lib/reporter/SerenityReporterForPlaywrightTest.d.ts +1 -1
  122. package/lib/reporter/SerenityReporterForPlaywrightTest.d.ts.map +1 -1
  123. package/lib/reporter/SerenityReporterForPlaywrightTest.js +8 -8
  124. package/lib/reporter/SerenityReporterForPlaywrightTest.js.map +1 -1
  125. package/lib/reporter/index.d.ts +2 -2
  126. package/lib/reporter/index.d.ts.map +1 -1
  127. package/lib/reporter/index.js +2 -2
  128. package/lib/reporter/index.js.map +1 -1
  129. package/package.json +58 -10
  130. package/src/api/PerformActivitiesAsPlaywrightSteps.ts +2 -2
  131. package/src/api/PlaywrightTestConfig.ts +1 -1
  132. package/src/api/WorkerEventStreamReader.ts +2 -2
  133. package/src/api/WorkerEventStreamWriter.ts +2 -2
  134. package/src/api/index.ts +3 -3
  135. package/src/api/test-api.ts +8 -8
  136. package/src/events/EventFactory.ts +5 -5
  137. package/src/events/PlaywrightSceneId.ts +1 -1
  138. package/src/events/index.ts +2 -2
  139. package/src/index.ts +2 -2
  140. package/src/reporter/PlaywrightEventBuffer.ts +8 -8
  141. package/src/reporter/PlaywrightStepReporter.ts +4 -4
  142. package/src/reporter/PlaywrightTestSceneIdFactory.ts +2 -2
  143. package/src/reporter/SerenityReporterForPlaywrightTest.ts +6 -6
  144. package/src/reporter/index.ts +2 -2
  145. package/tsconfig-cjs.build.json +18 -0
  146. package/tsconfig-esm.build.json +18 -0
@@ -0,0 +1,521 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import { mergeTests, test as playwrightBaseTest } from '@playwright/test';
5
+ import { AnsiDiffFormatter, Cast, Clock, Duration, Serenity, TakeNotes } from '@serenity-js/core';
6
+ import { SceneFinishes, SceneTagged } from '@serenity-js/core/events';
7
+ import { BrowserTag, ExecutionSuccessful, PlatformTag } from '@serenity-js/core/model';
8
+ import { BrowseTheWebWithPlaywright, SerenitySelectorEngines } from '@serenity-js/playwright';
9
+ import { CallAnApi } from '@serenity-js/rest';
10
+ import { Photographer, TakePhotosOfFailures } from '@serenity-js/web';
11
+ import { ensure, isFunction, property } from 'tiny-types';
12
+ import { PlaywrightSceneId } from '../events/index.js';
13
+ import { PlaywrightStepReporter, } from '../reporter/index.js';
14
+ import { PlaywrightTestSceneIdFactory } from '../reporter/PlaywrightTestSceneIdFactory.js';
15
+ import { PerformActivitiesAsPlaywrightSteps } from './PerformActivitiesAsPlaywrightSteps.js';
16
+ import { WorkerEventStreamWriter } from './WorkerEventStreamWriter.js';
17
+ export const fixtures = {
18
+ extraContextOptions: [
19
+ { defaultNavigationWaitUntil: 'load' },
20
+ { option: true }
21
+ ],
22
+ defaultActorName: [
23
+ 'Serena',
24
+ { option: true },
25
+ ],
26
+ cueTimeout: [
27
+ Duration.ofSeconds(5),
28
+ { option: true },
29
+ ],
30
+ interactionTimeout: [
31
+ Duration.ofSeconds(5),
32
+ { option: true },
33
+ ],
34
+ crew: [
35
+ [Photographer.whoWill(TakePhotosOfFailures)],
36
+ { option: true },
37
+ ],
38
+ axios: async ({ baseURL, extraHTTPHeaders, proxy }, use) => {
39
+ await use({
40
+ baseURL: baseURL,
41
+ headers: extraHTTPHeaders,
42
+ proxy: proxy && proxy?.server
43
+ ? asProxyConfig(proxy)
44
+ : undefined,
45
+ });
46
+ },
47
+ actors: [
48
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
49
+ async ({ axios, extraAbilities, extraContextOptions, page }, use) => {
50
+ await use(Cast.where(actor => {
51
+ const abilities = Array.isArray(extraAbilities)
52
+ ? extraAbilities
53
+ : extraAbilities(actor.name);
54
+ return actor.whoCan(BrowseTheWebWithPlaywright.usingPage(page, extraContextOptions), TakeNotes.usingAnEmptyNotepad(), CallAnApi.using(axios), ...abilities);
55
+ }));
56
+ },
57
+ { option: true },
58
+ ],
59
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
60
+ platform: [async ({}, use) => {
61
+ const platform = os.platform();
62
+ // https://nodejs.org/api/process.html#process_process_platform
63
+ const name = platform === 'win32'
64
+ ? 'Windows'
65
+ : (platform === 'darwin' ? 'macOS' : 'Linux');
66
+ await use({ name, version: os.release() });
67
+ }, { scope: 'worker' }],
68
+ diffFormatterInternal: [
69
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
70
+ async ({}, use) => {
71
+ const diffFormatter = new AnsiDiffFormatter();
72
+ await use(diffFormatter);
73
+ },
74
+ { scope: 'worker', box: true }
75
+ ],
76
+ sceneIdFactoryInternal: [
77
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
78
+ async ({}, use) => {
79
+ await use(new PlaywrightTestSceneIdFactory());
80
+ },
81
+ { scope: 'worker', box: true },
82
+ ],
83
+ serenity: [
84
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
85
+ async ({ playwright, sceneIdFactoryInternal }, use, workerInfo) => {
86
+ const clock = new Clock();
87
+ const cwd = process.cwd();
88
+ const serenity = new Serenity(clock, cwd, sceneIdFactoryInternal);
89
+ const serenitySelectorEngines = new SerenitySelectorEngines();
90
+ await serenitySelectorEngines.ensureRegisteredWith(playwright.selectors);
91
+ await use(serenity);
92
+ },
93
+ { scope: 'worker', box: true }
94
+ ],
95
+ eventStreamWriterInternal: [
96
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
97
+ async ({}, use, workerInfo) => {
98
+ const serenityOutputDirectory = path.join(workerInfo.project.outputDir, 'serenity');
99
+ const eventStreamWriter = new WorkerEventStreamWriter(serenityOutputDirectory, workerInfo);
100
+ await use(eventStreamWriter);
101
+ },
102
+ { scope: 'worker', box: true },
103
+ ],
104
+ configureWorkerInternal: [
105
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
106
+ async ({ diffFormatterInternal, eventStreamWriterInternal, sceneIdFactoryInternal, serenity, browser }, use, info) => {
107
+ serenity.configure({
108
+ actors: Cast.where(actor => actor.whoCan(BrowseTheWebWithPlaywright.using(browser), TakeNotes.usingAnEmptyNotepad())),
109
+ crew: [
110
+ eventStreamWriterInternal,
111
+ ],
112
+ diffFormatter: diffFormatterInternal,
113
+ });
114
+ sceneIdFactoryInternal.setTestId(`worker-${info.workerIndex}`);
115
+ const workerBeforeAllSceneId = serenity.assignNewSceneId();
116
+ await use(void 0);
117
+ await eventStreamWriterInternal.persistAll(workerBeforeAllSceneId);
118
+ },
119
+ { scope: 'worker', auto: true, box: true },
120
+ ],
121
+ configureScenarioInternal: [
122
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
123
+ async ({ actors, browser, browserName, crew, cueTimeout, diffFormatterInternal, eventStreamWriterInternal, interactionTimeout, platform, sceneIdFactoryInternal, serenity }, use, info) => {
124
+ serenity.configure({
125
+ actors: asCast(actors),
126
+ diffFormatter: diffFormatterInternal,
127
+ cueTimeout: asDuration(cueTimeout),
128
+ interactionTimeout: asDuration(interactionTimeout),
129
+ crew: [
130
+ ...crew,
131
+ new PlaywrightStepReporter(info),
132
+ ],
133
+ });
134
+ const playwrightSceneId = PlaywrightSceneId.from(info.project.name, { id: info.testId, repeatEachIndex: info.repeatEachIndex }, { retry: info.retry });
135
+ sceneIdFactoryInternal.setTestId(playwrightSceneId.value);
136
+ const sceneId = serenity.assignNewSceneId();
137
+ serenity.announce(new SceneTagged(sceneId, new PlatformTag(platform.name, platform.version), serenity.currentTime()), new SceneTagged(sceneId, new BrowserTag(browserName, browser.version()), serenity.currentTime()));
138
+ await use(void 0);
139
+ try {
140
+ serenity.announce(new SceneFinishes(sceneId, new ExecutionSuccessful(), serenity.currentTime()));
141
+ await serenity.waitForNextCue();
142
+ }
143
+ finally {
144
+ await eventStreamWriterInternal.persist(playwrightSceneId.value);
145
+ }
146
+ },
147
+ { auto: true, box: true, }
148
+ ],
149
+ extraAbilities: [
150
+ [],
151
+ { option: true },
152
+ ],
153
+ actorCalled: [
154
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
155
+ async ({ serenity }, use) => {
156
+ const actorCalled = (name) => {
157
+ const actor = serenity.theActorCalled(name);
158
+ return actor.whoCan(new PerformActivitiesAsPlaywrightSteps(actor, serenity, it));
159
+ };
160
+ await use(actorCalled);
161
+ },
162
+ { scope: 'worker' },
163
+ ],
164
+ actor: async ({ actorCalled, defaultActorName }, use) => {
165
+ await use(actorCalled(defaultActorName));
166
+ },
167
+ };
168
+ function createTestApi(baseTest) {
169
+ return {
170
+ useFixtures(customFixtures) {
171
+ return createTestApi(baseTest.extend(customFixtures));
172
+ },
173
+ beforeAll: baseTest.beforeAll,
174
+ beforeEach: baseTest.beforeEach,
175
+ afterEach: baseTest.afterEach,
176
+ afterAll: baseTest.afterAll,
177
+ describe: baseTest.describe,
178
+ expect: baseTest.expect,
179
+ it: baseTest,
180
+ test: baseTest,
181
+ };
182
+ }
183
+ const api = createTestApi(playwrightBaseTest).useFixtures(fixtures);
184
+ /**
185
+ * Declares a single test scenario.
186
+ *
187
+ * ## Example
188
+ *
189
+ * ```typescript
190
+ * import { Ensure, equals } from '@serenity-js/assertions'
191
+ * import { describe, it } from '@serenity-js/playwright-test'
192
+ *
193
+ * describe(`Todo List App`, () => {
194
+ *
195
+ * it(`should allow me to add a todo item`, async ({ actor }) => {
196
+ * await actor.attemptsTo(
197
+ * startWithAnEmptyList(),
198
+ *
199
+ * recordItem('Buy some milk'),
200
+ *
201
+ * Ensure.that(itemNames(), equals([
202
+ * 'Buy some milk',
203
+ * ])),
204
+ * )
205
+ * })
206
+ *
207
+ * it('supports multiple actors using separate browsers', async ({ actorCalled }) => {
208
+ * await actorCalled('Alice').attemptsTo(
209
+ * startWithAListContaining(
210
+ * 'Feed the cat'
211
+ * ),
212
+ * )
213
+ *
214
+ * await actorCalled('Bob').attemptsTo(
215
+ * startWithAListContaining(
216
+ * 'Walk the dog'
217
+ * ),
218
+ * )
219
+ *
220
+ * await actorCalled('Alice').attemptsTo(
221
+ * Ensure.that(itemNames(), equals([
222
+ * 'Feed the cat'
223
+ * ])),
224
+ * )
225
+ *
226
+ * await actorCalled('Bob').attemptsTo(
227
+ * Ensure.that(itemNames(), equals([
228
+ * 'Walk the dog'
229
+ * ])),
230
+ * )
231
+ * })
232
+ * })
233
+ * ```
234
+ *
235
+ * ## Learn more
236
+ * - [Grouping test scenarios](https://serenity-js.org/api/playwright-test/function/describe/)
237
+ * - [`SerenityFixtures`](https://serenity-js.org/api/playwright-test/interface/SerenityFixtures/)
238
+ * - [Playwright Test `test` function](https://playwright.dev/docs/api/class-test#test-call)
239
+ * - [Serenity/JS + Playwright Test project template](https://github.com/serenity-js/serenity-js-playwright-test-template/)
240
+ */
241
+ export const it = api.it;
242
+ /**
243
+ * Declares a single test scenario. Alias for [`it`](https://serenity-js.org/api/playwright-test/function/it/).
244
+ */
245
+ export const test = api.test;
246
+ /**
247
+ * Declares a group of test scenarios.
248
+ *
249
+ * ## Example
250
+ *
251
+ * ```typescript
252
+ * import { Ensure, equals } from '@serenity-js/assertions'
253
+ * import { describe, it, test } from '@serenity-js/playwright-test'
254
+ * import { Photographer, TakePhotosOfFailures, Value } from '@serenity-js/web'
255
+ *
256
+ * describe(`Todo List App`, () => {
257
+ *
258
+ * test.use({
259
+ * defaultActorName: 'Serena',
260
+ * crew: [
261
+ * Photographer.whoWill(TakePhotosOfFailures),
262
+ * ],
263
+ * })
264
+ *
265
+ * it(`should allow me to add a todo item`, async ({ actor }) => {
266
+ * await actor.attemptsTo(
267
+ * startWithAnEmptyList(),
268
+ *
269
+ * recordItem('Buy some milk'),
270
+ *
271
+ * Ensure.that(itemNames(), equals([
272
+ * 'Buy some milk',
273
+ * ])),
274
+ * )
275
+ * })
276
+ *
277
+ * it('should clear text input field when an item is added', async ({ actor }) => {
278
+ * await actor.attemptsTo(
279
+ * startWithAnEmptyList(),
280
+ *
281
+ * recordItem('Buy some milk'),
282
+ *
283
+ * Ensure.that(Value.of(newTodoInput()), equals('')),
284
+ * )
285
+ * })
286
+ * })
287
+ * ```
288
+ *
289
+ * ## Learn more
290
+ * - Declaring a Serenity/JS [test scenario](https://serenity-js.org/api/playwright-test/function/it/)
291
+ * - [Playwright Test `describe` function](https://playwright.dev/docs/api/class-test#test-describe-1)
292
+ * - [Serenity/JS + Playwright Test project template](https://github.com/serenity-js/serenity-js-playwright-test-template/)
293
+ */
294
+ export const describe = api.describe;
295
+ export const beforeAll = api.beforeAll;
296
+ export const beforeEach = api.beforeEach;
297
+ export const afterEach = api.afterEach;
298
+ export const afterAll = api.afterAll;
299
+ export const expect = api.expect;
300
+ export const useFixtures = api.useFixtures;
301
+ /**
302
+ * Creates a Serenity/JS BDD-style test API around the given Playwright [base test](https://playwright.dev/docs/test-fixtures).
303
+ *
304
+ * ## Using default configuration
305
+ *
306
+ * When your test scenario doesn't require [custom test fixtures](https://playwright.dev/docs/test-fixtures),
307
+ * and you're happy with the default [base test](https://playwright.dev/docs/api/class-test#test-call) offered by Playwright,
308
+ * you can import test API functions such as [`describe`](https://serenity-js.org/api/playwright-test/function/describe/) and [`it`](https://serenity-js.org/api/playwright-test/function/describe/) directly from `@serenity-js/playwright-test`.
309
+ *
310
+ * ```typescript
311
+ * import { describe, it, test } from '@serenity-js/playwright-test'
312
+ * import { Log } from '@serenity-js/core'
313
+ *
314
+ * // override default fixtures if needed
315
+ * test.use({
316
+ * defaultActorName: 'Alice'
317
+ * })
318
+ *
319
+ * describe('Serenity/JS default test API', () => {
320
+ *
321
+ * it('enables easy access to actors and standard Playwright fixtures', async ({ actor, browserName }) => {
322
+ * await actor.attemptsTo(
323
+ * Log.the(browserName),
324
+ * )
325
+ * })
326
+ * })
327
+ * ```
328
+ *
329
+ * In the above example, importing test API functions directly from `@serenity-js/playwright-test` is the equivalent of the following setup:
330
+ *
331
+ * ```typescript
332
+ * import { test as playwrightBaseTest } from '@playwright/test'
333
+ * import { useBase } from '@serenity-js/playwright-test'
334
+ *
335
+ * const { describe, it, test, beforeEach, afterEach } = useBase(playwrightBaseTest)
336
+ * ```
337
+ *
338
+ * ## Using custom fixtures
339
+ *
340
+ * When your test scenario requires [custom test fixtures](https://playwright.dev/docs/test-fixtures),
341
+ * but you're still happy with the default [base test](https://playwright.dev/docs/api/class-test#test-call) offered by Playwright,
342
+ * you can create fixture-aware test API functions such as [`describe`](https://serenity-js.org/api/playwright-test/function/describe/) and [`it`](https://serenity-js.org/api/playwright-test/function/describe/)
343
+ * by calling [`useFixtures`](https://serenity-js.org/api/playwright-test/function/useFixtures/).
344
+ *
345
+ * For example, you can create a test scenario using a static `message` fixture as follows:
346
+ *
347
+ * ```typescript
348
+ * import { useFixtures } from '@serenity-js/playwright-test'
349
+ * import { Log } from '@serenity-js/core'
350
+ *
351
+ * const { describe, it } = useFixtures<{ message: string }>({
352
+ * message: 'Hello world!'
353
+ * })
354
+ *
355
+ * describe('Serenity/JS useFixtures', () => {
356
+ *
357
+ * it('enables injecting custom test fixtures into test scenarios', async ({ actor, message }) => {
358
+ * await actor.attemptsTo(
359
+ * Log.the(message),
360
+ * )
361
+ * })
362
+ * })
363
+ * ```
364
+ *
365
+ * The value of your test fixtures can be either static or dynamic and based on the value of other fixtures.
366
+ *
367
+ * To create a dynamic test fixture use the [function syntax](https://playwright.dev/docs/test-fixtures):
368
+ *
369
+ * ```typescript
370
+ * import { Log } from '@serenity-js/core'
371
+ * import { useFixtures } from '@serenity-js/playwright-test'
372
+ *
373
+ * const { describe, it } = useFixtures<{ message: string }>({
374
+ * message: async ({ actor }, use) => {
375
+ * await use(`Hello, ${ actor.name }`);
376
+ * }
377
+ * })
378
+ *
379
+ * describe('Serenity/JS useFixtures', () => {
380
+ *
381
+ * it('enables injecting custom test fixtures into test scenarios', async ({ actor, message }) => {
382
+ * await actor.attemptsTo(
383
+ * Log.the(message),
384
+ * )
385
+ * })
386
+ * })
387
+ * ```
388
+ *
389
+ * In the above example, creating test API functions via `useFixtures` is the equivalent of the following setup:
390
+ *
391
+ * ```typescript
392
+ * import { test as playwrightBaseTest } from '@playwright/test'
393
+ * import { useBase } from '@serenity-js/playwright-test'
394
+ *
395
+ * const { describe, it, test, beforeEach, afterEach } = useBase(playwrightBaseTest)
396
+ * .useFixtures<{ message: string }>({
397
+ * message: async ({ actor }, use) => {
398
+ * await use(`Hello, ${ actor.name }`);
399
+ * }
400
+ * })
401
+ * ```
402
+ *
403
+ * ## Using custom base test
404
+ *
405
+ * In cases where you need to use a non-default base test, for example when doing [UI component testing](https://playwright.dev/docs/test-components),
406
+ * you can create Serenity/JS test API functions around your preferred base test.
407
+ *
408
+ * ```tsx
409
+ * import { test as componentTest } from '@playwright/experimental-ct-react'
410
+ * import { Ensure, contain } from '@serenity-js/assertions'
411
+ * import { useBase } from '@serenity-js/playwright-test'
412
+ * import { Enter, PageElement, CssClasses } from '@serenity-js/web'
413
+ *
414
+ * import EmailInput from './EmailInput';
415
+ *
416
+ * const { it, describe } = useBase(componentTest).useFixtures<{ emailAddress: string }>({
417
+ * emailAddress: ({ actor }, use) => {
418
+ * use(`${ actor.name }@example.org`)
419
+ * }
420
+ * })
421
+ *
422
+ * describe('EmailInput', () => {
423
+ *
424
+ * it('allows valid email addresses', async ({ actor, mount, emailAddress }) => {
425
+ * const nativeComponent = await mount(<EmailInput/>);
426
+ *
427
+ * const component = PageElement.from(nativeComponent);
428
+ *
429
+ * await actor.attemptsTo(
430
+ * Enter.theValue(emailAddress).into(component),
431
+ * Ensure.that(CssClasses.of(component), contain('valid')),
432
+ * )
433
+ * })
434
+ * })
435
+ * ```
436
+ *
437
+ * ## Merging multiple base tests
438
+ *
439
+ * To merge fixtures from multiple files or modules, pass them to `useBase`.
440
+ *
441
+ * ```tsx
442
+ * import { test as componentTest } from '@playwright/experimental-ct-react'
443
+ * import { test as a11yTest } from 'my-a11y-test-utils';
444
+ * import { Ensure, contain } from '@serenity-js/assertions'
445
+ * import { useBase } from '@serenity-js/playwright-test'
446
+ * import { Enter, PageElement, CssClasses } from '@serenity-js/web'
447
+ *
448
+ * import EmailInput from './EmailInput';
449
+ *
450
+ * const { it, describe } = useBase(componentTest, a11yTest).useFixtures<{ emailAddress: string }>({
451
+ * emailAddress: ({ actor }, use) => {
452
+ * use(`${ actor.name }@example.org`)
453
+ * }
454
+ * })
455
+ *
456
+ * describe('EmailInput', () => {
457
+ *
458
+ * it('allows valid email addresses', async ({ actor, mount, emailAddress }) => {
459
+ * const nativeComponent = await mount(<EmailInput/>);
460
+ *
461
+ * const component = PageElement.from(nativeComponent);
462
+ *
463
+ * await actor.attemptsTo(
464
+ * Enter.theValue(emailAddress).into(component),
465
+ * Ensure.that(CssClasses.of(component), contain('valid')),
466
+ * )
467
+ * })
468
+ * })
469
+ * ```
470
+ *
471
+ * @param baseTests
472
+ */
473
+ export function useBase(...baseTests) {
474
+ return createTestApi(mergeTests(...baseTests))
475
+ .useFixtures(fixtures);
476
+ }
477
+ /**
478
+ * @private
479
+ * @param maybeDuration
480
+ */
481
+ function asDuration(maybeDuration) {
482
+ return maybeDuration instanceof Duration
483
+ ? maybeDuration
484
+ : Duration.ofMilliseconds(maybeDuration);
485
+ }
486
+ /**
487
+ * @private
488
+ * @param maybeCast
489
+ */
490
+ function asCast(maybeCast) {
491
+ return ensure('actors', maybeCast, property('prepare', isFunction()));
492
+ }
493
+ /**
494
+ * @private
495
+ * @param proxy
496
+ */
497
+ function asProxyConfig(proxy) {
498
+ const proxyServer = proxy.server.trim();
499
+ // Playwright defaults to http when proxy.server does not define the protocol
500
+ // See https://playwright.dev/docs/api/class-testoptions#test-options-proxy
501
+ const hasProtocol = /^[\dA-Za-z]+:\/\//.test(proxyServer);
502
+ const proxyUrl = hasProtocol
503
+ ? new URL(proxyServer)
504
+ : new URL(`http://${proxyServer}`);
505
+ const host = proxyUrl.hostname;
506
+ const port = proxyUrl.port
507
+ ? Number(proxyUrl.port)
508
+ : undefined;
509
+ const auth = proxy.username
510
+ ? { username: proxy.username, password: proxy.password || '' }
511
+ : undefined;
512
+ const bypass = proxy.bypass;
513
+ return {
514
+ protocol: proxyUrl.protocol,
515
+ host,
516
+ port,
517
+ auth,
518
+ bypass,
519
+ };
520
+ }
521
+ //# sourceMappingURL=test-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-api.js","sourceRoot":"","sources":["../../src/api/test-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type TestCase, type TestResult } from '@playwright/test/reporter';
2
+ import { type DomainEvent, SceneFinished } from '@serenity-js/core/events';
3
+ import { Path } from '@serenity-js/core/io';
4
+ import type { Outcome } from '@serenity-js/core/model';
5
+ export declare class EventFactory {
6
+ private requirementsHierarchy;
7
+ constructor(rootDirectory: Path);
8
+ createSceneStartEvents(test: TestCase, result: TestResult): DomainEvent[];
9
+ private createSceneSequenceEvents;
10
+ createSceneFinishedEvent(test: TestCase, result: TestResult, scenarioOutcome: Outcome): SceneFinished;
11
+ private uniqueTags;
12
+ private scenarioDetailsFrom;
13
+ private scenarioMetadataFrom;
14
+ private tagsFrom;
15
+ }
16
+ //# sourceMappingURL=EventFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventFactory.d.ts","sourceRoot":"","sources":["../../src/events/EventFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,EACH,KAAK,WAAW,EAChB,aAAa,EAOhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAkC,IAAI,EAAyB,MAAM,sBAAsB,CAAC;AACnG,OAAO,KAAK,EAAE,OAAO,EAAO,MAAM,yBAAyB,CAAC;AAa5D,qBAAa,YAAY;IACrB,OAAO,CAAC,qBAAqB,CAAwB;gBAEzC,aAAa,EAAE,IAAI;IAM/B,sBAAsB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE;IA4CzE,OAAO,CAAC,yBAAyB;IAgCjC,wBAAwB,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,GAAG,aAAa;IAerG,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,QAAQ;CAwBnB"}
@@ -0,0 +1,90 @@
1
+ import { Duration, Timestamp } from '@serenity-js/core';
2
+ import { SceneFinished, SceneParametersDetected, SceneSequenceDetected, SceneStarts, SceneTagged, SceneTemplateDetected, TestRunnerDetected } from '@serenity-js/core/events';
3
+ import { FileSystem, FileSystemLocation, Path, RequirementsHierarchy } from '@serenity-js/core/io';
4
+ import { Category, Description, Name, ProjectTag, ScenarioDetails, ScenarioParameters, Tags } from '@serenity-js/core/model';
5
+ import { PlaywrightSceneId } from './PlaywrightSceneId.js';
6
+ export class EventFactory {
7
+ requirementsHierarchy;
8
+ constructor(rootDirectory) {
9
+ this.requirementsHierarchy = new RequirementsHierarchy(new FileSystem(rootDirectory));
10
+ }
11
+ createSceneStartEvents(test, result) {
12
+ const sceneId = PlaywrightSceneId.from(test.parent.project()?.name, test, result);
13
+ const startTime = new Timestamp(result.startTime);
14
+ const project = test.parent.project();
15
+ const projectName = project?.name ?? '';
16
+ const scenarioDetails = this.scenarioDetailsFrom(test);
17
+ const allTags = this.tagsFrom(scenarioDetails, test.tags);
18
+ if (projectName) {
19
+ allTags.push(new ProjectTag(projectName));
20
+ }
21
+ const events = [];
22
+ if (test.retries > 0) {
23
+ events.push(...this.createSceneSequenceEvents(sceneId, startTime, scenarioDetails, test, result));
24
+ }
25
+ events.push(new SceneStarts(sceneId, scenarioDetails, startTime), new TestRunnerDetected(sceneId, new Name('Playwright'), startTime), ...allTags.map(tag => new SceneTagged(sceneId, tag, startTime)));
26
+ return events;
27
+ }
28
+ createSceneSequenceEvents(sceneId, startTime, scenarioDetails, test, result) {
29
+ const attempt = result.retry + 1;
30
+ const parameters = {
31
+ Retries: `Attempt #${attempt}`
32
+ };
33
+ return [
34
+ new SceneSequenceDetected(sceneId, scenarioDetails, startTime),
35
+ new SceneTemplateDetected(sceneId, new Description(''), startTime),
36
+ new SceneParametersDetected(sceneId, scenarioDetails, new ScenarioParameters(new Name(''), new Description(`Max retries: ${test.retries}`), parameters)),
37
+ ];
38
+ }
39
+ createSceneFinishedEvent(test, result, scenarioOutcome) {
40
+ const sceneId = PlaywrightSceneId.from(test.parent.project()?.name, test, result);
41
+ const duration = Duration.ofMilliseconds(result.duration);
42
+ const sceneEndTime = new Timestamp(result.startTime).plus(duration);
43
+ const scenarioDetails = this.scenarioDetailsFrom(test);
44
+ return new SceneFinished(sceneId, scenarioDetails, scenarioOutcome, sceneEndTime);
45
+ }
46
+ uniqueTags(...tags) {
47
+ const uniqueTags = {};
48
+ for (const tag of tags) {
49
+ const { name, type } = tag.toJSON();
50
+ const key = `${name} ${type}`;
51
+ uniqueTags[key] = tag;
52
+ }
53
+ return Object.values(uniqueTags);
54
+ }
55
+ scenarioDetailsFrom(test) {
56
+ const { featureName, name } = this.scenarioMetadataFrom(test);
57
+ const { file, line, column } = test.location;
58
+ const nameWithoutTags = Tags.stripFrom(name);
59
+ const repetitionSuffix = test.repeatEachIndex
60
+ ? ` - Repetition ${test.repeatEachIndex}`
61
+ : '';
62
+ const scenarioName = `${nameWithoutTags}${repetitionSuffix}`;
63
+ return new ScenarioDetails(new Name(scenarioName), new Category(Tags.stripFrom(featureName)), new FileSystemLocation(Path.from(file), line, column));
64
+ }
65
+ scenarioMetadataFrom(test) {
66
+ const [root_, browserName_, fileName, describeOrItBlockTitle, ...nestedTitles] = test.titlePath();
67
+ const scenarioName = nestedTitles.join(' ').trim();
68
+ const name = scenarioName || describeOrItBlockTitle;
69
+ const featureName = scenarioName ? describeOrItBlockTitle : fileName;
70
+ return {
71
+ featureName,
72
+ name,
73
+ };
74
+ }
75
+ tagsFrom(scenarioDetails, extraTagValues) {
76
+ const tagsFromRequirementsHierarchy = this.requirementsHierarchy.requirementTagsFor(scenarioDetails.location.path, scenarioDetails.category.value);
77
+ const tagsFromTitle = Tags.from([
78
+ scenarioDetails.category.value,
79
+ scenarioDetails.name.value,
80
+ ].join(' '));
81
+ const extraTags = extraTagValues
82
+ .filter(Boolean)
83
+ .flatMap(tagValue => Tags.from(tagValue));
84
+ return [
85
+ ...tagsFromRequirementsHierarchy,
86
+ ...this.uniqueTags(...tagsFromTitle, ...extraTags)
87
+ ];
88
+ }
89
+ }
90
+ //# sourceMappingURL=EventFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventFactory.js","sourceRoot":"","sources":["../../src/events/EventFactory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FullProject, TestCase, TestResult } from '@playwright/test/reporter';
2
+ import { CorrelationId } from '@serenity-js/core/model';
3
+ export declare class PlaywrightSceneId extends CorrelationId {
4
+ static fromJSON(v: string): CorrelationId;
5
+ static from(projectName: FullProject['name'], test: Pick<TestCase, 'id' | 'repeatEachIndex'>, result: Pick<TestResult, 'retry'>): CorrelationId;
6
+ }
7
+ //# sourceMappingURL=PlaywrightSceneId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlaywrightSceneId.d.ts","sourceRoot":"","sources":["../../src/events/PlaywrightSceneId.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,qBAAa,iBAAkB,SAAQ,aAAa;WAEhC,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa;IAIlD,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,aAAa;CAQlJ"}