@qavajs/playwright-runner-adapter 2.3.3 → 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.
Files changed (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +198 -11
  3. package/adapter/PlaywrightWorld.d.ts +8 -6
  4. package/adapter/PlaywrightWorld.js +21 -8
  5. package/adapter/PlaywrightWorld.js.map +1 -1
  6. package/adapter/Template.d.ts +13 -2
  7. package/adapter/Template.js +57 -8
  8. package/adapter/Template.js.map +1 -1
  9. package/adapter/cucumberAdapter.d.ts +7 -0
  10. package/adapter/cucumberAdapter.js +254 -114
  11. package/adapter/cucumberAdapter.js.map +1 -1
  12. package/adapter/filter.d.ts +8 -0
  13. package/adapter/filter.js +27 -1
  14. package/adapter/filter.js.map +1 -1
  15. package/adapter/gherkin.d.ts +45 -0
  16. package/adapter/gherkin.js +155 -0
  17. package/adapter/gherkin.js.map +1 -0
  18. package/adapter/index.d.ts +2 -0
  19. package/adapter/index.js +2 -0
  20. package/adapter/index.js.map +1 -1
  21. package/adapter/js/PlaywrightWorld.d.ts +8 -6
  22. package/adapter/js/PlaywrightWorld.js +21 -8
  23. package/adapter/js/Template.d.ts +13 -2
  24. package/adapter/js/Template.js +57 -8
  25. package/adapter/js/cucumberAdapter.d.ts +7 -0
  26. package/adapter/js/cucumberAdapter.js +254 -114
  27. package/adapter/js/filter.d.ts +8 -0
  28. package/adapter/js/filter.js +27 -1
  29. package/adapter/js/gherkin.d.ts +45 -0
  30. package/adapter/js/gherkin.js +154 -0
  31. package/adapter/js/index.d.ts +2 -0
  32. package/adapter/js/index.js +2 -0
  33. package/adapter/js/loader.d.ts +9 -1
  34. package/adapter/js/loader.js +35 -15
  35. package/adapter/js/testDetails.d.ts +28 -0
  36. package/adapter/js/testDetails.js +78 -0
  37. package/adapter/js/timeout.d.ts +17 -0
  38. package/adapter/js/timeout.js +24 -0
  39. package/adapter/js/types.d.ts +35 -6
  40. package/adapter/js/withWorld.d.ts +43 -0
  41. package/adapter/js/withWorld.js +16 -0
  42. package/adapter/loader.d.ts +9 -1
  43. package/adapter/loader.js +35 -15
  44. package/adapter/loader.js.map +1 -1
  45. package/adapter/testDetails.d.ts +28 -0
  46. package/adapter/testDetails.js +79 -0
  47. package/adapter/testDetails.js.map +1 -0
  48. package/adapter/timeout.d.ts +17 -0
  49. package/adapter/timeout.js +25 -0
  50. package/adapter/timeout.js.map +1 -0
  51. package/adapter/types.d.ts +35 -6
  52. package/adapter/withWorld.d.ts +43 -0
  53. package/adapter/withWorld.js +17 -0
  54. package/adapter/withWorld.js.map +1 -0
  55. package/package.json +11 -9
@@ -0,0 +1,155 @@
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
+ }
155
+ //# sourceMappingURL=gherkin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gherkin.js","sourceRoot":"","sources":["../src/gherkin.ts"],"names":[],"mappings":"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"}
@@ -3,3 +3,5 @@ export * from './filter';
3
3
  export * from './Template';
4
4
  export * from '@cucumber/cucumber';
5
5
  export * from './cucumberAdapter';
6
+ export * from './testDetails';
7
+ export * from './withWorld';
package/adapter/index.js CHANGED
@@ -19,4 +19,6 @@ __exportStar(require("./filter"), exports);
19
19
  __exportStar(require("./Template"), exports);
20
20
  __exportStar(require("@cucumber/cucumber"), exports);
21
21
  __exportStar(require("./cucumberAdapter"), exports);
22
+ __exportStar(require("./testDetails"), exports);
23
+ __exportStar(require("./withWorld"), exports);
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2CAAyB;AACzB,6CAA2B;AAC3B,qDAAmC;AACnC,oDAAkC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2CAAyB;AACzB,6CAA2B;AAC3B,qDAAmC;AACnC,oDAAkC;AAAA,gDAA8B;AAChE,8CAA4B"}
@@ -1,18 +1,20 @@
1
1
  import { APIRequestContext, Browser, BrowserContext, Page } from '@playwright/test';
