@qavajs/playwright-runner-adapter 2.3.0 → 2.3.2
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 +16 -0
- package/README.md +347 -39
- package/adapter/PlaywrightWorld.d.ts +9 -16
- package/adapter/PlaywrightWorld.js +11 -13
- package/adapter/PlaywrightWorld.js.map +1 -1
- package/adapter/cucumberAdapter.d.ts +2 -4
- package/adapter/cucumberAdapter.js +16 -31
- package/adapter/cucumberAdapter.js.map +1 -1
- package/adapter/js/PlaywrightWorld.d.ts +9 -16
- package/adapter/js/PlaywrightWorld.js +11 -13
- package/adapter/js/cucumberAdapter.d.ts +2 -4
- package/adapter/js/cucumberAdapter.js +16 -31
- package/adapter/js/loader.d.ts +4 -11
- package/adapter/js/types.d.ts +67 -0
- package/adapter/js/types.js +2 -0
- package/adapter/loader.d.ts +4 -11
- package/adapter/loader.js.map +1 -1
- package/adapter/types.d.ts +67 -0
- package/adapter/types.js +3 -0
- package/adapter/types.js.map +1 -0
- package/package.json +30 -9
- package/src/PlaywrightWorld.ts +0 -53
- package/src/Template.ts +0 -23
- package/src/cucumberAdapter.ts +0 -388
- package/src/filter.ts +0 -53
- package/src/index.ts +0 -5
- package/src/loader.ts +0 -70
- package/tsconfig.js.json +0 -23
- package/tsconfig.json +0 -23
package/package.json
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qavajs/playwright-runner-adapter",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "adapter for playwright test runner",
|
|
5
5
|
"main": "adapter/index.js",
|
|
6
6
|
"types": "adapter/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./adapter/index.d.ts",
|
|
10
|
+
"require": "./adapter/index.js",
|
|
11
|
+
"import": "./adapter/index.js",
|
|
12
|
+
"default": "./adapter/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./js": {
|
|
15
|
+
"types": "./adapter/js/index.d.ts",
|
|
16
|
+
"require": "./adapter/js/index.js",
|
|
17
|
+
"import": "./adapter/js/index.js",
|
|
18
|
+
"default": "./adapter/js/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"adapter/**/*",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
7
28
|
"scripts": {
|
|
8
29
|
"build": "tsc && tsc --project tsconfig.js.json",
|
|
9
30
|
"test:ui": "playwright test --config test/playwright.config.ts --ui",
|
|
10
|
-
"test:e2e": "playwright test --config test/playwright.config.ts --project parallel --project serial"
|
|
31
|
+
"test:e2e": "playwright test --config test/playwright.config.ts --project parallel --project serial --project unit"
|
|
11
32
|
},
|
|
12
33
|
"author": "Alexandr Galichenko",
|
|
13
34
|
"license": "MIT",
|
|
@@ -19,15 +40,15 @@
|
|
|
19
40
|
"url": "https://github.com/qavajs/playwright-runner-adapter/issues"
|
|
20
41
|
},
|
|
21
42
|
"dependencies": {
|
|
22
|
-
"@cucumber/gherkin": "^
|
|
23
|
-
"@cucumber/tag-expressions": "^9.
|
|
24
|
-
"glob": "^13.0.
|
|
25
|
-
"@cucumber/cucumber": "^12.
|
|
43
|
+
"@cucumber/gherkin": "^39.0.0",
|
|
44
|
+
"@cucumber/tag-expressions": "^9.1.0",
|
|
45
|
+
"glob": "^13.0.6",
|
|
46
|
+
"@cucumber/cucumber": "^12.8.1"
|
|
26
47
|
},
|
|
27
48
|
"devDependencies": {
|
|
28
|
-
"@playwright/test": "^1.
|
|
29
|
-
"@types/node": "^25.
|
|
30
|
-
"typescript": "^
|
|
49
|
+
"@playwright/test": "^1.59.1",
|
|
50
|
+
"@types/node": "^25.6.0",
|
|
51
|
+
"typescript": "^6.0.3"
|
|
31
52
|
},
|
|
32
53
|
"keywords": [
|
|
33
54
|
"test",
|
package/src/PlaywrightWorld.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { APIRequestContext, Browser, BrowserContext, Page, test, expect } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Cucumber world for playwright adapter
|
|
5
|
-
*/
|
|
6
|
-
export class TestWorld {
|
|
7
|
-
log!: (data: any) => void;
|
|
8
|
-
attach!: (data: any, details?: { fileName?: string, mediaType: string }) => Promise<void>;
|
|
9
|
-
parameters!: string;
|
|
10
|
-
config: any;
|
|
11
|
-
supportCodeLibrary!: any;
|
|
12
|
-
test = test;
|
|
13
|
-
expect = expect;
|
|
14
|
-
|
|
15
|
-
constructor(options: any) {
|
|
16
|
-
this.log = options.log;
|
|
17
|
-
this.attach = options.attach;
|
|
18
|
-
this.parameters = options.parameters;
|
|
19
|
-
this.supportCodeLibrary = options.supportCodeLibrary;
|
|
20
|
-
this.config = options.config;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async executeStep(this: any, text: string, extraParam?: any) {
|
|
24
|
-
const steps = this.supportCodeLibrary.stepDefinitions.filter((s: any) => s.matchesStepName(text));
|
|
25
|
-
if (steps.length === 0) throw new Error(`Step "${text}" is not defined`);
|
|
26
|
-
if (steps.length > 1) throw new Error(`Step "${text}" matches multiple step definitions`);
|
|
27
|
-
const step = steps.pop() as any;
|
|
28
|
-
const { parameters } = await step.getInvocationParameters({ step: { text }, world: this } as any);
|
|
29
|
-
try {
|
|
30
|
-
await step.code.apply(this, [...parameters, extraParam]);
|
|
31
|
-
} catch (err) {
|
|
32
|
-
throw new Error(`${text}\n${err}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Cucumber Playwright world for playwright adapter
|
|
39
|
-
*/
|
|
40
|
-
export class PlaywrightWorld extends TestWorld {
|
|
41
|
-
page!: Page;
|
|
42
|
-
context!: BrowserContext;
|
|
43
|
-
browser!: Browser;
|
|
44
|
-
browserName!: string;
|
|
45
|
-
request!: APIRequestContext;
|
|
46
|
-
|
|
47
|
-
init: ({ [fixture: string]: any }) = ({ browser, context, page, request }: { browser: Browser, context: BrowserContext, page: Page, request: APIRequestContext }) => {
|
|
48
|
-
this.browser = browser;
|
|
49
|
-
this.context = context;
|
|
50
|
-
this.page = page;
|
|
51
|
-
this.request = request;
|
|
52
|
-
}
|
|
53
|
-
}
|
package/src/Template.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Define template step
|
|
3
|
-
* @param {string} scenario - multiline string with steps
|
|
4
|
-
* @example
|
|
5
|
-
* When('I click {string} and verify {string}', Template((locator, expected) => `
|
|
6
|
-
* I click '${locator}'
|
|
7
|
-
* I expect '${locator} > Value' to equal '${expected}'
|
|
8
|
-
* `));
|
|
9
|
-
*/
|
|
10
|
-
export function Template(scenario: (...args: any[]) => string) {
|
|
11
|
-
return new Proxy(scenario, {
|
|
12
|
-
apply: async function (template: (...args: string[]) => string, world: { executeStep: (step: string) => Promise<void> }, args: any[]) {
|
|
13
|
-
const scenario = template(...args) ;
|
|
14
|
-
const steps = scenario
|
|
15
|
-
.split('\n')
|
|
16
|
-
.map(step => step.trim())
|
|
17
|
-
.filter(Boolean);
|
|
18
|
-
for (const step of steps) {
|
|
19
|
-
await world.executeStep(step);
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
}
|
package/src/cucumberAdapter.ts
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import {load} from './loader';
|
|
2
|
-
import type {TestInfo, TestType} from '@playwright/test';
|
|
3
|
-
import type {ITestCaseHookParameter, ITestStepHookParameter} from '@cucumber/cucumber';
|
|
4
|
-
|
|
5
|
-
// Constants
|
|
6
|
-
const ANNOTATION_TYPES = {
|
|
7
|
-
NAME: 'name',
|
|
8
|
-
URI: 'uri',
|
|
9
|
-
TAGS: 'tags'
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
const HOOK_NAMES = {
|
|
13
|
-
BEFORE_ALL: 'Before All',
|
|
14
|
-
AFTER_ALL: 'After All',
|
|
15
|
-
BEFORE: 'Before',
|
|
16
|
-
AFTER: 'After',
|
|
17
|
-
BEFORE_STEP: 'Before Step',
|
|
18
|
-
AFTER_STEP: 'After Step',
|
|
19
|
-
WORLD: 'World',
|
|
20
|
-
BEFORE_HOOKS: 'Before Hooks',
|
|
21
|
-
AFTER_HOOKS: 'After Hooks'
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
const DEFAULT_VALUES = {
|
|
25
|
-
ATTACHMENT_FILENAME: 'attachment',
|
|
26
|
-
CONTENT_TYPE: 'text/plain',
|
|
27
|
-
COLUMN: 1,
|
|
28
|
-
TEST_CASE_ID: '0',
|
|
29
|
-
TEST_STEP_ID: '0'
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
const STEP_ARGUMENT_LABELS = {
|
|
33
|
-
DOC_STRING: ' [MultiLine]',
|
|
34
|
-
DATA_TABLE: ' [DataTable]'
|
|
35
|
-
} as const;
|
|
36
|
-
|
|
37
|
-
// Type definitions
|
|
38
|
-
interface Location {
|
|
39
|
-
file: string;
|
|
40
|
-
line: number;
|
|
41
|
-
column: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface StepDefinition {
|
|
45
|
-
uri: string;
|
|
46
|
-
line: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface TestResult {
|
|
50
|
-
status: string;
|
|
51
|
-
error?: Error;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface TestExecutionResult extends TestResult {
|
|
55
|
-
start: number;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface FeatureContext {
|
|
59
|
-
gherkinDocument: unknown;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface PickleStep {
|
|
63
|
-
text: string;
|
|
64
|
-
argument?: { docString?: unknown };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Creates a location object from step definition
|
|
69
|
-
*/
|
|
70
|
-
function getLine(step: StepDefinition): { location: Location } {
|
|
71
|
-
return {
|
|
72
|
-
location: {
|
|
73
|
-
column: DEFAULT_VALUES.COLUMN,
|
|
74
|
-
file: step.uri,
|
|
75
|
-
line: step.line
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Logs data to console
|
|
82
|
-
*/
|
|
83
|
-
function log(data: unknown): void {
|
|
84
|
-
console.log(data);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Attaches content to test report
|
|
89
|
-
*/
|
|
90
|
-
function attach(
|
|
91
|
-
this: { test: { info: () => TestInfo } },
|
|
92
|
-
body: string | Buffer,
|
|
93
|
-
details?: { fileName?: string; mediaType?: string }
|
|
94
|
-
): Promise<void> {
|
|
95
|
-
const fileName = details?.fileName ?? DEFAULT_VALUES.ATTACHMENT_FILENAME;
|
|
96
|
-
const contentType = details?.mediaType ?? DEFAULT_VALUES.CONTENT_TYPE;
|
|
97
|
-
return this.test.info().attach(fileName, { body, contentType });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Generates a human-readable step name with argument type indicator
|
|
102
|
-
*/
|
|
103
|
-
function stepName(pickleStep: {
|
|
104
|
-
text: string;
|
|
105
|
-
argument?: { docString?: unknown }
|
|
106
|
-
}): string {
|
|
107
|
-
let step = pickleStep.text;
|
|
108
|
-
|
|
109
|
-
if (pickleStep.argument) {
|
|
110
|
-
step += pickleStep.argument.docString
|
|
111
|
-
? STEP_ARGUMENT_LABELS.DOC_STRING
|
|
112
|
-
: STEP_ARGUMENT_LABELS.DATA_TABLE;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return step;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function createTestExecutionResult(): TestExecutionResult {
|
|
119
|
-
return {
|
|
120
|
-
status: 'passed',
|
|
121
|
-
start: Date.now()
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function createHookResult(result: TestExecutionResult): any {
|
|
126
|
-
return {
|
|
127
|
-
duration: (Date.now() - result.start) as any,
|
|
128
|
-
message: result.error?.message,
|
|
129
|
-
status: result.status.toUpperCase(),
|
|
130
|
-
exception: result.error
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function createScenarioAnnotation(testCase: any): { type: string; description: string }[] {
|
|
135
|
-
const tag = [...new Set(testCase.tags.map((item: { name: string }) => item.name))];
|
|
136
|
-
return [
|
|
137
|
-
{type: ANNOTATION_TYPES.NAME, description: testCase.name},
|
|
138
|
-
{type: ANNOTATION_TYPES.URI, description: testCase.uri},
|
|
139
|
-
{type: ANNOTATION_TYPES.TAGS, description: JSON.stringify(tag)}
|
|
140
|
-
];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function createScenarioTags(testCase: any): string[] {
|
|
144
|
-
return [...new Set<string>(testCase.tags.map((item: { name: string }) => item.name))];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function createCaseHookPayload(feature: FeatureContext, testCase: any): ITestCaseHookParameter {
|
|
148
|
-
return {
|
|
149
|
-
gherkinDocument: feature.gherkinDocument,
|
|
150
|
-
pickle: testCase
|
|
151
|
-
} as ITestCaseHookParameter;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async function executeCaseHooks(
|
|
155
|
-
test: TestType<any, any>,
|
|
156
|
-
hooks: any[],
|
|
157
|
-
world: any,
|
|
158
|
-
testCase: any,
|
|
159
|
-
defaultHookName: string,
|
|
160
|
-
createPayload: (hook: any) => any
|
|
161
|
-
): Promise<void> {
|
|
162
|
-
for (const hook of hooks) {
|
|
163
|
-
if (!hook.appliesToTestCase(testCase)) {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const hookName = hook.name ?? defaultHookName;
|
|
168
|
-
const location = getLine(hook);
|
|
169
|
-
await test.step(
|
|
170
|
-
hookName,
|
|
171
|
-
() => hook.code.apply(world, [createPayload(hook)]),
|
|
172
|
-
location
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function executeStepHooks(
|
|
178
|
-
test: TestType<any, any>,
|
|
179
|
-
hooks: any[],
|
|
180
|
-
world: any,
|
|
181
|
-
testCase: any,
|
|
182
|
-
hookName: string,
|
|
183
|
-
createPayload: (hook: any) => any
|
|
184
|
-
): Promise<void> {
|
|
185
|
-
for (const hook of hooks) {
|
|
186
|
-
if (!hook.appliesToTestCase(testCase)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const location = getLine(hook);
|
|
191
|
-
await test.step(
|
|
192
|
-
hookName,
|
|
193
|
-
() => hook.code.apply(world, [createPayload(hook)]),
|
|
194
|
-
location
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function createStepHookParameter(
|
|
200
|
-
feature: any,
|
|
201
|
-
testCase: any,
|
|
202
|
-
pickleStep: any,
|
|
203
|
-
result: TestExecutionResult
|
|
204
|
-
): ITestStepHookParameter {
|
|
205
|
-
return {
|
|
206
|
-
gherkinDocument: feature.gherkinDocument,
|
|
207
|
-
testCaseStartedId: DEFAULT_VALUES.TEST_CASE_ID,
|
|
208
|
-
testStepId: DEFAULT_VALUES.TEST_STEP_ID,
|
|
209
|
-
pickle: testCase,
|
|
210
|
-
pickleStep,
|
|
211
|
-
result: createHookResult(result)
|
|
212
|
-
} as any;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function createCaseHookParameter(
|
|
216
|
-
feature: any,
|
|
217
|
-
testCase: any,
|
|
218
|
-
result: TestExecutionResult
|
|
219
|
-
): ITestCaseHookParameter {
|
|
220
|
-
return {
|
|
221
|
-
gherkinDocument: feature.gherkinDocument,
|
|
222
|
-
testCaseStartedId: DEFAULT_VALUES.TEST_CASE_ID,
|
|
223
|
-
pickle: testCase,
|
|
224
|
-
result: createHookResult(result)
|
|
225
|
-
} as any;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function findSingleStepDefinition(
|
|
229
|
-
supportCodeLibrary: any,
|
|
230
|
-
pickleStep: PickleStep
|
|
231
|
-
): any {
|
|
232
|
-
const matchingSteps = supportCodeLibrary.stepDefinitions
|
|
233
|
-
.filter((stepDefinition: any) => stepDefinition.matchesStepName(pickleStep.text));
|
|
234
|
-
|
|
235
|
-
if (matchingSteps.length === 0) {
|
|
236
|
-
throw new Error(`Step '${pickleStep.text}' is not defined`);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (matchingSteps.length > 1) {
|
|
240
|
-
throw new Error(`Step '${pickleStep.text}' matches multiple step definitions`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return matchingSteps[0];
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Executes before all hooks
|
|
248
|
-
*/
|
|
249
|
-
function executeBeforeAllHooks(
|
|
250
|
-
test: TestType<any, any>,
|
|
251
|
-
hooks: any[]
|
|
252
|
-
): void {
|
|
253
|
-
if (hooks.length === 0) return;
|
|
254
|
-
test.beforeAll(async () => {
|
|
255
|
-
for (const hook of hooks) {
|
|
256
|
-
const location = getLine(hook);
|
|
257
|
-
await test.step(
|
|
258
|
-
HOOK_NAMES.BEFORE_ALL,
|
|
259
|
-
() => hook.code.apply({}),
|
|
260
|
-
location
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Executes after all hooks
|
|
268
|
-
*/
|
|
269
|
-
function executeAfterAllHooks(
|
|
270
|
-
test: TestType<any, any>,
|
|
271
|
-
hooks: any[]
|
|
272
|
-
): void {
|
|
273
|
-
if (hooks.length === 0) return;
|
|
274
|
-
test.afterAll(async () => {
|
|
275
|
-
for (const hook of hooks) {
|
|
276
|
-
const location = getLine(hook);
|
|
277
|
-
await test.step(
|
|
278
|
-
HOOK_NAMES.AFTER_ALL,
|
|
279
|
-
() => hook.code.apply({}),
|
|
280
|
-
location
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Main configuration function that sets up Cucumber adapter for Playwright
|
|
288
|
-
*/
|
|
289
|
-
export function defineConfig(config: any): any {
|
|
290
|
-
const {features, supportCodeLibrary} = load(config);
|
|
291
|
-
|
|
292
|
-
const fixture = new supportCodeLibrary.World({config});
|
|
293
|
-
const test: TestType<any, any> = fixture.test;
|
|
294
|
-
executeBeforeAllHooks(test, supportCodeLibrary.beforeTestRunHookDefinitions);
|
|
295
|
-
|
|
296
|
-
for (const feature of features) {
|
|
297
|
-
const tests = feature.tests;
|
|
298
|
-
test.describe(feature.feature as string, () => {
|
|
299
|
-
|
|
300
|
-
for (const testCase of tests) {
|
|
301
|
-
const tag = createScenarioTags(testCase);
|
|
302
|
-
const annotation = createScenarioAnnotation(testCase);
|
|
303
|
-
const testBody = async (fixtures: any) => {
|
|
304
|
-
const world = new supportCodeLibrary.World({
|
|
305
|
-
log,
|
|
306
|
-
attach,
|
|
307
|
-
supportCodeLibrary,
|
|
308
|
-
config
|
|
309
|
-
});
|
|
310
|
-
world.init(fixtures);
|
|
311
|
-
|
|
312
|
-
const result = createTestExecutionResult();
|
|
313
|
-
|
|
314
|
-
await executeCaseHooks(
|
|
315
|
-
test,
|
|
316
|
-
supportCodeLibrary.beforeTestCaseHookDefinitions,
|
|
317
|
-
world,
|
|
318
|
-
testCase,
|
|
319
|
-
HOOK_NAMES.BEFORE,
|
|
320
|
-
() => createCaseHookPayload(feature, testCase)
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
for (const pickleStep of testCase.steps) {
|
|
325
|
-
if (result.status !== 'passed') {
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
const step = findSingleStepDefinition(supportCodeLibrary, pickleStep);
|
|
329
|
-
const location = getLine(step);
|
|
330
|
-
await test.step(stepName(pickleStep), async () => {
|
|
331
|
-
await executeStepHooks(
|
|
332
|
-
test,
|
|
333
|
-
supportCodeLibrary.beforeTestStepHookDefinitions,
|
|
334
|
-
world,
|
|
335
|
-
testCase,
|
|
336
|
-
HOOK_NAMES.BEFORE_STEP,
|
|
337
|
-
() => ({
|
|
338
|
-
gherkinDocument: feature.gherkinDocument,
|
|
339
|
-
pickle: testCase,
|
|
340
|
-
pickleStep
|
|
341
|
-
})
|
|
342
|
-
);
|
|
343
|
-
const {parameters} = await step.getInvocationParameters({
|
|
344
|
-
step: {
|
|
345
|
-
text: pickleStep.text,
|
|
346
|
-
argument: pickleStep.argument
|
|
347
|
-
},
|
|
348
|
-
world
|
|
349
|
-
} as any);
|
|
350
|
-
try {
|
|
351
|
-
await step.code.apply(world, parameters);
|
|
352
|
-
} catch (err: any) {
|
|
353
|
-
result.status = 'failed';
|
|
354
|
-
result.error = err;
|
|
355
|
-
throw err;
|
|
356
|
-
} finally {
|
|
357
|
-
await executeStepHooks(
|
|
358
|
-
test,
|
|
359
|
-
supportCodeLibrary.afterTestStepHookDefinitions,
|
|
360
|
-
world,
|
|
361
|
-
testCase,
|
|
362
|
-
HOOK_NAMES.AFTER_STEP,
|
|
363
|
-
() => createStepHookParameter(feature, testCase, pickleStep, result)
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
}, location);
|
|
367
|
-
}
|
|
368
|
-
} finally {
|
|
369
|
-
await executeCaseHooks(
|
|
370
|
-
test,
|
|
371
|
-
supportCodeLibrary.afterTestCaseHookDefinitions,
|
|
372
|
-
world,
|
|
373
|
-
testCase,
|
|
374
|
-
HOOK_NAMES.AFTER,
|
|
375
|
-
() => createCaseHookParameter(feature, testCase, result)
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
testBody.toString = () => fixture.init.toString();
|
|
381
|
-
test(testCase.name, {tag, annotation}, testBody);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
executeAfterAllHooks(test, supportCodeLibrary.afterTestRunHookDefinitions);
|
|
388
|
-
}
|
package/src/filter.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import parse from '@cucumber/tag-expressions';
|
|
2
|
-
|
|
3
|
-
class TagExpression extends RegExp {
|
|
4
|
-
private expressionNode: {
|
|
5
|
-
evaluate(variables: string[]): boolean;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
constructor(private tagExpression: string) {
|
|
9
|
-
super('');
|
|
10
|
-
this.expressionNode = parse(this.tagExpression);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
test(testName: string) {
|
|
14
|
-
const tokens = testName.split(/\s+/);
|
|
15
|
-
return this.expressionNode.evaluate(tokens);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Translate cucumber tag expression to playwright grep
|
|
21
|
-
* @param {string} tagExpression
|
|
22
|
-
* @example
|
|
23
|
-
* import { tags } from '@qavajs/playwright-runner-adapter';
|
|
24
|
-
* export default defineConfig({
|
|
25
|
-
* grep: tags('@oneTag and @anotherTag')
|
|
26
|
-
* });
|
|
27
|
-
*/
|
|
28
|
-
export function tags(tagExpression: string): RegExp {
|
|
29
|
-
return new TagExpression(tagExpression);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class Filter extends RegExp {
|
|
33
|
-
constructor(private predicate: (testName: string) => boolean) {
|
|
34
|
-
super('');
|
|
35
|
-
this.predicate = predicate;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
test(testName: string) {
|
|
39
|
-
return this.predicate(testName);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Filter test cases by name
|
|
45
|
-
* @param {(testName: string) => boolean} predicate
|
|
46
|
-
* import { filter } from '@qavajs/playwright-runner-adapter';
|
|
47
|
-
* export default defineConfig({
|
|
48
|
-
* grep: filter(name => name.includes('login test'))
|
|
49
|
-
* });
|
|
50
|
-
*/
|
|
51
|
-
export function filter(predicate: (testName: string) => boolean): RegExp {
|
|
52
|
-
return new Filter(predicate);
|
|
53
|
-
}
|
package/src/index.ts
DELETED
package/src/loader.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { AstBuilder, compile, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin';
|
|
5
|
-
import { supportCodeLibraryBuilder } from '@cucumber/cucumber';
|
|
6
|
-
import type { Pickle, GherkinDocument } from '@cucumber/messages';
|
|
7
|
-
import { globSync } from 'glob';
|
|
8
|
-
import { PlaywrightWorld } from './PlaywrightWorld';
|
|
9
|
-
|
|
10
|
-
const uuidFn = () => randomUUID();
|
|
11
|
-
const builder = new AstBuilder(uuidFn);
|
|
12
|
-
const matcher = new GherkinClassicTokenMatcher();
|
|
13
|
-
const parser = new Parser(builder, matcher)
|
|
14
|
-
|
|
15
|
-
function duplicates(tests: readonly Pickle[]) {
|
|
16
|
-
const counts: Record<string, number> = {};
|
|
17
|
-
return tests.map(item => {
|
|
18
|
-
const name = item.name;
|
|
19
|
-
if (!(name in counts)) {
|
|
20
|
-
counts[name] = 1;
|
|
21
|
-
}
|
|
22
|
-
if (counts[name] > 1) {
|
|
23
|
-
item.name = `${item.name} ${counts[name]}`;
|
|
24
|
-
}
|
|
25
|
-
counts[name] = counts[name] + 1;
|
|
26
|
-
return item;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Feature = {
|
|
31
|
-
feature?: string;
|
|
32
|
-
gherkinDocument: GherkinDocument;
|
|
33
|
-
tests: readonly Pickle[];
|
|
34
|
-
uri: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function loadFeatures(globPattern: string[]): Feature[] {
|
|
38
|
-
const files = globSync(globPattern);
|
|
39
|
-
return files.map(file => {
|
|
40
|
-
const filePath = resolve(file);
|
|
41
|
-
const gherkinDocument = parser.parse(readFileSync(filePath, 'utf-8'));
|
|
42
|
-
return {
|
|
43
|
-
feature: gherkinDocument.feature?.name,
|
|
44
|
-
uri: filePath,
|
|
45
|
-
gherkinDocument,
|
|
46
|
-
tests: duplicates(compile(gherkinDocument, file, uuidFn))
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function loadStepDefinitions(globPattern: string[]) {
|
|
52
|
-
supportCodeLibraryBuilder.reset(process.cwd(), uuidFn);
|
|
53
|
-
supportCodeLibraryBuilder.methods.setWorldConstructor(PlaywrightWorld);
|
|
54
|
-
const files = globSync(globPattern);
|
|
55
|
-
for (const file of files) {
|
|
56
|
-
const filePath = resolve(file);
|
|
57
|
-
require(filePath);
|
|
58
|
-
}
|
|
59
|
-
return supportCodeLibraryBuilder.finalize();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function load(config: any) {
|
|
63
|
-
const features = loadFeatures(config.paths);
|
|
64
|
-
const supportCodeLibrary = loadStepDefinitions([
|
|
65
|
-
...(config.requireModules ?? []),
|
|
66
|
-
...(config.require ?? []),
|
|
67
|
-
...(config.import ?? [])
|
|
68
|
-
]);
|
|
69
|
-
return { features, supportCodeLibrary }
|
|
70
|
-
}
|
package/tsconfig.js.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"include": [
|
|
3
|
-
"src/**/*.ts"
|
|
4
|
-
],
|
|
5
|
-
"exclude": [
|
|
6
|
-
"test/**/*.spec.ts"
|
|
7
|
-
],
|
|
8
|
-
"compilerOptions": {
|
|
9
|
-
"target": "es2023",
|
|
10
|
-
"module": "node16",
|
|
11
|
-
"moduleResolution": "node16",
|
|
12
|
-
"outDir": "./adapter/js",
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"sourceMap": false,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"strict": true,
|
|
17
|
-
"skipLibCheck": true,
|
|
18
|
-
"declaration": true,
|
|
19
|
-
"types": [
|
|
20
|
-
"node"
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"include": [
|
|
3
|
-
"src/**/*.ts"
|
|
4
|
-
],
|
|
5
|
-
"exclude": [
|
|
6
|
-
"test/**/*.spec.ts"
|
|
7
|
-
],
|
|
8
|
-
"compilerOptions": {
|
|
9
|
-
"target": "es2023",
|
|
10
|
-
"module": "node16",
|
|
11
|
-
"moduleResolution": "node16",
|
|
12
|
-
"outDir": "./adapter",
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"sourceMap": true,
|
|
15
|
-
"forceConsistentCasingInFileNames": true,
|
|
16
|
-
"strict": true,
|
|
17
|
-
"skipLibCheck": true,
|
|
18
|
-
"declaration": true,
|
|
19
|
-
"types": [
|
|
20
|
-
"node"
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
}
|