@serenity-js/playwright-test 3.31.17 → 3.32.1
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 +37 -0
- package/README.md +6 -5
- package/lib/api/PlaywrightTestConfig.d.ts +7 -6
- package/lib/api/PlaywrightTestConfig.d.ts.map +1 -1
- package/lib/api/WorkerEventStreamReader.d.ts +13 -0
- package/lib/api/WorkerEventStreamReader.d.ts.map +1 -0
- package/lib/api/WorkerEventStreamReader.js +58 -0
- package/lib/api/WorkerEventStreamReader.js.map +1 -0
- package/lib/api/WorkerEventStreamWriter.d.ts +24 -0
- package/lib/api/WorkerEventStreamWriter.d.ts.map +1 -0
- package/lib/api/WorkerEventStreamWriter.js +86 -0
- package/lib/api/WorkerEventStreamWriter.js.map +1 -0
- package/lib/api/index.d.ts +1 -2
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +1 -2
- package/lib/api/index.js.map +1 -1
- package/lib/api/serenity-fixtures.d.ts +377 -0
- package/lib/api/serenity-fixtures.d.ts.map +1 -0
- package/lib/api/{SerenityOptions.js → serenity-fixtures.js} +1 -1
- package/lib/api/serenity-fixtures.js.map +1 -0
- package/lib/api/test-api.d.ts +27 -15
- package/lib/api/test-api.d.ts.map +1 -1
- package/lib/api/test-api.js +126 -104
- package/lib/api/test-api.js.map +1 -1
- package/lib/events/EventFactory.d.ts +16 -0
- package/lib/events/EventFactory.d.ts.map +1 -0
- package/lib/events/EventFactory.js +94 -0
- package/lib/events/EventFactory.js.map +1 -0
- package/lib/events/PlaywrightSceneId.d.ts +7 -0
- package/lib/events/PlaywrightSceneId.d.ts.map +1 -0
- package/lib/events/PlaywrightSceneId.js +19 -0
- package/lib/events/PlaywrightSceneId.js.map +1 -0
- package/lib/events/index.d.ts +3 -0
- package/lib/events/index.d.ts.map +1 -0
- package/lib/events/index.js +19 -0
- package/lib/events/index.js.map +1 -0
- package/lib/reporter/PlaywrightErrorParser.d.ts +7 -0
- package/lib/reporter/PlaywrightErrorParser.d.ts.map +1 -0
- package/lib/reporter/PlaywrightErrorParser.js +28 -0
- package/lib/reporter/PlaywrightErrorParser.js.map +1 -0
- package/lib/reporter/PlaywrightEventBuffer.d.ts +25 -0
- package/lib/reporter/PlaywrightEventBuffer.d.ts.map +1 -0
- package/lib/reporter/PlaywrightEventBuffer.js +147 -0
- package/lib/reporter/PlaywrightEventBuffer.js.map +1 -0
- package/lib/reporter/PlaywrightTestSceneIdFactory.d.ts +8 -0
- package/lib/reporter/PlaywrightTestSceneIdFactory.d.ts.map +1 -0
- package/lib/reporter/PlaywrightTestSceneIdFactory.js +15 -0
- package/lib/reporter/PlaywrightTestSceneIdFactory.js.map +1 -0
- package/lib/reporter/SerenityReporterForPlaywrightTest.d.ts +15 -22
- package/lib/reporter/SerenityReporterForPlaywrightTest.d.ts.map +1 -1
- package/lib/reporter/SerenityReporterForPlaywrightTest.js +62 -163
- package/lib/reporter/SerenityReporterForPlaywrightTest.js.map +1 -1
- package/lib/reporter/index.d.ts +0 -2
- package/lib/reporter/index.d.ts.map +1 -1
- package/lib/reporter/index.js +0 -2
- package/lib/reporter/index.js.map +1 -1
- package/package.json +9 -9
- package/src/api/PlaywrightTestConfig.ts +7 -6
- package/src/api/WorkerEventStreamReader.ts +27 -0
- package/src/api/WorkerEventStreamWriter.ts +117 -0
- package/src/api/index.ts +1 -2
- package/src/api/serenity-fixtures.ts +392 -0
- package/src/api/test-api.ts +187 -99
- package/src/events/EventFactory.ts +204 -0
- package/src/events/PlaywrightSceneId.ts +20 -0
- package/src/events/index.ts +2 -0
- package/src/reporter/PlaywrightErrorParser.ts +35 -0
- package/src/reporter/PlaywrightEventBuffer.ts +251 -0
- package/src/reporter/PlaywrightTestSceneIdFactory.ts +14 -0
- package/src/reporter/SerenityReporterForPlaywrightTest.ts +89 -250
- package/src/reporter/index.ts +0 -2
- package/lib/api/SerenityFixtures.d.ts +0 -130
- package/lib/api/SerenityFixtures.d.ts.map +0 -1
- package/lib/api/SerenityFixtures.js +0 -3
- package/lib/api/SerenityFixtures.js.map +0 -1
- package/lib/api/SerenityOptions.d.ts +0 -271
- package/lib/api/SerenityOptions.d.ts.map +0 -1
- package/lib/api/SerenityOptions.js.map +0 -1
- package/lib/reporter/DomainEventBuffer.d.ts +0 -11
- package/lib/reporter/DomainEventBuffer.d.ts.map +0 -1
- package/lib/reporter/DomainEventBuffer.js +0 -24
- package/lib/reporter/DomainEventBuffer.js.map +0 -1
- package/lib/reporter/PlaywrightAttachments.d.ts +0 -2
- package/lib/reporter/PlaywrightAttachments.d.ts.map +0 -1
- package/lib/reporter/PlaywrightAttachments.js +0 -5
- package/lib/reporter/PlaywrightAttachments.js.map +0 -1
- package/src/api/SerenityFixtures.ts +0 -132
- package/src/api/SerenityOptions.ts +0 -277
- package/src/reporter/DomainEventBuffer.ts +0 -28
- package/src/reporter/PlaywrightAttachments.ts +0 -1
package/src/api/test-api.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
1
5
|
import type {
|
|
2
6
|
Expect,
|
|
3
7
|
Fixtures,
|
|
@@ -7,52 +11,42 @@ import type {
|
|
|
7
11
|
PlaywrightWorkerOptions,
|
|
8
12
|
TestInfo,
|
|
9
13
|
TestType,
|
|
14
|
+
WorkerInfo,
|
|
10
15
|
} from '@playwright/test';
|
|
11
16
|
import { test as playwrightBaseTest } from '@playwright/test';
|
|
12
|
-
import {
|
|
17
|
+
import type { DiffFormatter } from '@serenity-js/core';
|
|
18
|
+
import { AnsiDiffFormatter, Cast, Clock, Duration, Serenity, TakeNotes } from '@serenity-js/core';
|
|
13
19
|
import { SceneFinishes, SceneTagged } from '@serenity-js/core/lib/events';
|
|
14
20
|
import { BrowserTag, PlatformTag } from '@serenity-js/core/lib/model';
|
|
15
21
|
import { BrowseTheWebWithPlaywright, SerenitySelectorEngines } from '@serenity-js/playwright';
|
|
16
22
|
import { CallAnApi } from '@serenity-js/rest';
|
|
17
23
|
import { Photographer, TakePhotosOfFailures } from '@serenity-js/web';
|
|
18
|
-
import * as os from 'os';
|
|
19
|
-
import type { JSONValue } from 'tiny-types';
|
|
20
24
|
import { ensure, isFunction, property } from 'tiny-types';
|
|
21
25
|
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
SERENITY_JS_DOMAIN_EVENTS_ATTACHMENT_CONTENT_TYPE
|
|
26
|
-
} from '../reporter';
|
|
26
|
+
import { PlaywrightSceneId } from '../events';
|
|
27
|
+
import { PlaywrightStepReporter, } from '../reporter';
|
|
28
|
+
import { PlaywrightTestSceneIdFactory } from '../reporter/PlaywrightTestSceneIdFactory';
|
|
27
29
|
import { PerformActivitiesAsPlaywrightSteps } from './PerformActivitiesAsPlaywrightSteps';
|
|
28
|
-
import type { SerenityFixtures } from './
|
|
29
|
-
import
|
|
30
|
+
import type { SerenityFixtures, SerenityWorkerFixtures } from './serenity-fixtures';
|
|
31
|
+
import { WorkerEventStreamWriter } from './WorkerEventStreamWriter';
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
interface SerenityInternalFixtures {
|
|
34
|
+
configureScenarioInternal: void;
|
|
35
|
+
}
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
TakeNotes.usingAnEmptyNotepad(),
|
|
40
|
-
CallAnApi.using({
|
|
41
|
-
baseURL: baseURL,
|
|
42
|
-
headers: extraHTTPHeaders,
|
|
43
|
-
proxy: proxy && proxy?.server
|
|
44
|
-
? asProxyConfig(proxy)
|
|
45
|
-
: undefined,
|
|
46
|
-
}),
|
|
47
|
-
)));
|
|
48
|
-
},
|
|
49
|
-
{ option: true },
|
|
50
|
-
],
|
|
37
|
+
interface SerenityInternalWorkerFixtures {
|
|
38
|
+
configureWorkerInternal: void;
|
|
39
|
+
sceneIdFactoryInternal: PlaywrightTestSceneIdFactory;
|
|
40
|
+
diffFormatterInternal: DiffFormatter;
|
|
41
|
+
eventStreamWriterInternal: WorkerEventStreamWriter;
|
|
42
|
+
}
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
export const fixtures: Fixtures<SerenityFixtures & SerenityInternalFixtures, SerenityWorkerFixtures & SerenityInternalWorkerFixtures, PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions> = {
|
|
45
|
+
|
|
46
|
+
extraContextOptions: [
|
|
47
|
+
{ defaultNavigationWaitUntil: 'load' },
|
|
48
|
+
{ option: true }
|
|
49
|
+
],
|
|
56
50
|
|
|
57
51
|
defaultActorName: [
|
|
58
52
|
'Serena',
|
|
@@ -70,14 +64,30 @@ export const fixtures: Fixtures<Omit<SerenityOptions, 'actors'> & SerenityFixtur
|
|
|
70
64
|
],
|
|
71
65
|
|
|
72
66
|
crew: [
|
|
73
|
-
[
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
[ Photographer.whoWill(TakePhotosOfFailures) ],
|
|
68
|
+
{ option: true },
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
actors: [
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
73
|
+
async ({ extraContextOptions, baseURL, extraHTTPHeaders, page, proxy }, use): Promise<void> => {
|
|
74
|
+
await use(Cast.where(actor => actor.whoCan(
|
|
75
|
+
BrowseTheWebWithPlaywright.usingPage(page, extraContextOptions),
|
|
76
|
+
TakeNotes.usingAnEmptyNotepad(),
|
|
77
|
+
CallAnApi.using({
|
|
78
|
+
baseURL: baseURL,
|
|
79
|
+
headers: extraHTTPHeaders,
|
|
80
|
+
proxy: proxy && proxy?.server
|
|
81
|
+
? asProxyConfig(proxy)
|
|
82
|
+
: undefined,
|
|
83
|
+
}),
|
|
84
|
+
)));
|
|
85
|
+
},
|
|
76
86
|
{ option: true },
|
|
77
87
|
],
|
|
78
88
|
|
|
79
|
-
// eslint-disable-next-line no-empty-pattern
|
|
80
|
-
platform: async ({}, use) => {
|
|
89
|
+
// eslint-disable-next-line no-empty-pattern,@typescript-eslint/explicit-module-boundary-types
|
|
90
|
+
platform: [ async ({}, use) => {
|
|
81
91
|
const platform = os.platform();
|
|
82
92
|
|
|
83
93
|
// https://nodejs.org/api/process.html#process_process_platform
|
|
@@ -86,75 +96,148 @@ export const fixtures: Fixtures<Omit<SerenityOptions, 'actors'> & SerenityFixtur
|
|
|
86
96
|
: (platform === 'darwin' ? 'macOS' : 'Linux');
|
|
87
97
|
|
|
88
98
|
await use({ name, version: os.release() });
|
|
89
|
-
},
|
|
99
|
+
}, { scope: 'worker' } ],
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
diffFormatterInternal: [
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,no-empty-pattern
|
|
103
|
+
async ({}, use) => {
|
|
104
|
+
const diffFormatter = new AnsiDiffFormatter();
|
|
105
|
+
await use(diffFormatter);
|
|
106
|
+
},
|
|
107
|
+
{ scope: 'worker', box: true }
|
|
108
|
+
],
|
|
92
109
|
|
|
93
|
-
|
|
110
|
+
sceneIdFactoryInternal: [
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,no-empty-pattern
|
|
112
|
+
async ({ }, use) => {
|
|
113
|
+
await use(new PlaywrightTestSceneIdFactory());
|
|
114
|
+
},
|
|
115
|
+
{ scope: 'worker', box: true },
|
|
116
|
+
],
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
domainEventBuffer,
|
|
102
|
-
new PlaywrightStepReporter(info),
|
|
103
|
-
],
|
|
104
|
-
});
|
|
118
|
+
serenity: [
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
120
|
+
async ({ playwright, sceneIdFactoryInternal }, use, workerInfo) => {
|
|
121
|
+
const clock = new Clock();
|
|
122
|
+
const cwd = process.cwd();
|
|
123
|
+
const serenity = new Serenity(clock, cwd, sceneIdFactoryInternal);
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
new PlatformTag(platform.name, platform.version),
|
|
109
|
-
serenityInstance.currentTime(),
|
|
110
|
-
));
|
|
125
|
+
const serenitySelectorEngines = new SerenitySelectorEngines();
|
|
126
|
+
await serenitySelectorEngines.ensureRegisteredWith(playwright.selectors);
|
|
111
127
|
|
|
112
|
-
|
|
128
|
+
await use(serenity);
|
|
129
|
+
},
|
|
130
|
+
{ scope: 'worker', box: true }
|
|
131
|
+
],
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
eventStreamWriterInternal: [
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,no-empty-pattern
|
|
135
|
+
async ({ }, use, workerInfo) => {
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
serialisedEvents.push({
|
|
118
|
-
type: event.constructor.name,
|
|
119
|
-
value: event.toJSON(),
|
|
120
|
-
});
|
|
137
|
+
const serenityOutputDirectory = path.join(workerInfo.project.outputDir, 'serenity');
|
|
121
138
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
const eventStreamWriter = new WorkerEventStreamWriter(
|
|
140
|
+
serenityOutputDirectory,
|
|
141
|
+
workerInfo,
|
|
142
|
+
);
|
|
126
143
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
},
|
|
144
|
+
await use(eventStreamWriter);
|
|
145
|
+
},
|
|
146
|
+
{ scope: 'worker', box: true },
|
|
147
|
+
],
|
|
132
148
|
|
|
133
|
-
|
|
149
|
+
configureWorkerInternal: [
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
151
|
+
async ({ diffFormatterInternal, eventStreamWriterInternal, sceneIdFactoryInternal, serenity, browser }, use, info: WorkerInfo) => {
|
|
152
|
+
|
|
153
|
+
serenity.configure({
|
|
154
|
+
actors: Cast.where(actor => actor.whoCan(
|
|
155
|
+
BrowseTheWebWithPlaywright.using(browser),
|
|
156
|
+
TakeNotes.usingAnEmptyNotepad(),
|
|
157
|
+
// todo: consider making `axios` a fixture and injecting an ability to CallAnApi
|
|
158
|
+
)),
|
|
159
|
+
crew: [
|
|
160
|
+
eventStreamWriterInternal,
|
|
161
|
+
],
|
|
162
|
+
diffFormatter: diffFormatterInternal,
|
|
163
|
+
});
|
|
134
164
|
|
|
135
|
-
|
|
165
|
+
sceneIdFactoryInternal.setTestId(`worker-${ info.workerIndex }`);
|
|
166
|
+
const workerBeforeAllSceneId = serenity.assignNewSceneId();
|
|
136
167
|
|
|
137
|
-
|
|
168
|
+
await use(void 0);
|
|
169
|
+
|
|
170
|
+
await eventStreamWriterInternal.persistAll(workerBeforeAllSceneId);
|
|
171
|
+
},
|
|
172
|
+
{ scope: 'worker', auto: true, box: true },
|
|
173
|
+
],
|
|
138
174
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
175
|
+
configureScenarioInternal: [
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
177
|
+
async ({ actors, browser, browserName, crew, cueTimeout, diffFormatterInternal, eventStreamWriterInternal, interactionTimeout, platform, sceneIdFactoryInternal, serenity }, use, info: TestInfo) => {
|
|
178
|
+
|
|
179
|
+
serenity.configure({
|
|
180
|
+
actors: asCast(actors),
|
|
181
|
+
diffFormatter: diffFormatterInternal,
|
|
182
|
+
cueTimeout: asDuration(cueTimeout),
|
|
183
|
+
interactionTimeout: asDuration(interactionTimeout),
|
|
184
|
+
crew: [
|
|
185
|
+
...crew,
|
|
186
|
+
new PlaywrightStepReporter(info),
|
|
187
|
+
],
|
|
188
|
+
});
|
|
143
189
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
190
|
+
const playwrightSceneId = PlaywrightSceneId.from(
|
|
191
|
+
info.project.name,
|
|
192
|
+
{ id: info.testId, repeatEachIndex: info.repeatEachIndex },
|
|
193
|
+
{ retry: info.retry }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
sceneIdFactoryInternal.setTestId(playwrightSceneId.value);
|
|
197
|
+
const sceneId = serenity.assignNewSceneId();
|
|
198
|
+
|
|
199
|
+
serenity.announce(
|
|
200
|
+
new SceneTagged(
|
|
201
|
+
sceneId,
|
|
202
|
+
new PlatformTag(platform.name, platform.version),
|
|
203
|
+
serenity.currentTime(),
|
|
204
|
+
),
|
|
205
|
+
new SceneTagged(
|
|
206
|
+
sceneId,
|
|
207
|
+
new BrowserTag(browserName, browser.version()),
|
|
208
|
+
serenity.currentTime(),
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
await use(void 0);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
serenity.announce(
|
|
216
|
+
new SceneFinishes(sceneId, serenity.currentTime()),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await serenity.waitForNextCue();
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
await eventStreamWriterInternal.persist(playwrightSceneId.value);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{ auto: true, box: true, }
|
|
226
|
+
],
|
|
149
227
|
|
|
150
|
-
|
|
228
|
+
actorCalled: [
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
230
|
+
async ({ serenity }, use) => {
|
|
151
231
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
232
|
+
const actorCalled = (name: string) => {
|
|
233
|
+
const actor = serenity.theActorCalled(name);
|
|
234
|
+
return actor.whoCan(new PerformActivitiesAsPlaywrightSteps(actor, serenity, it));
|
|
235
|
+
};
|
|
155
236
|
|
|
156
|
-
|
|
157
|
-
|
|
237
|
+
await use(actorCalled);
|
|
238
|
+
},
|
|
239
|
+
{ scope: 'worker' },
|
|
240
|
+
],
|
|
158
241
|
|
|
159
242
|
actor: async ({ actorCalled, defaultActorName }, use) => {
|
|
160
243
|
await use(actorCalled(defaultActorName));
|
|
@@ -191,14 +274,17 @@ export type TestApi<TestArgs extends Record<string, any>, WorkerArgs extends Rec
|
|
|
191
274
|
*
|
|
192
275
|
* Shorthand for [`useBase`](https://serenity-js.org/api/playwright-test/function/useBase/)
|
|
193
276
|
*/
|
|
194
|
-
useFixtures: <T extends Record<string, any>, W extends Record<string, any> = object>(
|
|
277
|
+
useFixtures: <T extends Record<string, any>, W extends Record<string, any> = object>(
|
|
278
|
+
customFixtures: Fixtures<T, W, TestArgs, WorkerArgs>
|
|
279
|
+
) => TestApi<TestArgs & T, WorkerArgs & W>,
|
|
280
|
+
|
|
195
281
|
it: TestType<TestArgs, WorkerArgs>,
|
|
196
282
|
test: TestType<TestArgs, WorkerArgs>,
|
|
197
283
|
}
|
|
198
284
|
|
|
199
|
-
function createTestApi<
|
|
285
|
+
function createTestApi<BaseTestFixtures extends (PlaywrightTestArgs & PlaywrightTestOptions), BaseWorkerFixtures extends (PlaywrightWorkerArgs & PlaywrightWorkerOptions)>(baseTest: TestType<BaseTestFixtures, BaseWorkerFixtures>): TestApi<BaseTestFixtures, BaseWorkerFixtures> {
|
|
200
286
|
return {
|
|
201
|
-
useFixtures<T extends Record<string, any>, W extends Record<string, any> = object>(customFixtures: Fixtures<T, W,
|
|
287
|
+
useFixtures<T extends Record<string, any>, W extends Record<string, any> = object>(customFixtures: Fixtures<T, W, BaseTestFixtures, BaseWorkerFixtures>): TestApi<BaseTestFixtures & T, BaseWorkerFixtures & W> {
|
|
202
288
|
return createTestApi(baseTest.extend(customFixtures));
|
|
203
289
|
},
|
|
204
290
|
beforeAll: baseTest.beforeAll,
|
|
@@ -212,7 +298,7 @@ function createTestApi<TestArgs extends Record<string, any>, WorkerArgs extends
|
|
|
212
298
|
};
|
|
213
299
|
}
|
|
214
300
|
|
|
215
|
-
const api = createTestApi(playwrightBaseTest).useFixtures(fixtures);
|
|
301
|
+
const api = createTestApi(playwrightBaseTest).useFixtures<SerenityFixtures, SerenityWorkerFixtures>(fixtures);
|
|
216
302
|
|
|
217
303
|
/**
|
|
218
304
|
* Declares a single test scenario.
|
|
@@ -478,11 +564,13 @@ export const useFixtures = api.useFixtures;
|
|
|
478
564
|
*
|
|
479
565
|
* @param baseTest
|
|
480
566
|
*/
|
|
481
|
-
export function useBase<
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
567
|
+
export function useBase<
|
|
568
|
+
BaseTestFixtures extends (PlaywrightTestArgs & PlaywrightTestOptions),
|
|
569
|
+
BaseWorkerFixtures extends (PlaywrightWorkerArgs & PlaywrightWorkerOptions)
|
|
570
|
+
> (baseTest: TestType<BaseTestFixtures, BaseWorkerFixtures>): TestApi<BaseTestFixtures & SerenityFixtures, BaseWorkerFixtures & SerenityWorkerFixtures> {
|
|
571
|
+
return createTestApi<BaseTestFixtures, BaseWorkerFixtures>(baseTest).useFixtures(
|
|
572
|
+
fixtures as Fixtures<SerenityFixtures, SerenityWorkerFixtures, BaseTestFixtures, BaseWorkerFixtures>
|
|
573
|
+
);
|
|
486
574
|
}
|
|
487
575
|
|
|
488
576
|
/**
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { FullProject } from '@playwright/test/reporter';
|
|
2
|
+
import { type TestCase, type TestResult } from '@playwright/test/reporter';
|
|
3
|
+
import { Duration, Timestamp } from '@serenity-js/core';
|
|
4
|
+
import {
|
|
5
|
+
type DomainEvent,
|
|
6
|
+
SceneFinished,
|
|
7
|
+
SceneParametersDetected,
|
|
8
|
+
SceneSequenceDetected,
|
|
9
|
+
SceneStarts,
|
|
10
|
+
SceneTagged,
|
|
11
|
+
SceneTemplateDetected,
|
|
12
|
+
TestRunnerDetected
|
|
13
|
+
} from '@serenity-js/core/lib/events';
|
|
14
|
+
import { FileSystem, FileSystemLocation, Path, RequirementsHierarchy } from '@serenity-js/core/lib/io';
|
|
15
|
+
import type { Outcome, Tag } from '@serenity-js/core/lib/model';
|
|
16
|
+
import {
|
|
17
|
+
Category,
|
|
18
|
+
Description,
|
|
19
|
+
Name,
|
|
20
|
+
ProjectTag,
|
|
21
|
+
ScenarioDetails,
|
|
22
|
+
ScenarioParameters,
|
|
23
|
+
Tags
|
|
24
|
+
} from '@serenity-js/core/lib/model';
|
|
25
|
+
|
|
26
|
+
import { PlaywrightSceneId } from './PlaywrightSceneId';
|
|
27
|
+
|
|
28
|
+
export class EventFactory {
|
|
29
|
+
private requirementsHierarchy: RequirementsHierarchy;
|
|
30
|
+
|
|
31
|
+
constructor(rootDirectory: Path) {
|
|
32
|
+
this.requirementsHierarchy = new RequirementsHierarchy(
|
|
33
|
+
new FileSystem(rootDirectory),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createSceneStartEvents(test: TestCase, result: TestResult): DomainEvent[] {
|
|
38
|
+
const sceneId = PlaywrightSceneId.from(test.parent.project()?.name, test, result);
|
|
39
|
+
const startTime = new Timestamp(result.startTime);
|
|
40
|
+
|
|
41
|
+
const project: FullProject | undefined = test.parent.project();
|
|
42
|
+
const projectName = project?.name ?? '';
|
|
43
|
+
|
|
44
|
+
const scenarioDetails = this.scenarioDetailsFrom(test);
|
|
45
|
+
|
|
46
|
+
const allTags = this.tagsFrom(
|
|
47
|
+
scenarioDetails,
|
|
48
|
+
test.tags,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (projectName) {
|
|
52
|
+
allTags.push(new ProjectTag(projectName));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const events: DomainEvent[] = [];
|
|
56
|
+
|
|
57
|
+
if (test.retries > 0) {
|
|
58
|
+
events.push(
|
|
59
|
+
...this.createSceneSequenceEvents(
|
|
60
|
+
sceneId,
|
|
61
|
+
startTime,
|
|
62
|
+
scenarioDetails,
|
|
63
|
+
test,
|
|
64
|
+
result
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
events.push(
|
|
70
|
+
new SceneStarts(sceneId,
|
|
71
|
+
scenarioDetails,
|
|
72
|
+
startTime
|
|
73
|
+
),
|
|
74
|
+
new TestRunnerDetected(sceneId, new Name('Playwright'), startTime),
|
|
75
|
+
...allTags.map(tag => new SceneTagged(sceneId, tag, startTime))
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return events;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private createSceneSequenceEvents(
|
|
82
|
+
sceneId: PlaywrightSceneId,
|
|
83
|
+
startTime: Timestamp,
|
|
84
|
+
scenarioDetails: ScenarioDetails,
|
|
85
|
+
test: TestCase,
|
|
86
|
+
result: TestResult
|
|
87
|
+
): DomainEvent[] {
|
|
88
|
+
|
|
89
|
+
const attempt = result.retry + 1;
|
|
90
|
+
const parameters = {
|
|
91
|
+
Retries: `Attempt #${ attempt }`
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return [
|
|
95
|
+
new SceneSequenceDetected(sceneId, scenarioDetails, startTime),
|
|
96
|
+
new SceneTemplateDetected(
|
|
97
|
+
sceneId,
|
|
98
|
+
new Description(''),
|
|
99
|
+
startTime,
|
|
100
|
+
),
|
|
101
|
+
new SceneParametersDetected(
|
|
102
|
+
sceneId,
|
|
103
|
+
scenarioDetails,
|
|
104
|
+
new ScenarioParameters(
|
|
105
|
+
new Name(''),
|
|
106
|
+
new Description(`Max retries: ${ test.retries }`),
|
|
107
|
+
parameters,
|
|
108
|
+
)
|
|
109
|
+
),
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
createSceneFinishedEvent(test: TestCase, result: TestResult, scenarioOutcome: Outcome): SceneFinished {
|
|
114
|
+
const sceneId = PlaywrightSceneId.from(test.parent.project()?.name, test, result);
|
|
115
|
+
const duration = Duration.ofMilliseconds(result.duration);
|
|
116
|
+
const sceneEndTime = new Timestamp(result.startTime).plus(duration);
|
|
117
|
+
|
|
118
|
+
const scenarioDetails = this.scenarioDetailsFrom(test);
|
|
119
|
+
|
|
120
|
+
return new SceneFinished(
|
|
121
|
+
sceneId,
|
|
122
|
+
scenarioDetails,
|
|
123
|
+
scenarioOutcome,
|
|
124
|
+
sceneEndTime,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private uniqueTags(...tags: Tag[]) {
|
|
129
|
+
const uniqueTags: Record<string, Tag> = { };
|
|
130
|
+
|
|
131
|
+
for (const tag of tags) {
|
|
132
|
+
const { name, type } = tag.toJSON();
|
|
133
|
+
const key = `${ name } ${ type }`;
|
|
134
|
+
uniqueTags[key] = tag;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return Object.values(uniqueTags);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private scenarioDetailsFrom(test: Pick<TestCase, 'titlePath' | 'location' | 'parent' | 'repeatEachIndex'>): ScenarioDetails {
|
|
141
|
+
|
|
142
|
+
const { featureName, name } = this.scenarioMetadataFrom(test);
|
|
143
|
+
const { file, line, column } = test.location;
|
|
144
|
+
|
|
145
|
+
const nameWithoutTags = Tags.stripFrom(name);
|
|
146
|
+
|
|
147
|
+
const repetitionSuffix = test.repeatEachIndex
|
|
148
|
+
? ` - Repetition ${ test.repeatEachIndex }`
|
|
149
|
+
: '';
|
|
150
|
+
|
|
151
|
+
const scenarioName = `${ nameWithoutTags }${ repetitionSuffix }`;
|
|
152
|
+
|
|
153
|
+
return new ScenarioDetails(
|
|
154
|
+
new Name(scenarioName),
|
|
155
|
+
new Category(Tags.stripFrom(featureName)),
|
|
156
|
+
new FileSystemLocation(Path.from(file), line, column),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private scenarioMetadataFrom(test: Pick<TestCase, 'titlePath' | 'location'>): { featureName: string, name: string } {
|
|
161
|
+
const [
|
|
162
|
+
root_,
|
|
163
|
+
browserName_,
|
|
164
|
+
fileName,
|
|
165
|
+
describeOrItBlockTitle,
|
|
166
|
+
...nestedTitles
|
|
167
|
+
] = test.titlePath();
|
|
168
|
+
|
|
169
|
+
const scenarioName = nestedTitles.join(' ').trim();
|
|
170
|
+
|
|
171
|
+
const name = scenarioName || describeOrItBlockTitle;
|
|
172
|
+
const featureName = scenarioName ? describeOrItBlockTitle : fileName;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
featureName,
|
|
176
|
+
name,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private tagsFrom(scenarioDetails: ScenarioDetails, extraTagValues: string[]): Tag[] {
|
|
181
|
+
|
|
182
|
+
const tagsFromRequirementsHierarchy = this.requirementsHierarchy.requirementTagsFor(
|
|
183
|
+
scenarioDetails.location.path,
|
|
184
|
+
scenarioDetails.category.value,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const tagsFromTitle = Tags.from([
|
|
188
|
+
scenarioDetails.category.value,
|
|
189
|
+
scenarioDetails.name.value,
|
|
190
|
+
].join(' '));
|
|
191
|
+
|
|
192
|
+
const extraTags = extraTagValues
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.flatMap(tagValue => Tags.from(tagValue));
|
|
195
|
+
|
|
196
|
+
return [
|
|
197
|
+
...tagsFromRequirementsHierarchy,
|
|
198
|
+
...this.uniqueTags(
|
|
199
|
+
...tagsFromTitle,
|
|
200
|
+
...extraTags,
|
|
201
|
+
)
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import type { FullProject, TestCase, TestResult } from '@playwright/test/reporter';
|
|
4
|
+
import { CorrelationId } from '@serenity-js/core/lib/model';
|
|
5
|
+
|
|
6
|
+
export class PlaywrightSceneId extends CorrelationId {
|
|
7
|
+
|
|
8
|
+
static override fromJSON(v: string): CorrelationId {
|
|
9
|
+
return new PlaywrightSceneId(v);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static from(projectName: FullProject['name'], test: Pick<TestCase, 'id' | 'repeatEachIndex'>, result: Pick<TestResult, 'retry'>): CorrelationId {
|
|
13
|
+
const projectId = createHash('sha1')
|
|
14
|
+
.update(projectName)
|
|
15
|
+
.digest('hex')
|
|
16
|
+
.slice(0, 10);
|
|
17
|
+
|
|
18
|
+
return new PlaywrightSceneId(`${ test.id }-${ projectId }-${ test.repeatEachIndex }-${ result.retry }`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TestError } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
export class PlaywrightErrorParser {
|
|
4
|
+
private static ascii = new RegExp(
|
|
5
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', // eslint-disable-line no-control-regex
|
|
6
|
+
'g',
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
public errorFrom(testError: TestError): Error {
|
|
10
|
+
const message = testError.message && PlaywrightErrorParser.stripAsciiFrom(testError.message);
|
|
11
|
+
|
|
12
|
+
let stack = testError.stack && PlaywrightErrorParser.stripAsciiFrom(testError.stack);
|
|
13
|
+
|
|
14
|
+
// TODO: Do I need to process it?
|
|
15
|
+
// const value = testError.value;
|
|
16
|
+
|
|
17
|
+
const prologue = `Error: ${ message }`;
|
|
18
|
+
if (stack && message && stack.startsWith(prologue)) {
|
|
19
|
+
stack = stack.slice(prologue.length);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (testError.cause) {
|
|
23
|
+
stack += `\nCaused by: ${ this.errorFrom(testError.cause).stack }`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.stack = stack;
|
|
28
|
+
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private static stripAsciiFrom(text: string): string {
|
|
33
|
+
return text.replace(this.ascii, '');
|
|
34
|
+
}
|
|
35
|
+
}
|