@qavajs/playwright-runner-adapter 2.4.0 → 2.5.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.
- package/CHANGELOG.md +28 -0
- package/README.md +160 -11
- package/adapter/PlaywrightWorld.d.ts +8 -6
- package/adapter/PlaywrightWorld.js +21 -8
- package/adapter/PlaywrightWorld.js.map +1 -1
- package/adapter/Template.d.ts +13 -2
- package/adapter/Template.js +57 -8
- package/adapter/Template.js.map +1 -1
- package/adapter/cucumberAdapter.d.ts +7 -0
- package/adapter/cucumberAdapter.js +253 -98
- package/adapter/cucumberAdapter.js.map +1 -1
- package/adapter/filter.d.ts +8 -0
- package/adapter/filter.js +27 -1
- package/adapter/filter.js.map +1 -1
- package/adapter/gherkin.d.ts +45 -0
- package/adapter/gherkin.js +155 -0
- package/adapter/gherkin.js.map +1 -0
- package/adapter/index.d.ts +1 -0
- package/adapter/index.js +1 -0
- package/adapter/index.js.map +1 -1
- package/adapter/js/PlaywrightWorld.d.ts +8 -6
- package/adapter/js/PlaywrightWorld.js +21 -8
- package/adapter/js/Template.d.ts +13 -2
- package/adapter/js/Template.js +57 -8
- package/adapter/js/cucumberAdapter.d.ts +7 -0
- package/adapter/js/cucumberAdapter.js +253 -98
- package/adapter/js/filter.d.ts +8 -0
- package/adapter/js/filter.js +27 -1
- package/adapter/js/gherkin.d.ts +45 -0
- package/adapter/js/gherkin.js +154 -0
- package/adapter/js/index.d.ts +1 -0
- package/adapter/js/index.js +1 -0
- package/adapter/js/loader.d.ts +9 -1
- package/adapter/js/loader.js +35 -15
- package/adapter/js/testDetails.d.ts +10 -0
- package/adapter/js/testDetails.js +27 -1
- package/adapter/js/timeout.d.ts +17 -0
- package/adapter/js/timeout.js +24 -0
- package/adapter/js/types.d.ts +35 -6
- package/adapter/js/withWorld.d.ts +43 -0
- package/adapter/js/withWorld.js +16 -0
- package/adapter/loader.d.ts +9 -1
- package/adapter/loader.js +35 -15
- package/adapter/loader.js.map +1 -1
- package/adapter/testDetails.d.ts +10 -0
- package/adapter/testDetails.js +27 -1
- package/adapter/testDetails.js.map +1 -1
- package/adapter/timeout.d.ts +17 -0
- package/adapter/timeout.js +25 -0
- package/adapter/timeout.js.map +1 -0
- package/adapter/types.d.ts +35 -6
- package/adapter/withWorld.d.ts +43 -0
- package/adapter/withWorld.js +17 -0
- package/adapter/withWorld.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectPositions = collectPositions;
|
|
4
|
+
exports.resolveLocation = resolveLocation;
|
|
5
|
+
exports.callAt = callAt;
|
|
6
|
+
exports.stepTitle = stepTitle;
|
|
7
|
+
exports.toPickleArgument = toPickleArgument;
|
|
8
|
+
exports.buildSnippet = buildSnippet;
|
|
9
|
+
exports.formatLocation = formatLocation;
|
|
10
|
+
exports.featureTitle = featureTitle;
|
|
11
|
+
const node_vm_1 = require("node:vm");
|
|
12
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
13
|
+
const PREVIEW_LENGTH = 50;
|
|
14
|
+
/**
|
|
15
|
+
* Map ast node ids (scenarios, steps, example rows, rules) of a gherkin document to their positions
|
|
16
|
+
*/
|
|
17
|
+
function collectPositions(gherkinDocument) {
|
|
18
|
+
const positions = new Map();
|
|
19
|
+
const addSteps = (steps) => {
|
|
20
|
+
for (const step of steps)
|
|
21
|
+
positions.set(step.id, step.location);
|
|
22
|
+
};
|
|
23
|
+
const addScenario = (scenario) => {
|
|
24
|
+
positions.set(scenario.id, scenario.location);
|
|
25
|
+
addSteps(scenario.steps);
|
|
26
|
+
for (const examples of scenario.examples) {
|
|
27
|
+
for (const row of examples.tableBody)
|
|
28
|
+
positions.set(row.id, row.location);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
for (const child of gherkinDocument.feature?.children ?? []) {
|
|
32
|
+
if (child.background)
|
|
33
|
+
addSteps(child.background.steps);
|
|
34
|
+
if (child.scenario)
|
|
35
|
+
addScenario(child.scenario);
|
|
36
|
+
if (child.rule) {
|
|
37
|
+
for (const ruleChild of child.rule.children) {
|
|
38
|
+
if (ruleChild.background)
|
|
39
|
+
addSteps(ruleChild.background.steps);
|
|
40
|
+
if (ruleChild.scenario)
|
|
41
|
+
addScenario(ruleChild.scenario);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return positions;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve location of a pickle or pickle step.
|
|
49
|
+
* The last ast node id is used, so scenario outline examples point to their example row.
|
|
50
|
+
*/
|
|
51
|
+
function resolveLocation(positions, file, astNodeIds) {
|
|
52
|
+
for (let i = astNodeIds.length - 1; i >= 0; i--) {
|
|
53
|
+
const position = positions.get(astNodeIds[i]);
|
|
54
|
+
if (position)
|
|
55
|
+
return { file, line: position.line, column: position.column ?? 1 };
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Call function so that the immediate caller frame reports the given source location.
|
|
61
|
+
* Playwright takes test and describe locations from the caller frame,
|
|
62
|
+
* so this makes reports, `--list` and IDE integrations point to the feature file instead of the adapter.
|
|
63
|
+
*/
|
|
64
|
+
function callAt(location, fn, args) {
|
|
65
|
+
if (!location)
|
|
66
|
+
return fn(...args);
|
|
67
|
+
const code = '(function (fn, args) { return fn(...args); })';
|
|
68
|
+
const callColumn = code.indexOf('fn(...args)');
|
|
69
|
+
const caller = (0, node_vm_1.runInThisContext)(code, {
|
|
70
|
+
filename: location.file,
|
|
71
|
+
lineOffset: location.line - 1,
|
|
72
|
+
columnOffset: location.column - 1 - callColumn
|
|
73
|
+
});
|
|
74
|
+
return caller(fn, args);
|
|
75
|
+
}
|
|
76
|
+
function preview(text) {
|
|
77
|
+
const singleLine = text.replace(/\s*\n\s*/g, ' ↵ ').replace(/\s+/g, ' ').trim();
|
|
78
|
+
return singleLine.length > PREVIEW_LENGTH
|
|
79
|
+
? `${singleLine.slice(0, PREVIEW_LENGTH - 1)}…`
|
|
80
|
+
: singleLine;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build step title for reports including short preview of doc string or data table argument
|
|
84
|
+
*/
|
|
85
|
+
function stepTitle(pickleStep) {
|
|
86
|
+
const argument = pickleStep.argument;
|
|
87
|
+
if (argument?.docString) {
|
|
88
|
+
return `${pickleStep.text} [MultiLine: ${preview(argument.docString.content)}]`;
|
|
89
|
+
}
|
|
90
|
+
if (argument?.dataTable) {
|
|
91
|
+
const rows = argument.dataTable.rows
|
|
92
|
+
.map(row => `| ${row.cells.map(cell => cell.value).join(' | ')} |`)
|
|
93
|
+
.join(' ');
|
|
94
|
+
return `${pickleStep.text} [DataTable: ${preview(rows)}]`;
|
|
95
|
+
}
|
|
96
|
+
return pickleStep.text;
|
|
97
|
+
}
|
|
98
|
+
function isTable(value) {
|
|
99
|
+
return Array.isArray(value) && value.every(row => Array.isArray(row));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Convert value passed to executeStep into a pickle step argument.
|
|
103
|
+
* Strings become doc strings, `string[][]` and DataTable instances become data tables.
|
|
104
|
+
*/
|
|
105
|
+
function toPickleArgument(value) {
|
|
106
|
+
if (typeof value === 'string') {
|
|
107
|
+
return { argument: { docString: { content: value } } };
|
|
108
|
+
}
|
|
109
|
+
const rows = value instanceof cucumber_1.DataTable ? value.raw() : isTable(value) ? value : undefined;
|
|
110
|
+
if (rows) {
|
|
111
|
+
return {
|
|
112
|
+
argument: {
|
|
113
|
+
dataTable: {
|
|
114
|
+
rows: rows.map(row => ({ cells: row.map(cell => ({ value: String(cell) })) }))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { extra: value };
|
|
120
|
+
}
|
|
121
|
+
const KEYWORD_TYPES = {
|
|
122
|
+
Context: 'precondition',
|
|
123
|
+
Action: 'event',
|
|
124
|
+
Outcome: 'outcome'
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Build cucumber-style snippet for undefined step
|
|
128
|
+
*/
|
|
129
|
+
function buildSnippet(supportCodeLibrary, pickleStep) {
|
|
130
|
+
try {
|
|
131
|
+
const { default: SnippetBuilder } = require('@cucumber/cucumber/lib/formatter/step_definition_snippet_builder/index');
|
|
132
|
+
const { default: SnippetSyntax } = require('@cucumber/cucumber/lib/formatter/step_definition_snippet_builder/javascript_snippet_syntax');
|
|
133
|
+
const builder = new SnippetBuilder({
|
|
134
|
+
snippetSyntax: new SnippetSyntax('async-await'),
|
|
135
|
+
parameterTypeRegistry: supportCodeLibrary.parameterTypeRegistry
|
|
136
|
+
});
|
|
137
|
+
return builder.build({
|
|
138
|
+
keywordType: KEYWORD_TYPES[pickleStep.type ?? ''] ?? 'precondition',
|
|
139
|
+
pickleStep
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function formatLocation(location) {
|
|
147
|
+
return location ? `${location.file}:${location.line}` : '';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Feature title used for describe block; falls back to file path for unnamed features
|
|
151
|
+
*/
|
|
152
|
+
function featureTitle(name, uri) {
|
|
153
|
+
return name?.trim() ? name : uri;
|
|
154
|
+
}
|
package/adapter/js/index.d.ts
CHANGED
package/adapter/js/index.js
CHANGED
package/adapter/js/loader.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { CucumberAdapterConfig, Feature, SupportCodeLibrary } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Find files matching glob patterns; returns unique absolute paths in stable (sorted) order
|
|
4
|
+
*/
|
|
5
|
+
export declare function findFiles(patterns: string[]): string[];
|
|
2
6
|
export declare function loadFeatures(globPattern: string[]): Feature[];
|
|
3
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Require modules by name or path resolved from the current working directory (e.g. `ts-node/register`)
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadModules(modules: string[]): void;
|
|
11
|
+
export declare function loadStepDefinitions(globPattern: string[], requireModules?: string[]): SupportCodeLibrary;
|
|
4
12
|
export declare function load(config: CucumberAdapterConfig): {
|
|
5
13
|
features: Feature[];
|
|
6
14
|
supportCodeLibrary: SupportCodeLibrary;
|
package/adapter/js/loader.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findFiles = findFiles;
|
|
3
4
|
exports.loadFeatures = loadFeatures;
|
|
5
|
+
exports.loadModules = loadModules;
|
|
4
6
|
exports.loadStepDefinitions = loadStepDefinitions;
|
|
5
7
|
exports.load = load;
|
|
6
8
|
const node_fs_1 = require("node:fs");
|
|
7
9
|
const node_path_1 = require("node:path");
|
|
10
|
+
const node_module_1 = require("node:module");
|
|
8
11
|
const node_crypto_1 = require("node:crypto");
|
|
9
12
|
const gherkin_1 = require("@cucumber/gherkin");
|
|
10
13
|
const cucumber_1 = require("@cucumber/cucumber");
|
|
11
|
-
const glob_1 = require("glob");
|
|
12
14
|
const PlaywrightWorld_1 = require("./PlaywrightWorld");
|
|
13
15
|
const uuidFn = () => (0, node_crypto_1.randomUUID)();
|
|
14
16
|
const builder = new gherkin_1.AstBuilder(uuidFn);
|
|
@@ -28,35 +30,53 @@ function duplicates(tests) {
|
|
|
28
30
|
return item;
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Find files matching glob patterns; returns unique absolute paths in stable (sorted) order
|
|
35
|
+
*/
|
|
36
|
+
function findFiles(patterns) {
|
|
37
|
+
if (patterns.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
const files = (0, node_fs_1.globSync)(patterns).map(file => (0, node_path_1.resolve)(file));
|
|
40
|
+
return [...new Set(files)].sort();
|
|
41
|
+
}
|
|
31
42
|
function loadFeatures(globPattern) {
|
|
32
|
-
const files = (
|
|
33
|
-
return files.map(
|
|
34
|
-
const filePath = (0, node_path_1.resolve)(file);
|
|
43
|
+
const files = findFiles(globPattern);
|
|
44
|
+
return files.map(filePath => {
|
|
35
45
|
const gherkinDocument = parser.parse((0, node_fs_1.readFileSync)(filePath, 'utf-8'));
|
|
36
46
|
return {
|
|
37
47
|
feature: gherkinDocument.feature?.name,
|
|
38
48
|
uri: filePath,
|
|
39
49
|
gherkinDocument,
|
|
40
|
-
tests: duplicates((0, gherkin_1.compile)(gherkinDocument,
|
|
50
|
+
tests: duplicates((0, gherkin_1.compile)(gherkinDocument, (0, node_path_1.relative)(process.cwd(), filePath), uuidFn))
|
|
41
51
|
};
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Require modules by name or path resolved from the current working directory (e.g. `ts-node/register`)
|
|
56
|
+
*/
|
|
57
|
+
function loadModules(modules) {
|
|
58
|
+
const cwdRequire = (0, node_module_1.createRequire)((0, node_path_1.join)(process.cwd(), 'index.js'));
|
|
59
|
+
for (const name of modules) {
|
|
60
|
+
cwdRequire(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function loadStepDefinitions(globPattern, requireModules = []) {
|
|
45
64
|
cucumber_1.supportCodeLibraryBuilder.reset(process.cwd(), uuidFn);
|
|
65
|
+
// cucumber defaults to 5000ms; apply a default timeout only when set explicitly with setDefaultTimeout
|
|
66
|
+
cucumber_1.supportCodeLibraryBuilder.defaultTimeout = undefined;
|
|
46
67
|
cucumber_1.supportCodeLibraryBuilder.methods.setWorldConstructor(PlaywrightWorld_1.PlaywrightWorld);
|
|
47
|
-
|
|
48
|
-
for (const
|
|
49
|
-
const filePath = (0, node_path_1.resolve)(file);
|
|
68
|
+
loadModules(requireModules);
|
|
69
|
+
for (const filePath of findFiles(globPattern)) {
|
|
50
70
|
require(filePath);
|
|
51
71
|
}
|
|
52
72
|
return cucumber_1.supportCodeLibraryBuilder.finalize();
|
|
53
73
|
}
|
|
54
74
|
function load(config) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
]);
|
|
75
|
+
const envPaths = process.env.CUCUMBER_PATHS
|
|
76
|
+
?.split(',')
|
|
77
|
+
.map(path => path.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
const features = loadFeatures(envPaths?.length ? envPaths : config.paths);
|
|
80
|
+
const supportCodeLibrary = loadStepDefinitions([...(config.require ?? []), ...(config.import ?? [])], config.requireModules ?? []);
|
|
61
81
|
return { features, supportCodeLibrary };
|
|
62
82
|
}
|
|
@@ -4,7 +4,16 @@ export interface ResolvedTestDetails {
|
|
|
4
4
|
tag: string[];
|
|
5
5
|
annotation: TestDetailsAnnotation[];
|
|
6
6
|
lock: string[];
|
|
7
|
+
/** test timeout in milliseconds from `@timeout(ms)` tag (the last one wins) */
|
|
8
|
+
timeout?: number;
|
|
7
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolve retries from `@retries(n)` feature tag.
|
|
12
|
+
* Playwright configures retries per describe block, so the tag is only supported on Feature.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveRetries(featureTags: readonly {
|
|
15
|
+
name: string;
|
|
16
|
+
}[]): number | undefined;
|
|
8
17
|
/**
|
|
9
18
|
* Translate gherkin tags of a pickle into playwright test details.
|
|
10
19
|
*
|
|
@@ -12,6 +21,7 @@ export interface ResolvedTestDetails {
|
|
|
12
21
|
* - `@lock(name)` → `lock: 'name'`; `@lock(-exclusive-)` → built-in exclusive lock
|
|
13
22
|
* - `@annotation(type,description)` → `annotation: { type, description }`
|
|
14
23
|
* - `@skip()` / `@fixme()` / `@fail()` / `@slow()` → playwright modifier with optional description, e.g. `@fixme(JIRA-1)`
|
|
24
|
+
* - `@timeout(ms)` → test timeout; `@retries(n)` → consumed here, applied per feature (see `resolveRetries`)
|
|
15
25
|
*
|
|
16
26
|
* Note: gherkin tags may not contain whitespace, so write `@annotation(issue,JIRA-1)` without spaces.
|
|
17
27
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRetries = resolveRetries;
|
|
3
4
|
exports.resolveTestDetails = resolveTestDetails;
|
|
4
5
|
const LOCK_TAG = /^@lock\(([^)]+)\)$/;
|
|
5
6
|
const ANNOTATION_TAG = /^@annotation\(([^,)]+)(?:,(.*))?\)$/;
|
|
@@ -8,6 +9,21 @@ const ANNOTATION_TAG = /^@annotation\(([^,)]+)(?:,(.*))?\)$/;
|
|
|
8
9
|
* (skip/fixme before fixtures are set up, fail/slow at test start).
|
|
9
10
|
*/
|
|
10
11
|
const MODIFIER_TAG = /^@(skip|fixme|fail|slow)\((.*)\)$/;
|
|
12
|
+
const TIMEOUT_TAG = /^@timeout\((\d+)\)$/;
|
|
13
|
+
const RETRIES_TAG = /^@retries\((\d+)\)$/;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve retries from `@retries(n)` feature tag.
|
|
16
|
+
* Playwright configures retries per describe block, so the tag is only supported on Feature.
|
|
17
|
+
*/
|
|
18
|
+
function resolveRetries(featureTags) {
|
|
19
|
+
let retries;
|
|
20
|
+
for (const { name } of featureTags) {
|
|
21
|
+
const match = RETRIES_TAG.exec(name);
|
|
22
|
+
if (match)
|
|
23
|
+
retries = Number(match[1]);
|
|
24
|
+
}
|
|
25
|
+
return retries;
|
|
26
|
+
}
|
|
11
27
|
function unique(items) {
|
|
12
28
|
return [...new Set(items)];
|
|
13
29
|
}
|
|
@@ -22,6 +38,7 @@ function createAnnotation(type, description) {
|
|
|
22
38
|
* - `@lock(name)` → `lock: 'name'`; `@lock(-exclusive-)` → built-in exclusive lock
|
|
23
39
|
* - `@annotation(type,description)` → `annotation: { type, description }`
|
|
24
40
|
* - `@skip()` / `@fixme()` / `@fail()` / `@slow()` → playwright modifier with optional description, e.g. `@fixme(JIRA-1)`
|
|
41
|
+
* - `@timeout(ms)` → test timeout; `@retries(n)` → consumed here, applied per feature (see `resolveRetries`)
|
|
25
42
|
*
|
|
26
43
|
* Note: gherkin tags may not contain whitespace, so write `@annotation(issue,JIRA-1)` without spaces.
|
|
27
44
|
*/
|
|
@@ -29,12 +46,21 @@ function resolveTestDetails(pickle) {
|
|
|
29
46
|
const tag = [];
|
|
30
47
|
const extraAnnotation = [];
|
|
31
48
|
const lock = [];
|
|
49
|
+
let timeout;
|
|
32
50
|
for (const name of unique(pickle.tags.map(item => item.name))) {
|
|
33
51
|
const lockMatch = LOCK_TAG.exec(name);
|
|
34
52
|
if (lockMatch) {
|
|
35
53
|
lock.push(lockMatch[1].trim());
|
|
36
54
|
continue;
|
|
37
55
|
}
|
|
56
|
+
const timeoutMatch = TIMEOUT_TAG.exec(name);
|
|
57
|
+
if (timeoutMatch) {
|
|
58
|
+
timeout = Number(timeoutMatch[1]);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (RETRIES_TAG.test(name)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
38
64
|
const annotationMatch = ANNOTATION_TAG.exec(name) ?? MODIFIER_TAG.exec(name);
|
|
39
65
|
if (annotationMatch) {
|
|
40
66
|
extraAnnotation.push(createAnnotation(annotationMatch[1].trim(), annotationMatch[2]));
|
|
@@ -48,5 +74,5 @@ function resolveTestDetails(pickle) {
|
|
|
48
74
|
{ type: 'tags', description: JSON.stringify(tag) },
|
|
49
75
|
...extraAnnotation
|
|
50
76
|
];
|
|
51
|
-
return { tag, annotation, lock: unique(lock) };
|
|
77
|
+
return { tag, annotation, lock: unique(lock), ...(timeout === undefined ? {} : { timeout }) };
|
|
52
78
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface DefinitionWithTimeout {
|
|
2
|
+
options?: {
|
|
3
|
+
timeout?: number;
|
|
4
|
+
};
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve timeout of a step or hook definition:
|
|
8
|
+
* `{ timeout }` option of the definition, otherwise value set by `setDefaultTimeout`.
|
|
9
|
+
* Returns `undefined` when no timeout applies (not set, or disabled with `-1`/`0`);
|
|
10
|
+
* the Playwright test timeout still applies in that case.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveTimeout(definition: DefinitionWithTimeout, defaultTimeout?: number): number | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Run definition code, failing with `message` if it does not complete within `timeout` milliseconds
|
|
15
|
+
*/
|
|
16
|
+
export declare function runWithTimeout<T>(code: () => T | Promise<T>, timeout: number | undefined, message: string): Promise<T>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveTimeout = resolveTimeout;
|
|
4
|
+
exports.runWithTimeout = runWithTimeout;
|
|
5
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
6
|
+
/**
|
|
7
|
+
* Resolve timeout of a step or hook definition:
|
|
8
|
+
* `{ timeout }` option of the definition, otherwise value set by `setDefaultTimeout`.
|
|
9
|
+
* Returns `undefined` when no timeout applies (not set, or disabled with `-1`/`0`);
|
|
10
|
+
* the Playwright test timeout still applies in that case.
|
|
11
|
+
*/
|
|
12
|
+
function resolveTimeout(definition, defaultTimeout) {
|
|
13
|
+
const timeout = definition.options?.timeout ?? defaultTimeout;
|
|
14
|
+
return timeout === undefined || timeout <= 0 ? undefined : timeout;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run definition code, failing with `message` if it does not complete within `timeout` milliseconds
|
|
18
|
+
*/
|
|
19
|
+
function runWithTimeout(code, timeout, message) {
|
|
20
|
+
const promise = Promise.resolve().then(code);
|
|
21
|
+
if (timeout === undefined)
|
|
22
|
+
return promise;
|
|
23
|
+
return (0, cucumber_1.wrapPromiseWithTimeout)(promise, timeout, message.replace('{timeout}', String(timeout)));
|
|
24
|
+
}
|
package/adapter/js/types.d.ts
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import type { GherkinDocument, Pickle } from '@cucumber/messages';
|
|
2
2
|
import type { TestType } from '@playwright/test';
|
|
3
|
+
import type { Readable } from 'node:stream';
|
|
3
4
|
export interface CucumberAdapterConfig {
|
|
5
|
+
/** Glob patterns for feature files. Can be overridden with `CUCUMBER_PATHS` env variable (comma separated). */
|
|
4
6
|
paths: string[];
|
|
7
|
+
/** Glob patterns for step definition files */
|
|
5
8
|
require?: string[];
|
|
9
|
+
/** Module names or paths (resolved from cwd) required before step definitions, e.g. `ts-node/register` */
|
|
6
10
|
requireModules?: string[];
|
|
11
|
+
/** Glob patterns for step definition files */
|
|
7
12
|
import?: string[];
|
|
13
|
+
/** Parameters available in world as `this.parameters` */
|
|
14
|
+
worldParameters?: unknown;
|
|
15
|
+
/** Cucumber tag expression to select scenarios. Combined with `CUCUMBER_TAGS` env variable using `and`. */
|
|
16
|
+
tags?: string;
|
|
17
|
+
/** Fail scenarios with pending steps (default `true`). When `false` such scenarios are marked as fixme. */
|
|
18
|
+
strict?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Location reported for steps: `'definition'` (default) points to the step definition code,
|
|
21
|
+
* `'feature'` points to the step line in the feature file
|
|
22
|
+
*/
|
|
23
|
+
stepLocation?: 'definition' | 'feature';
|
|
8
24
|
[key: string]: unknown;
|
|
9
25
|
}
|
|
26
|
+
export interface AttachOptions {
|
|
27
|
+
fileName?: string;
|
|
28
|
+
mediaType?: string;
|
|
29
|
+
}
|
|
30
|
+
export type AttachData = string | Buffer | Readable;
|
|
31
|
+
export type AttachFn = (data: AttachData, mediaTypeOrOptions?: string | AttachOptions) => Promise<void>;
|
|
10
32
|
export interface WorldOptions {
|
|
11
33
|
log?: (data: unknown) => void;
|
|
12
|
-
attach?:
|
|
13
|
-
fileName?: string;
|
|
14
|
-
mediaType?: string;
|
|
15
|
-
}) => Promise<void>;
|
|
34
|
+
attach?: AttachFn;
|
|
16
35
|
parameters?: unknown;
|
|
17
36
|
supportCodeLibrary?: SupportCodeLibrary;
|
|
18
37
|
config?: CucumberAdapterConfig;
|
|
@@ -21,13 +40,20 @@ export interface HookDefinition {
|
|
|
21
40
|
uri?: string;
|
|
22
41
|
line: number;
|
|
23
42
|
name?: string;
|
|
24
|
-
|
|
43
|
+
options?: {
|
|
44
|
+
timeout?: number;
|
|
45
|
+
};
|
|
46
|
+
code: (this: unknown, ...args: unknown[]) => unknown;
|
|
25
47
|
appliesToTestCase(testCase: Pickle): boolean;
|
|
26
48
|
}
|
|
27
49
|
export interface StepDefinitionMatch {
|
|
28
50
|
uri?: string;
|
|
29
51
|
line: number;
|
|
30
|
-
|
|
52
|
+
pattern?: string | RegExp;
|
|
53
|
+
options?: {
|
|
54
|
+
timeout?: number;
|
|
55
|
+
};
|
|
56
|
+
code: (this: unknown, ...args: unknown[]) => unknown;
|
|
31
57
|
matchesStepName(text: string): boolean;
|
|
32
58
|
getInvocationParameters(options: {
|
|
33
59
|
step: {
|
|
@@ -52,6 +78,9 @@ export interface WorldBase {
|
|
|
52
78
|
export interface SupportCodeLibrary {
|
|
53
79
|
World: new (options: WorldOptions) => WorldBase;
|
|
54
80
|
stepDefinitions: StepDefinitionMatch[];
|
|
81
|
+
parameterTypeRegistry: unknown;
|
|
82
|
+
/** set only when `setDefaultTimeout` is called */
|
|
83
|
+
defaultTimeout?: number;
|
|
55
84
|
beforeTestRunHookDefinitions: HookDefinition[];
|
|
56
85
|
afterTestRunHookDefinitions: HookDefinition[];
|
|
57
86
|
beforeTestCaseHookDefinitions: HookDefinition[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ITestCaseHookParameter, type ITestStepHookParameter } from '@cucumber/cucumber';
|
|
2
|
+
type StepPattern = string | RegExp;
|
|
3
|
+
type StepFunction<World> = (this: World, ...args: any[]) => unknown;
|
|
4
|
+
type HookFunction<World, Parameter> = (this: World, parameter: Parameter) => unknown;
|
|
5
|
+
export interface StepOptions {
|
|
6
|
+
timeout?: number;
|
|
7
|
+
wrapperOptions?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface HookOptions {
|
|
10
|
+
name?: string;
|
|
11
|
+
tags?: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface DefineStep<World> {
|
|
15
|
+
(pattern: StepPattern, code: StepFunction<World>): void;
|
|
16
|
+
(pattern: StepPattern, options: StepOptions, code: StepFunction<World>): void;
|
|
17
|
+
}
|
|
18
|
+
export interface DefineHook<World, Parameter> {
|
|
19
|
+
(code: HookFunction<World, Parameter>): void;
|
|
20
|
+
(tags: string, code: HookFunction<World, Parameter>): void;
|
|
21
|
+
(options: HookOptions, code: HookFunction<World, Parameter>): void;
|
|
22
|
+
}
|
|
23
|
+
export interface TypedSupportCode<World> {
|
|
24
|
+
Given: DefineStep<World>;
|
|
25
|
+
When: DefineStep<World>;
|
|
26
|
+
Then: DefineStep<World>;
|
|
27
|
+
defineStep: DefineStep<World>;
|
|
28
|
+
Before: DefineHook<World, ITestCaseHookParameter>;
|
|
29
|
+
After: DefineHook<World, ITestCaseHookParameter>;
|
|
30
|
+
BeforeStep: DefineHook<World, ITestStepHookParameter>;
|
|
31
|
+
AfterStep: DefineHook<World, ITestStepHookParameter>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get step and hook definition functions with `this` typed as the given world,
|
|
35
|
+
* so step definitions don't need `this: MyWorld` annotation
|
|
36
|
+
* @example
|
|
37
|
+
* const { Given, When, Before } = withWorld<MyWorld>();
|
|
38
|
+
* When('I open {string}', async function (url: string) {
|
|
39
|
+
* await this.page.goto(url); // `this` is MyWorld
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
export declare function withWorld<World>(): TypedSupportCode<World>;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withWorld = withWorld;
|
|
4
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
5
|
+
/**
|
|
6
|
+
* Get step and hook definition functions with `this` typed as the given world,
|
|
7
|
+
* so step definitions don't need `this: MyWorld` annotation
|
|
8
|
+
* @example
|
|
9
|
+
* const { Given, When, Before } = withWorld<MyWorld>();
|
|
10
|
+
* When('I open {string}', async function (url: string) {
|
|
11
|
+
* await this.page.goto(url); // `this` is MyWorld
|
|
12
|
+
* });
|
|
13
|
+
*/
|
|
14
|
+
function withWorld() {
|
|
15
|
+
return { Given: cucumber_1.Given, When: cucumber_1.When, Then: cucumber_1.Then, defineStep: cucumber_1.defineStep, Before: cucumber_1.Before, After: cucumber_1.After, BeforeStep: cucumber_1.BeforeStep, AfterStep: cucumber_1.AfterStep };
|
|
16
|
+
}
|
package/adapter/loader.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { CucumberAdapterConfig, Feature, SupportCodeLibrary } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Find files matching glob patterns; returns unique absolute paths in stable (sorted) order
|
|
4
|
+
*/
|
|
5
|
+
export declare function findFiles(patterns: string[]): string[];
|
|
2
6
|
export declare function loadFeatures(globPattern: string[]): Feature[];
|
|
3
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Require modules by name or path resolved from the current working directory (e.g. `ts-node/register`)
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadModules(modules: string[]): void;
|
|
11
|
+
export declare function loadStepDefinitions(globPattern: string[], requireModules?: string[]): SupportCodeLibrary;
|
|
4
12
|
export declare function load(config: CucumberAdapterConfig): {
|
|
5
13
|
features: Feature[];
|
|
6
14
|
supportCodeLibrary: SupportCodeLibrary;
|
package/adapter/loader.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findFiles = findFiles;
|
|
3
4
|
exports.loadFeatures = loadFeatures;
|
|
5
|
+
exports.loadModules = loadModules;
|
|
4
6
|
exports.loadStepDefinitions = loadStepDefinitions;
|
|
5
7
|
exports.load = load;
|
|
6
8
|
const node_fs_1 = require("node:fs");
|
|
7
9
|
const node_path_1 = require("node:path");
|
|
10
|
+
const node_module_1 = require("node:module");
|
|
8
11
|
const node_crypto_1 = require("node:crypto");
|
|
9
12
|
const gherkin_1 = require("@cucumber/gherkin");
|
|
10
13
|
const cucumber_1 = require("@cucumber/cucumber");
|
|
11
|
-
const glob_1 = require("glob");
|
|
12
14
|
const PlaywrightWorld_1 = require("./PlaywrightWorld");
|
|
13
15
|
const uuidFn = () => (0, node_crypto_1.randomUUID)();
|
|
14
16
|
const builder = new gherkin_1.AstBuilder(uuidFn);
|
|
@@ -28,36 +30,54 @@ function duplicates(tests) {
|
|
|
28
30
|
return item;
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Find files matching glob patterns; returns unique absolute paths in stable (sorted) order
|
|
35
|
+
*/
|
|
36
|
+
function findFiles(patterns) {
|
|
37
|
+
if (patterns.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
const files = (0, node_fs_1.globSync)(patterns).map(file => (0, node_path_1.resolve)(file));
|
|
40
|
+
return [...new Set(files)].sort();
|
|
41
|
+
}
|
|
31
42
|
function loadFeatures(globPattern) {
|
|
32
|
-
const files = (
|
|
33
|
-
return files.map(
|
|
34
|
-
const filePath = (0, node_path_1.resolve)(file);
|
|
43
|
+
const files = findFiles(globPattern);
|
|
44
|
+
return files.map(filePath => {
|
|
35
45
|
const gherkinDocument = parser.parse((0, node_fs_1.readFileSync)(filePath, 'utf-8'));
|
|
36
46
|
return {
|
|
37
47
|
feature: gherkinDocument.feature?.name,
|
|
38
48
|
uri: filePath,
|
|
39
49
|
gherkinDocument,
|
|
40
|
-
tests: duplicates((0, gherkin_1.compile)(gherkinDocument,
|
|
50
|
+
tests: duplicates((0, gherkin_1.compile)(gherkinDocument, (0, node_path_1.relative)(process.cwd(), filePath), uuidFn))
|
|
41
51
|
};
|
|
42
52
|
});
|
|
43
53
|
}
|
|
44
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Require modules by name or path resolved from the current working directory (e.g. `ts-node/register`)
|
|
56
|
+
*/
|
|
57
|
+
function loadModules(modules) {
|
|
58
|
+
const cwdRequire = (0, node_module_1.createRequire)((0, node_path_1.join)(process.cwd(), 'index.js'));
|
|
59
|
+
for (const name of modules) {
|
|
60
|
+
cwdRequire(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function loadStepDefinitions(globPattern, requireModules = []) {
|
|
45
64
|
cucumber_1.supportCodeLibraryBuilder.reset(process.cwd(), uuidFn);
|
|
65
|
+
// cucumber defaults to 5000ms; apply a default timeout only when set explicitly with setDefaultTimeout
|
|
66
|
+
cucumber_1.supportCodeLibraryBuilder.defaultTimeout = undefined;
|
|
46
67
|
cucumber_1.supportCodeLibraryBuilder.methods.setWorldConstructor(PlaywrightWorld_1.PlaywrightWorld);
|
|
47
|
-
|
|
48
|
-
for (const
|
|
49
|
-
const filePath = (0, node_path_1.resolve)(file);
|
|
68
|
+
loadModules(requireModules);
|
|
69
|
+
for (const filePath of findFiles(globPattern)) {
|
|
50
70
|
require(filePath);
|
|
51
71
|
}
|
|
52
72
|
return cucumber_1.supportCodeLibraryBuilder.finalize();
|
|
53
73
|
}
|
|
54
74
|
function load(config) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
]);
|
|
75
|
+
const envPaths = process.env.CUCUMBER_PATHS
|
|
76
|
+
?.split(',')
|
|
77
|
+
.map(path => path.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
const features = loadFeatures(envPaths?.length ? envPaths : config.paths);
|
|
80
|
+
const supportCodeLibrary = loadStepDefinitions([...(config.require ?? []), ...(config.import ?? [])], config.requireModules ?? []);
|
|
61
81
|
return { features, supportCodeLibrary };
|
|
62
82
|
}
|
|
63
83
|
//# sourceMappingURL=loader.js.map
|
package/adapter/loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|