2
- import type { CucumberAdapterConfig, SupportCodeLibrary, WorldOptions } from './types';
2
+ import type { AttachFn, CucumberAdapterConfig, SupportCodeLibrary, WorldOptions } from './types';
3
3
  export declare class TestWorld {
4
4
  log: (data: unknown) => void;
5
- attach: (data: unknown, details?: {
6
- fileName?: string;
7
- mediaType?: string;
8
- }) => Promise<void>;
5
+ attach: AttachFn;
9
6
  parameters: unknown;
10
7
  config: CucumberAdapterConfig;
11
8
  supportCodeLibrary: SupportCodeLibrary;
12
9
  test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
13
10
  expect: import("@playwright/test").Expect<{}>;
14
11
  constructor(options: WorldOptions);
15
- executeStep(this: TestWorld, text: string, extraParam?: unknown): Promise<void>;
12
+ /**
13
+ * Execute step by its text
14
+ * @param text - step text
15
+ * @param argument - doc string (`string`), data table (`string[][]` or `DataTable`) or any extra parameter
16
+ */
17
+ executeStep(this: TestWorld, text: string, argument?: unknown): Promise<void>;
16
18
  }
17
19
  export declare class PlaywrightWorld extends TestWorld {
18
20
  page: Page;
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PlaywrightWorld = exports.TestWorld = void 0;
4
4
  const test_1 = require("@playwright/test");
5
+ const gherkin_1 = require("./gherkin");
6
+ const timeout_1 = require("./timeout");
5
7
  class TestWorld {
6
8
  log;
7
9
  attach;
@@ -17,7 +19,12 @@ class TestWorld {
17
19
  this.supportCodeLibrary = options.supportCodeLibrary;
18
20
  this.config = options.config;
19
21
  }
20
- async executeStep(text, extraParam) {
22
+ /**
23
+ * Execute step by its text
24
+ * @param text - step text
25
+ * @param argument - doc string (`string`), data table (`string[][]` or `DataTable`) or any extra parameter
26
+ */
27
+ async executeStep(text, argument) {
21
28
  const steps = this.supportCodeLibrary.stepDefinitions
22
29
  .filter(s => s.matchesStepName(text));
23
30
  if (steps.length === 0)
@@ -27,13 +34,19 @@ class TestWorld {
27
34
  throw new Error(`Step "${text}" matches multiple step definitions:\n${locations}`);
28
35
  }
29
36
  const step = steps[0];
30
- const { parameters } = await step.getInvocationParameters({ step: { text }, world: this });
31
- try {
32
- await step.code.apply(this, [...parameters, extraParam]);
33
- }
34
- catch (err) {
35
- throw new Error(`Step failed: ${text}`, { cause: err });
36
- }
37
+ const { argument: pickleArgument, extra } = (0, gherkin_1.toPickleArgument)(argument);
38
+ const location = step.uri ? { file: step.uri, line: step.line, column: 1 } : undefined;
39
+ // reported as a nested step, so failures inside composite steps point to the exact inner step
40
+ await this.test.step((0, gherkin_1.stepTitle)({ text, argument: pickleArgument }), async () => {
41
+ const { parameters } = await step.getInvocationParameters({ step: { text, argument: pickleArgument }, world: this });
42
+ const args = extra === undefined ? parameters : [...parameters, extra];
43
+ try {
44
+ await (0, timeout_1.runWithTimeout)(() => step.code.apply(this, args), (0, timeout_1.resolveTimeout)(step, this.supportCodeLibrary.defaultTimeout), `Step '${text}' timed out after {timeout} ms\n at ${step.uri}:${step.line}`);
45
+ }
46
+ catch (err) {
47
+ throw new Error(`Step failed: ${text}`, { cause: err });
48
+ }
49
+ }, { location });
37
50
  }
38
51
  }
39
52
  exports.TestWorld = TestWorld;
@@ -1,10 +1,21 @@
1
+ export interface TemplateStep {
2
+ text: string;
3
+ argument?: string | string[][];
4
+ }
5
+ /**
6
+ * Parse template text into steps. Supports data tables (`| a | b |` lines)
7
+ * and doc strings (`"""` or ``` blocks) placed after a step.
8
+ */
9
+ export declare function parseTemplate(scenario: string): TemplateStep[];
1
10
  /**
2
11
  * Define template step
3
- * @param {string} scenario - multiline string with steps
12
+ * @param {string} scenario - multiline string with steps; steps can have data tables and doc strings
4
13
  * @example
5
14
  * When('I click {string} and verify {string}', Template((locator, expected) => `
6
15
  * I click '${locator}'
7
16
  * I expect '${locator} > Value' to equal '${expected}'
17
+ * I fill form
18
+ * | name | John |
8
19
  * `));
9
20
  */
10
- export declare function Template(scenario: (...args: unknown[]) => string): (...args: unknown[]) => string;
21
+ export declare function Template(scenario: (...args: any[]) => string): (...args: unknown[]) => string;
@@ -1,25 +1,74 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTemplate = parseTemplate;
3
4
  exports.Template = Template;
5
+ const DOC_STRING_DELIMITERS = ['"""', '```'];
6
+ function parseRow(line) {
7
+ const content = line.replace(/^\|/, '').replace(/(?<!\\)\|$/, '');
8
+ return content
9
+ .split(/(?<!\\)\|/)
10
+ .map(cell => cell.trim().replace(/\\\|/g, '|'));
11
+ }
12
+ function stripIndent(line, indent) {
13
+ const leading = line.length - line.trimStart().length;
14
+ return line.slice(Math.min(indent, leading));
15
+ }
16
+ /**
17
+ * Parse template text into steps. Supports data tables (`| a | b |` lines)
18
+ * and doc strings (`"""` or ``` blocks) placed after a step.
19
+ */
20
+ function parseTemplate(scenario) {
21
+ const lines = scenario.split('\n');
22
+ const steps = [];
23
+ const previousStep = (line) => {
24
+ const step = steps.at(-1);
25
+ if (!step)
26
+ throw new Error(`Template argument "${line.trim()}" must follow a step`);
27
+ return step;
28
+ };
29
+ for (let i = 0; i < lines.length; i++) {
30
+ const line = lines[i];
31
+ const trimmed = line.trim();
32
+ if (!trimmed)
33
+ continue;
34
+ const delimiter = DOC_STRING_DELIMITERS.find(d => trimmed.startsWith(d));
35
+ if (delimiter) {
36
+ const step = previousStep(line);
37
+ const indent = line.indexOf(delimiter);
38
+ const content = [];
39
+ for (i++; i < lines.length && lines[i].trim() !== delimiter; i++) {
40
+ content.push(stripIndent(lines[i], indent));
41
+ }
42
+ step.argument = content.join('\n');
43
+ continue;
44
+ }
45
+ if (trimmed.startsWith('|')) {
46
+ const step = previousStep(line);
47
+ if (!Array.isArray(step.argument))
48
+ step.argument = [];
49
+ step.argument.push(parseRow(trimmed));
50
+ continue;
51
+ }
52
+ steps.push({ text: trimmed });
53
+ }
54
+ return steps;
55
+ }
4
56
  /**
5
57
  * Define template step
6
- * @param {string} scenario - multiline string with steps
58
+ * @param {string} scenario - multiline string with steps; steps can have data tables and doc strings
7
59
  * @example
8
60
  * When('I click {string} and verify {string}', Template((locator, expected) => `
9
61
  * I click '${locator}'
10
62
  * I expect '${locator} > Value' to equal '${expected}'
63
+ * I fill form
64
+ * | name | John |
11
65
  * `));
12
66
  */
13
67
  function Template(scenario) {
14
68
  return new Proxy(scenario, {
15
69
  apply: async function (template, world, args) {
16
- const scenario = template(...args);
17
- const steps = scenario
18
- .split('\n')
19
- .map(step => step.trim())
20
- .filter(Boolean);
21
- for (const step of steps) {
22
- await world.executeStep(step);
70
+ for (const step of parseTemplate(template(...args))) {
71
+ await world.executeStep(step.text, step.argument);
23
72
  }
24
73
  },
25
74
  });
@@ -1,2 +1,9 @@
1
+ import type { Pickle } from '@cucumber/messages';
1
2
  import type { CucumberAdapterConfig } from './types';
3
+ /**
4
+ * Create scenario filter from tag expressions keyed by their source (used in error messages),
5
+ * e.g. `{ 'tags option': config.tags, 'CUCUMBER_TAGS env variable': process.env.CUCUMBER_TAGS }`.
6
+ * All expressions must match.
7
+ */
8
+ export declare function createTagFilter(sources: Record<string, string | undefined>): (pickle: Pickle) => boolean;
2
9
  export declare function defineConfig(config: CucumberAdapterConfig): void;