@sun-asterisk/sungen 3.2.22-beta.1 → 3.2.22
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/dist/generators/test-generator/adapters/appium/templates/imports.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +23 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-row-column.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +10 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-with-alert-action.hbs +7 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +14 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +17 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +13 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +26 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +24 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-variable-assertion.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +10 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +21 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-variable-assertion.hbs +10 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/row-scoped-column-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +23 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/storage-key-assertion.hbs +2 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/tab-order-assertion.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-dialog-heading-assertion.hbs +10 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +17 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +13 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-table-refresh.hbs +13 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/drag-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -5
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +1 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +29 -8
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +20 -1
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +19 -0
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +45 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +150 -19
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +36 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +33 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +78 -24
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +2 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +61 -12
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +22 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -1
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +104 -29
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +104 -26
- package/dist/orchestrator/templates/specs-db.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-db.js +18 -5
- package/dist/orchestrator/templates/specs-db.js.map +1 -1
- package/dist/orchestrator/templates/specs-db.ts +19 -5
- package/package.json +3 -3
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +23 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-row-column.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +10 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-with-alert-action.hbs +7 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +14 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row-nth.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +17 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +13 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +26 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +24 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-variable-assertion.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-dialog-heading-assertion.hbs +10 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +21 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-variable-assertion.hbs +10 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/row-scoped-column-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +23 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/storage-key-assertion.hbs +2 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/tab-order-assertion.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-dialog-heading-assertion.hbs +10 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +17 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +13 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-table-refresh.hbs +13 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/drag-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -5
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +1 -0
- package/src/generators/test-generator/code-generator.ts +34 -9
- package/src/generators/test-generator/diagnostics.ts +20 -1
- package/src/generators/test-generator/patterns/index.ts +165 -24
- package/src/generators/test-generator/patterns/types.ts +35 -0
- package/src/generators/test-generator/step-mapper.ts +84 -23
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +2 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +4 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +61 -12
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +22 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +5 -1
- package/src/orchestrator/templates/specs-api.ts +104 -26
- package/src/orchestrator/templates/specs-db.ts +19 -5
|
@@ -2,6 +2,15 @@ import { $, $$, expect, driver, browser } from '@wdio/globals';
|
|
|
2
2
|
{{#if runtimeData}}
|
|
3
3
|
import { TestDataLoader } from '{{basePath}}/test-data';
|
|
4
4
|
{{/if}}
|
|
5
|
+
{{#if needsDb}}
|
|
6
|
+
import { db } from '{{basePath}}/db';
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#if needsApi}}
|
|
9
|
+
import { api } from '{{basePath}}/api';
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if needsMock}}
|
|
12
|
+
import { mock } from '{{basePath}}/mock';
|
|
13
|
+
{{/if}}
|
|
5
14
|
|
|
6
15
|
// This file is auto-generated from Gherkin feature files
|
|
7
16
|
// DO NOT EDIT MANUALLY - changes will be overwritten
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
+
{{#if casesDataset}}
|
|
2
|
+
for (const __row of testData.cases('{{casesDataset}}')) {
|
|
3
|
+
{{#if tags}}
|
|
4
|
+
it('{{escapeQuotes scenarioName}} — ' + __row.__label + ' [{{{tags}}}]', async () => {
|
|
5
|
+
{{else}}
|
|
6
|
+
it('{{escapeQuotes scenarioName}} — ' + __row.__label, async () => {
|
|
7
|
+
{{/if}}
|
|
8
|
+
const rowData = testData.withRow(__row);
|
|
9
|
+
{{#each steps}}
|
|
10
|
+
{{#if comment}}
|
|
11
|
+
// {{comment}}
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{code}}
|
|
14
|
+
{{/each}}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
{{else}}
|
|
18
|
+
{{#if tags}}
|
|
19
|
+
it('{{escapeQuotes scenarioName}} [{{{tags}}}]', async () => {
|
|
20
|
+
{{else}}
|
|
1
21
|
it('{{escapeQuotes scenarioName}}', async () => {
|
|
22
|
+
{{/if}}
|
|
2
23
|
{{#each steps}}
|
|
3
24
|
{{#if comment}}
|
|
4
25
|
// {{comment}}
|
|
5
26
|
{{/if}}
|
|
6
27
|
{{code}}
|
|
7
28
|
{{/each}}
|
|
8
|
-
});
|
|
29
|
+
});
|
|
30
|
+
{{/if}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{{!-- Table row/column capture targets HTML <table> roles — not available on native mobile. --}}
|
|
2
|
+
throw new Error("[sungen] 'remember column in table row' targets HTML table roles and is not supported on the appium adapter. Use a list / accessibility-id read instead, or tag this scenario @manual.");
|
package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{!-- Capture (mobile): multi-source read mirrors contain-text-assertion (Android @text /
|
|
2
|
+
@content-desc, iOS label/value). capture: 'inputValue' means the input's OWN value —
|
|
3
|
+
prioritize iOS `value` and Android's raw `text` attribute over the label/content-desc. --}}
|
|
4
|
+
{
|
|
5
|
+
const __el = await {{> locator}};
|
|
6
|
+
const __sources = driver.isIOS
|
|
7
|
+
? {{#if (eq capture 'inputValue')}}[await __el.getAttribute('value'), await __el.getAttribute('label')]{{else}}[await __el.getAttribute('label'), await __el.getAttribute('value')]{{/if}}
|
|
8
|
+
: [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
9
|
+
testData.set('{{escapeQuotes varName}}', String(__sources.find(s => s && String(s).trim() !== '') ?? '').trim());
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{{!-- Native alert (mobile): unlike web (handler set BEFORE the trigger), mobile alerts appear
|
|
2
|
+
AFTER the triggering action — click first, then best-effort accept/dismiss (ignore if no
|
|
3
|
+
alert appears; mirrors alert-accept-action.hbs / alert-dismiss-action.hbs). --}}
|
|
4
|
+
{
|
|
5
|
+
await {{> locator}}.click();
|
|
6
|
+
try { await driver.{{#if (eq dialogAction 'dismiss')}}dismissAlert{{else}}acceptAlert{{/if}}(); } catch { /* no native alert present */ }
|
|
7
|
+
}
|
package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
{{!-- Drag the source element onto the target. WebdriverIO dragAndDrop drives W3C touch actions.
|
|
2
|
-
|
|
1
|
+
{{!-- Drag the source element onto the target. WebdriverIO dragAndDrop drives W3C touch actions.
|
|
2
|
+
|
|
3
|
+
The TARGET selector is rendered by the canonical `appium-selector` partial, NOT by a switch
|
|
4
|
+
of its own. This file used to hand-roll one covering 5 of the 13 strategies that partial
|
|
5
|
+
knows, and its default branch preferred `targetValue` over `targetName` — backwards for the
|
|
6
|
+
`role` strategy, where the resolver puts the generic role type in `value` ('button') and the
|
|
7
|
+
real accessible label in `name` ('Submit'). Since `role` is the DEFAULT strategy for buttons,
|
|
8
|
+
links and headings, a drag onto a role-resolved target aimed at `~button`.
|
|
9
|
+
It was invisible until C3: before that, the target FIELDS were never passed at all (only a
|
|
10
|
+
pre-built Playwright locator string), so the switch fell through with everything undefined
|
|
11
|
+
and emitted `$('~')` — broken, but obviously so. C3 populated the fields and turned an
|
|
12
|
+
obvious break into a plausible-looking wrong one. Delegating removes the second copy of a
|
|
13
|
+
mapping that only ever needed one. --}}
|
|
14
|
+
await (await {{> locator}}).dragAndDrop(await $('{{> appium-selector strategy=targetStrategy value=targetValue name=targetName}}'));
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{{!-- Positional table row actions target HTML <table> roles — not available on native mobile. --}}
|
|
2
|
+
throw new Error("[sungen] 'table action in row (nth)' targets HTML table roles and is not supported on the appium adapter. Use list / accessibility-id steps, or tag this scenario @manual.");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{{!-- All-contain (mobile): $$ over the base selector, per-OS multi-source text read (same idiom
|
|
2
|
+
as contain-text-assertion), substring check on every match — no CSS filter() combinator
|
|
3
|
+
exists on Appium, so this loop replaces it. --}}
|
|
4
|
+
{
|
|
5
|
+
const __items_{{stepCounter}} = await $$({{> appium-selector-expr}});
|
|
6
|
+
if (__items_{{stepCounter}}.length === 0) throw new Error('[sungen] Expected at least one [{{escapeQuotes selectorRef}}]');
|
|
7
|
+
const __expected_{{stepCounter}} = String('{{escapeQuotes expectedText}}').trim();
|
|
8
|
+
for (const __el_{{stepCounter}} of __items_{{stepCounter}}) {
|
|
9
|
+
const __sources_{{stepCounter}} = driver.isIOS
|
|
10
|
+
? [await __el_{{stepCounter}}.getAttribute('label'), await __el_{{stepCounter}}.getAttribute('value')]
|
|
11
|
+
: [await __el_{{stepCounter}}.getText(), await __el_{{stepCounter}}.getAttribute('content-desc')];
|
|
12
|
+
const __actual_{{stepCounter}} = String(__sources_{{stepCounter}}.find(s => s && String(s).trim() !== '') ?? '').trim();
|
|
13
|
+
if (!__actual_{{stepCounter}}.includes(__expected_{{stepCounter}})) {
|
|
14
|
+
throw new Error(`[sungen] Expected [{{escapeQuotes selectorRef}}] text containing "${__expected_{{stepCounter}} }" but found "${__actual_{{stepCounter}} }"`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{!-- Q2 all-card structural assertion (mobile): every container element must hold the child.
|
|
2
|
+
WebdriverIO's $$() returns a live element ARRAY, not a lazy Locator — there is no `.nth()`
|
|
3
|
+
(that is Playwright's API). Enumerate by index and chain off the array element itself via
|
|
4
|
+
`.$()` (WDIO findElementFromElement). --}}
|
|
5
|
+
{
|
|
6
|
+
const {{containerVar}} = await $$('{{> appium-selector}}');
|
|
7
|
+
if ({{containerVar}}.length === 0) {
|
|
8
|
+
throw new Error('[sungen] Expected at least one [{{escapeQuotes selectorRef}}]');
|
|
9
|
+
}
|
|
10
|
+
for (let {{containerIndexVar}} = 0; {{containerIndexVar}} < {{containerVar}}.length; {{containerIndexVar}}++) {
|
|
11
|
+
await expect({{#with child}}{{> locator parentLocator=(add (add (add ../containerVar "[") ../containerIndexVar) "]")}}{{/with}}).toBeDisplayed();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{{!-- Filtered disabled (mobile): $$ over the base selector, per-OS text read matched against
|
|
2
|
+
dataValue — no CSS filter() combinator exists on Appium, so the match is hand-rolled.
|
|
3
|
+
Assert the matching element is not enabled.
|
|
4
|
+
|
|
5
|
+
The `__found` guard is load-bearing, NOT defensive noise. Playwright's twin
|
|
6
|
+
(`expect(locator.filter(...)).toBeDisabled()`) FAILS when the filtered locator matches
|
|
7
|
+
nothing, because a Playwright locator re-queries and times out. WDIO's `$$()` returns an
|
|
8
|
+
already-resolved ARRAY, so an assertion placed only inside the match branch simply never
|
|
9
|
+
runs when nothing matches — the scenario then passes having asserted NOTHING. That safety
|
|
10
|
+
property comes free on web and has to be reproduced by hand here.
|
|
11
|
+
(`hidden-with-filter-assertion` deliberately has no such guard: Playwright's `toBeHidden()`
|
|
12
|
+
also passes on a zero-match locator, since "not attached" counts as hidden. `toBeDisabled()`
|
|
13
|
+
has no such exemption — absent is not disabled.) --}}
|
|
14
|
+
{
|
|
15
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
16
|
+
const __items = await $$({{> appium-selector-expr}});
|
|
17
|
+
let __found = false;
|
|
18
|
+
for (const __el of __items) {
|
|
19
|
+
const __sources = driver.isIOS
|
|
20
|
+
? [await __el.getAttribute('label'), await __el.getAttribute('value')]
|
|
21
|
+
: [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
22
|
+
const __actual = String(__sources.find(s => s && String(s).trim() !== '') ?? '').trim();
|
|
23
|
+
if (__actual === __expected) { __found = true; await expect(__el).not.toBeEnabled(); }
|
|
24
|
+
}
|
|
25
|
+
if (!__found) throw new Error(`[sungen] No element with text "${__expected}" to assert disabled — the assertion never ran`);
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{{!-- Role variable, disabled (mobile best-effort): mirrors hidden-with-role-variable-assertion —
|
|
2
|
+
a static name scopes the search via the role→accessibility-id mapping in
|
|
3
|
+
appium-selector.hbs, then dataValue filters by per-OS text within that scope; with no name,
|
|
4
|
+
search directly by dataValue text. Assert the matching element(s) are not enabled. --}}
|
|
5
|
+
{{#if name}}
|
|
6
|
+
{
|
|
7
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
8
|
+
const __items = await $$('~{{escapeQuotes name}}');
|
|
9
|
+
let __found = false;
|
|
10
|
+
for (const __el of __items) {
|
|
11
|
+
const __s = driver.isIOS ? [await __el.getAttribute('label'), await __el.getAttribute('value')] : [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
12
|
+
if (String(__s.find(s => s && String(s).trim() !== '') ?? '').trim() === __expected) { __found = true; await expect(__el).not.toBeEnabled(); }
|
|
13
|
+
}
|
|
14
|
+
{{!-- Guard: with no match the loop body never runs, so the step would pass having asserted
|
|
15
|
+
nothing. See disabled-with-filter-assertion for why `disabled` needs this and `hidden` does not. --}}
|
|
16
|
+
if (!__found) throw new Error(`[sungen] No element with text "${__expected}" to assert disabled — the assertion never ran`);
|
|
17
|
+
}
|
|
18
|
+
{{else}}
|
|
19
|
+
{
|
|
20
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
21
|
+
const __sel = driver.isIOS ? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"` : `android=new UiSelector().text("${__expected}")`;
|
|
22
|
+
await expect($(__sel)).not.toBeEnabled();
|
|
23
|
+
}
|
|
24
|
+
{{/if}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{{!-- Disabled by value (mobile): search directly for an element whose text/label/value equals
|
|
2
|
+
dataValue (same idiom as label-value-assertion) and assert it is not enabled. --}}
|
|
3
|
+
{
|
|
4
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
5
|
+
const __sel = driver.isIOS
|
|
6
|
+
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
7
|
+
: `android=new UiSelector().text("${__expected}")`;
|
|
8
|
+
await expect($(__sel)).not.toBeEnabled();
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{!-- Dialog heading, hidden (mobile best-effort): mirrors visible-dialog-heading-assertion —
|
|
2
|
+
search for the heading text directly (no native "dialog" role to scope to) and assert it
|
|
3
|
+
is not displayed. --}}
|
|
4
|
+
{
|
|
5
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
6
|
+
const __sel = driver.isIOS
|
|
7
|
+
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
8
|
+
: `android=new UiSelector().text("${__expected}")`;
|
|
9
|
+
await expect($(__sel)).not.toBeDisplayed();
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{{!-- Filtered hidden (mobile): $$ over the base selector, per-OS text read (contain-text-assertion
|
|
2
|
+
idiom) matched against dataValue — no CSS filter() combinator exists on Appium, so the match
|
|
3
|
+
is hand-rolled. Assert none of the matching elements is displayed. --}}
|
|
4
|
+
{
|
|
5
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
6
|
+
const __items = await $$({{> appium-selector-expr}});
|
|
7
|
+
for (const __el of __items) {
|
|
8
|
+
const __sources = driver.isIOS
|
|
9
|
+
? [await __el.getAttribute('label'), await __el.getAttribute('value')]
|
|
10
|
+
: [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
11
|
+
const __actual = String(__sources.find(s => s && String(s).trim() !== '') ?? '').trim();
|
|
12
|
+
if (__actual === __expected) await expect(__el).not.toBeDisplayed();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{!-- Role variable, hidden (mobile best-effort): role has no native-mobile analog. A static name
|
|
2
|
+
scopes the search via the role→accessibility-id mapping in appium-selector.hbs, then
|
|
3
|
+
dataValue filters by per-OS text within that scope; with no name, search directly by
|
|
4
|
+
dataValue text like hidden-with-variable-assertion. Assert none of the matches is
|
|
5
|
+
displayed. --}}
|
|
6
|
+
{{#if name}}
|
|
7
|
+
{
|
|
8
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
9
|
+
const __items = await $$('~{{escapeQuotes name}}');
|
|
10
|
+
for (const __el of __items) {
|
|
11
|
+
const __s = driver.isIOS ? [await __el.getAttribute('label'), await __el.getAttribute('value')] : [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
12
|
+
if (String(__s.find(s => s && String(s).trim() !== '') ?? '').trim() === __expected) await expect(__el).not.toBeDisplayed();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
{{else}}
|
|
16
|
+
{
|
|
17
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
18
|
+
const __sel = driver.isIOS ? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"` : `android=new UiSelector().text("${__expected}")`;
|
|
19
|
+
await expect($(__sel)).not.toBeDisplayed();
|
|
20
|
+
}
|
|
21
|
+
{{/if}}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{!-- Hidden by value (mobile): no base selector to filter — search directly for an element
|
|
2
|
+
whose text/label/value equals dataValue (same idiom as label-value-assertion) and assert
|
|
3
|
+
it is not displayed. --}}
|
|
4
|
+
{
|
|
5
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
6
|
+
const __sel = driver.isIOS
|
|
7
|
+
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
8
|
+
: `android=new UiSelector().text("${__expected}")`;
|
|
9
|
+
await expect($(__sel)).not.toBeDisplayed();
|
|
10
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{{!-- Row-scoped column assertions target HTML <table> ARIA roles — not available on native mobile. --}}
|
|
2
|
+
throw new Error("[sungen] row-scoped column assertion targets HTML table roles and is not supported on the appium adapter. Use list / accessibility-id assertions, or tag this scenario @manual.");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{{!-- State-with-filter (mobile): $$ over the base selector, per-OS text read (hand-rolled — no
|
|
2
|
+
CSS filter() combinator on Appium) matched against dataValue, then assert the state on the
|
|
3
|
+
match. checked/selected map to the composite-row attribute (mirrors checked-assertion);
|
|
4
|
+
empty is a text read; enabled/focused use direct WDIO matchers. A state with no
|
|
5
|
+
native-mobile analog (sorted*, aria-busy loading) fails loud, matching sorted-assertion's
|
|
6
|
+
house style. --}}
|
|
7
|
+
{
|
|
8
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
9
|
+
const __items = await $$({{> appium-selector-expr}});
|
|
10
|
+
let __el: WebdriverIO.Element | undefined;
|
|
11
|
+
for (const __c of __items) {
|
|
12
|
+
const __s = driver.isIOS ? [await __c.getAttribute('label'), await __c.getAttribute('value')] : [await __c.getText(), await __c.getAttribute('content-desc')];
|
|
13
|
+
if (String(__s.find(s => s && String(s).trim() !== '') ?? '').trim() === __expected) { __el = __c; break; }
|
|
14
|
+
}
|
|
15
|
+
if (!__el) throw new Error(`[sungen] No match with text "${__expected}" to assert state on`);
|
|
16
|
+
{{#if (or selectedProbe (eq stateAssertion "toBeChecked()"))}}await expect(__el).toHaveAttribute(driver.isIOS ? 'value' : 'checked', driver.isIOS ? '1' : 'true');
|
|
17
|
+
{{else if (eq stateAssertion "toBeChecked({ checked: false })")}}await expect(__el).not.toHaveAttribute(driver.isIOS ? 'value' : 'checked', driver.isIOS ? '1' : 'true');
|
|
18
|
+
{{else if (eq stateAssertion "toBeEnabled()")}}await expect(__el).toBeEnabled();
|
|
19
|
+
{{else if (eq stateAssertion "toBeFocused()")}}await expect(__el).toBeFocused();
|
|
20
|
+
{{else if (eq stateAssertion "toBeEmpty()")}}await expect(__el).toHaveText('');
|
|
21
|
+
{{else}}throw new Error('[sungen] state qualifier has no native-mobile equivalent — tag this scenario @manual.');
|
|
22
|
+
{{/if}}
|
|
23
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
{{!-- Browser localStorage/sessionStorage has no native-mobile equivalent. --}}
|
|
2
|
+
throw new Error("[sungen] browser local/session storage is web-only and has no native-mobile equivalent. Assert the equivalent app state via a UI element, or via @api/@query against the backend, or tag this scenario @manual.");
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{{!-- Keyboard tab order is a web-only accessibility concern — no keyboard focus traversal on
|
|
2
|
+
native touch input. --}}
|
|
3
|
+
throw new Error("[sungen] tab order assertions are web-only (keyboard focus traversal) and have no native-mobile equivalent. Run a keyboard/accessibility audit manually and tag this scenario @manual.");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{!-- Dialog heading, visible (mobile best-effort): no native "dialog" role — a native alert and
|
|
2
|
+
an in-app modal both render their heading as ordinary text, so search for it directly (same
|
|
3
|
+
idiom as label-value-assertion) and assert displayed. --}}
|
|
4
|
+
{
|
|
5
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
6
|
+
const __sel = driver.isIOS
|
|
7
|
+
? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"`
|
|
8
|
+
: `android=new UiSelector().text("${__expected}")`;
|
|
9
|
+
await expect($(__sel)).toBeDisplayed();
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{{!-- Filtered visible (mobile): $$ over the base selector, per-OS text read matched against
|
|
2
|
+
dataValue — no CSS filter() combinator exists on Appium, so the match is hand-rolled
|
|
3
|
+
(covers both the locator- and role-strategy call sites, since both resolve through
|
|
4
|
+
appium-selector-expr). Assert at least one match is displayed. --}}
|
|
5
|
+
{
|
|
6
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
7
|
+
const __items = await $$({{> appium-selector-expr}});
|
|
8
|
+
let __found = false;
|
|
9
|
+
for (const __el of __items) {
|
|
10
|
+
const __sources = driver.isIOS
|
|
11
|
+
? [await __el.getAttribute('label'), await __el.getAttribute('value')]
|
|
12
|
+
: [await __el.getText(), await __el.getAttribute('content-desc')];
|
|
13
|
+
const __actual = String(__sources.find(s => s && String(s).trim() !== '') ?? '').trim();
|
|
14
|
+
if (__actual === __expected && await __el.isDisplayed()) { __found = true; break; }
|
|
15
|
+
}
|
|
16
|
+
if (!__found) throw new Error(`[sungen] Expected a visible match with text "${__expected}"`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{!-- Role variable, visible (mobile best-effort): ARIA role has no native-mobile analog — reuse
|
|
2
|
+
the role→accessibility-id mapping in appium-selector.hbs (`~name`) when a static name
|
|
3
|
+
exists; the resolver already folds dataValue into name when there is none, so this is only
|
|
4
|
+
the fallback for a caller that omits both. --}}
|
|
5
|
+
{{#if name}}
|
|
6
|
+
await expect($('~{{escapeQuotes name}}')).toBeDisplayed();
|
|
7
|
+
{{else}}
|
|
8
|
+
{
|
|
9
|
+
const __expected = String('{{escapeQuotes dataValue}}').trim();
|
|
10
|
+
const __sel = driver.isIOS ? `-ios predicate string:label == "${__expected}" OR value == "${__expected}"` : `android=new UiSelector().text("${__expected}")`;
|
|
11
|
+
await expect($(__sel)).toBeDisplayed();
|
|
12
|
+
}
|
|
13
|
+
{{/if}}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{!-- Table refresh (mobile): no aria-busy analog — `indicator` is a raw WDIO selector string
|
|
2
|
+
from qa/app.yaml. Soft-wait for it to appear (it may already be gone), hard-wait for it to
|
|
3
|
+
clear; if it never appears within the soft window, fall back to a short settle pause
|
|
4
|
+
instead of hanging on an indicator this app never shows. --}}
|
|
5
|
+
{
|
|
6
|
+
const __indicator = $('{{escapeQuotes indicator}}');
|
|
7
|
+
const __appeared = await __indicator.waitForDisplayed({ timeout: 1500 }).catch(() => false);
|
|
8
|
+
if (__appeared) {
|
|
9
|
+
await __indicator.waitForDisplayed({ reverse: true, timeout: 15000 });
|
|
10
|
+
} else {
|
|
11
|
+
await driver.pause(500);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/drag-action.hbs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
await {{> locator}}.dragTo({{
|
|
1
|
+
await {{> locator}}.dragTo({{#switch targetStrategy}}{{#case 'role'}}{{#if targetName}}page.getByRole('{{targetRole}}', { name: '{{escapeQuotes targetName}}' }){{else}}page.getByRole('{{targetRole}}'){{/if}}{{/case}}{{#case 'testid'}}page.getByTestId('{{escapeQuotes targetValue}}'){{/case}}{{#case 'text'}}page.getByText('{{escapeQuotes targetValue}}'){{/case}}{{#case 'locator'}}page.locator('{{escapeQuotes targetValue}}'){{/case}}{{#default}}page.getByText('{{escapeQuotes targetRef}}'){{/default}}{{/switch}});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const frame = page.frameLocator('{{
|
|
1
|
+
const frame = page.frameLocator('{{escapeQuotes frameSelector}}');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
const
|
|
3
|
-
const __n_{{stepCounter}} = await
|
|
4
|
-
expect(__n_{{stepCounter}}, 'Expected at least one [{{selectorRef}}]').toBeGreaterThan(0);
|
|
5
|
-
for (let
|
|
6
|
-
await expect({{#with child}}{{> locator}}{{/with}}, 'Every [{{selectorRef}}] must contain [{{childRef}}]').toBeVisible();
|
|
2
|
+
const {{containerVar}} = {{> locator}};
|
|
3
|
+
const __n_{{stepCounter}} = await {{containerVar}}.count();
|
|
4
|
+
expect(__n_{{stepCounter}}, 'Expected at least one [{{escapeQuotes selectorRef}}]').toBeGreaterThan(0);
|
|
5
|
+
for (let {{containerIndexVar}} = 0; {{containerIndexVar}} < __n_{{stepCounter}}; {{containerIndexVar}}++) {
|
|
6
|
+
await expect({{#with child}}{{> locator parentLocator=(add (add (add ../containerVar ".nth(") ../containerIndexVar) ")")}}{{/with}}, 'Every [{{escapeQuotes selectorRef}}] must contain [{{escapeQuotes childRef}}]').toBeVisible();
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{!-- Column assertion scoped to the current row (`tableRow`, bound by the row-in-table entry step that ran before this one). columnIndex uses `isDefined` rather than a bare truthy check: column 0 is a valid index. --}}{{#if (isDefined columnIndex)}}await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth({{columnIndex}})).toHaveText('{{escapeQuotes cellValue}}');{{else if isGiven}}await tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '{{escapeQuotes cellValue}}' }).filter({ visible: true }).waitFor();{{else}}await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '{{escapeQuotes cellValue}}' }).filter({ visible: true })).toBeVisible();{{/if}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/generators/test-generator/code-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAA8B,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAqOrD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAM;IAErB,OAAO,CAAC,eAAe,CAAc;IAErC,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO,CAAC,WAAW,CAA8B;gBAErC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO;IAiBtK,oFAAoF;IACpF,cAAc,IAAI,oBAAoB;IAItC;;OAEG;IACG,YAAY,CAChB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC;IA0EzB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAclB;;OAEG;IACH,SAAS,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAS7C;;OAEG;IACH;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA0C3B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAY3B,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YASzB,gBAAgB;YA0ThB,uBAAuB;YAYvB,kBAAkB;YAoBlB,YAAY;IAqB1B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;YASlB,gBAAgB;IAoJ9B;;OAEG;IACH,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,kBAAkB;IAyBpB,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,aAAa,EAAE,CAAC;CA+D5B"}
|
|
@@ -74,21 +74,31 @@ function extractCleanupFlags(tags) {
|
|
|
74
74
|
return found ? flags : undefined;
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
|
-
* Extract pass-through tags (non-functional)
|
|
78
|
-
* Any tag not recognized by sungen as functional → pass through.
|
|
77
|
+
* Extract pass-through tags (non-functional) — the unique, unformatted tag tokens (e.g. `@smoke`).
|
|
78
|
+
* Any tag not recognized by sungen as functional → pass through. Formatting is adapter-specific
|
|
79
|
+
* (Playwright wants a quoted `{ tag: [...] }` array; Appium/mocha has no such option and instead
|
|
80
|
+
* appends the tags to the `it()` title — see `formatPassThroughTags`).
|
|
79
81
|
*/
|
|
80
82
|
const FUNCTIONAL_TAG_PREFIXES = [
|
|
81
83
|
'@parallel', '@serial', '@cleanup:', '@auth:', '@manual', '@no-auth',
|
|
82
84
|
'@steps:', '@extend:', '@screenshot:', '@beforeAll', '@afterEach', '@afterAll',
|
|
83
|
-
'@flow', '@cases:', '@deferred:', '@owned-by:',
|
|
85
|
+
'@flow', '@cases:', '@deferred:', '@owned-by:', '@platform:',
|
|
84
86
|
];
|
|
85
87
|
function extractPassThroughTags(scenarioTags, featureTags) {
|
|
86
88
|
const allTags = [...featureTags, ...scenarioTags];
|
|
87
89
|
const passThrough = allTags.filter(tag => !FUNCTIONAL_TAG_PREFIXES.some(prefix => tag.startsWith(prefix)));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
return [...new Set(passThrough)];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Format the unique pass-through tags for the active adapter's `ScenarioData.tags` field:
|
|
94
|
+
* - Playwright: `'@smoke', '@high'` — spliced verbatim into `{ tag: [...] }` (see scenario.hbs).
|
|
95
|
+
* - Appium: `@smoke @high` — appended to the mocha `it()` title (D5) so `--grep` still filters,
|
|
96
|
+
* since mocha has no per-test tag/annotation option like Playwright's.
|
|
97
|
+
*/
|
|
98
|
+
function formatPassThroughTags(tags, frameworkName) {
|
|
99
|
+
if (tags.length === 0)
|
|
90
100
|
return undefined;
|
|
91
|
-
return
|
|
101
|
+
return frameworkName === 'appium' ? tags.join(' ') : tags.map(t => `'${t}'`).join(', ');
|
|
92
102
|
}
|
|
93
103
|
/**
|
|
94
104
|
* Derive a feature's unit from its source path: the catalog-resolution id (relative to qa/), the
|
|
@@ -651,6 +661,16 @@ class CodeGenerator {
|
|
|
651
661
|
// - Single group: flat structure (test.use at describe level if auth)
|
|
652
662
|
// - Multiple groups: nested describes per auth role
|
|
653
663
|
const needsGrouping = authGroups.length > 1;
|
|
664
|
+
// D6 — the Appium test-file template has no per-group before-hook (only `singleAuthRole` runs
|
|
665
|
+
// `__ensureAuth`); >1 auth group would render every group's scenarios with NO authentication at
|
|
666
|
+
// all, silently. Mirrors the web `@parallel` guard below, but unconditional (isParallel has no
|
|
667
|
+
// per-group meaning on mobile — the template never branches on it).
|
|
668
|
+
if (needsGrouping && this.adapter.frameworkName === 'appium') {
|
|
669
|
+
const roles = groupOrder.map(k => k || '(no @auth)').join(', ');
|
|
670
|
+
throw new Error(`Feature "${feature.name}" has multiple auth groups (${roles}) but the Appium adapter only ` +
|
|
671
|
+
`supports a single shared auth role per feature — the other groups would run unauthenticated.\n` +
|
|
672
|
+
`Fix: split "${feature.name}" into one feature file per @auth role.`);
|
|
673
|
+
}
|
|
654
674
|
if (needsGrouping && !isParallel) {
|
|
655
675
|
throw new Error(`Feature "${feature.name}" has multiple auth groups but no @parallel tag.\n` +
|
|
656
676
|
`Serial mode uses a shared browser context — it cannot mix different auth roles.\n` +
|
|
@@ -874,8 +894,9 @@ class CodeGenerator {
|
|
|
874
894
|
});
|
|
875
895
|
}
|
|
876
896
|
}
|
|
877
|
-
// Extract pass-through tags (feature + scenario, excluding functional tags)
|
|
878
|
-
|
|
897
|
+
// Extract pass-through tags (feature + scenario, excluding functional tags), formatted for
|
|
898
|
+
// the active adapter (Playwright `{ tag: [...] }` vs. Appium's mocha-title suffix — D5).
|
|
899
|
+
const tags = formatPassThroughTags(extractPassThroughTags(scenario.tags, featureTags), this.adapter.frameworkName);
|
|
879
900
|
// Use adapter to render scenario
|
|
880
901
|
const rendered = this.adapter.renderScenario({
|
|
881
902
|
scenarioName: scenario.name,
|