@sun-asterisk/sungen 3.2.21 → 3.2.22-beta.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/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +8 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts +3 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +15 -0
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/inspect.d.ts +41 -0
- package/dist/cli/commands/inspect.d.ts.map +1 -0
- package/dist/cli/commands/inspect.js +134 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +9 -0
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +10 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +38 -0
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +11 -0
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +44 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +10 -0
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/types.d.ts +2 -0
- package/dist/exporters/types.d.ts.map +1 -1
- 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 +25 -1
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +24 -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 +159 -20
- 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 +100 -24
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/harness/audit.d.ts +2 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +101 -10
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/flow-contract.d.ts +87 -0
- package/dist/harness/flow-contract.d.ts.map +1 -0
- package/dist/harness/flow-contract.js +259 -0
- package/dist/harness/flow-contract.js.map +1 -0
- package/dist/harness/flow-plan.d.ts +3 -0
- package/dist/harness/flow-plan.d.ts.map +1 -1
- package/dist/harness/flow-plan.js +6 -2
- package/dist/harness/flow-plan.js.map +1 -1
- package/dist/harness/parse.d.ts +5 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +29 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +40 -0
- package/dist/harness/perf.d.ts.map +1 -0
- package/dist/harness/perf.js +136 -0
- package/dist/harness/perf.js.map +1 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +13 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +8 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +60 -6
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +51 -3
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +10 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +23 -0
- 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 +65 -17
- package/dist/orchestrator/templates/qa-context.md +14 -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/cli/commands/audit.ts +8 -0
- package/src/cli/commands/delivery.ts +14 -2
- package/src/cli/commands/inspect.ts +128 -0
- package/src/cli/commands/trace.ts +9 -0
- package/src/cli/index.ts +2 -0
- package/src/exporters/matrix/build.ts +40 -0
- package/src/exporters/matrix/export.ts +11 -0
- package/src/exporters/matrix/render-xlsx.ts +45 -1
- package/src/exporters/matrix/types.ts +10 -0
- package/src/exporters/playwright-report-parser.ts +2 -0
- package/src/exporters/types.ts +2 -0
- 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 +25 -1
- package/src/generators/test-generator/patterns/index.ts +172 -25
- package/src/generators/test-generator/patterns/types.ts +35 -0
- package/src/generators/test-generator/step-mapper.ts +106 -23
- package/src/harness/audit.ts +104 -11
- package/src/harness/flow-contract.ts +261 -0
- package/src/harness/flow-plan.ts +10 -3
- package/src/harness/parse.ts +31 -1
- package/src/harness/perf.ts +112 -0
- package/src/harness/sensors.ts +13 -1
- package/src/harness/spec-coverage.ts +55 -5
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +51 -3
- package/src/orchestrator/templates/ai-src/commands/create-test.md +10 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +23 -0
- 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 +65 -17
- package/src/orchestrator/templates/qa-context.md +14 -1
- package/src/orchestrator/templates/specs-api.ts +104 -26
- package/src/orchestrator/templates/specs-db.ts +19 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParsedStep } from '../../gherkin-parser';
|
|
2
2
|
import { MappedStep } from '../step-mapper';
|
|
3
|
-
import { StepPattern, PatternContext } from './types';
|
|
3
|
+
import { StepPattern, PatternContext, Platform } from './types';
|
|
4
4
|
import { capabilityRegistry } from '../../../capabilities/registry';
|
|
5
5
|
import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
|
|
6
6
|
|
|
@@ -21,6 +21,78 @@ export function maskRefsForMatching(text: string): string {
|
|
|
21
21
|
.replace(/\[[^\]]*\]/g, '[~]');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Gesture forms that OWN the word "tap" — each has its own pattern keyed on it (`mobile-double-tap`
|
|
26
|
+
* at priority 17, `tap-top-of-element` at 18) and means MORE than a click. They are the one
|
|
27
|
+
* carve-out from the synonym rule below: leave their text exactly as written.
|
|
28
|
+
*/
|
|
29
|
+
const TAP_GESTURE_FORMS = /\bdouble[-\s]?tap|\btap(?:s)?\b[^.]*\b(?:top of|at the top|at top)\b/i;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `tap` is a synonym of `click` — for MATCHING only.
|
|
33
|
+
*
|
|
34
|
+
* The vocabulary was split: exactly three patterns in `@sungen/driver-mobile` key on "tap", while
|
|
35
|
+
* every other pattern in the shared vocabulary keys on "click". So a mobile-phrased step SILENTLY
|
|
36
|
+
* LOST meaning instead of failing — `User tap [Sign In] button and accept [OK] alert` matched the
|
|
37
|
+
* plain-tap pattern and the alert clause simply vanished, leaving a test that passed having checked
|
|
38
|
+
* less than it claimed. Green tests that assert nothing are the worst outcome in this whole class,
|
|
39
|
+
* which is why this is a synonym rather than 40 duplicated matchers.
|
|
40
|
+
*
|
|
41
|
+
* The rewrite is ADDITIVE — `tap` becomes `tap click`, it does not become `click`. Two reasons, and
|
|
42
|
+
* the first one cost a full golden run to learn:
|
|
43
|
+
*
|
|
44
|
+
* 1. `@sungen/driver-mobile` is registered on WEB projects too (same non-opt-in bundling as
|
|
45
|
+
* driver-ui). Substituting the verb re-keys `mobile-tap` onto "click", where its priority 15
|
|
46
|
+
* outranks the generic `click-element` (10) — so it captured every plain click on every WEB
|
|
47
|
+
* feature and relabelled it a tap. 8 golden fixtures caught it. Adding a verb instead of
|
|
48
|
+
* swapping one means text with no "tap" in it is returned untouched, so web output cannot
|
|
49
|
+
* change here BY CONSTRUCTION rather than by our vigilance.
|
|
50
|
+
* 2. Both vocabularies then see their own verb, and ordinary PRIORITY decides the winner —
|
|
51
|
+
* no matcher has to learn about the other. `tap … and accept alert` reaches
|
|
52
|
+
* `click-with-alert` (21); `tap … in [T] table row 2` reaches `table-action-in-row-nth` (19);
|
|
53
|
+
* a plain `tap` still lands on `mobile-tap` (15) ahead of `click-element` (10).
|
|
54
|
+
*
|
|
55
|
+
* Safe because every click-keyed matcher in the vocabulary is a word-boundary test (`\bclick(s)?\b`
|
|
56
|
+
* / `includes('click')`) — none depends on the verb's neighbours, and none tests for the ABSENCE of
|
|
57
|
+
* "click". Verified across driver-ui, driver-db, driver-mock and driver-mobile before relying on it.
|
|
58
|
+
*
|
|
59
|
+
* Resolvers and generators always receive the ORIGINAL `step.text`, so comments and value
|
|
60
|
+
* extraction never see this string.
|
|
61
|
+
*
|
|
62
|
+
* ORDER IS LOAD-BEARING: this runs AFTER `maskRefsForMatching`, so a selector or variable NAME
|
|
63
|
+
* containing "tap" (a `[Tap Water]` field) has already been blanked and cannot be rewritten into a
|
|
64
|
+
* verb. Reversing the two would reclassify that step as a click. The unit test asserts the order.
|
|
65
|
+
*/
|
|
66
|
+
export function normalizeVerbs(text: string): string {
|
|
67
|
+
if (TAP_GESTURE_FORMS.test(text)) return text;
|
|
68
|
+
return text.replace(/\btap(s|ped)?\b/gi, (m) => `${m} click`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `TemplateEngine.loadStepTemplate` signals an absent `.hbs` with this exact message
|
|
73
|
+
* (`template-engine.ts`). Matching on it is what separates "this adapter cannot express the step"
|
|
74
|
+
* — a reviewable compile diagnostic — from a genuine bug inside a template, which must keep
|
|
75
|
+
* propagating untouched.
|
|
76
|
+
*/
|
|
77
|
+
const TEMPLATE_NOT_FOUND = /^Template not found: (.+)\.hbs in any subfolder$/;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The step body emitted in place of code the active adapter cannot produce. A comment alone would
|
|
81
|
+
* let the test go GREEN having asserted nothing — the silent-loss class this SPI exists to close —
|
|
82
|
+
* so the step throws at run time as well as reporting at compile time.
|
|
83
|
+
*
|
|
84
|
+
* `JSON.stringify` does the quoting, NOT a hand-rolled `replace(/'/g, …)`. The reason string embeds
|
|
85
|
+
* arbitrary author text (the step sentence), and hand-escaping only the quote character is wrong for
|
|
86
|
+
* any input containing a backslash or a raw newline: a step ending `…\` would emit `'…\\'`, whose
|
|
87
|
+
* trailing escaped-backslash-then-quote breaks the literal and takes the WHOLE generated spec down
|
|
88
|
+
* with a syntax error — reopening, one level deeper, the exact "one bad step kills the file" failure
|
|
89
|
+
* this function exists to prevent. Step text here is routinely non-ASCII (Vietnamese, Japanese),
|
|
90
|
+
* which JSON.stringify also handles correctly.
|
|
91
|
+
*/
|
|
92
|
+
function unsupportedStepCode(reason: string): string {
|
|
93
|
+
return `throw new Error(${JSON.stringify(`[sungen] ${reason}`)});`;
|
|
94
|
+
}
|
|
95
|
+
|
|
24
96
|
/**
|
|
25
97
|
* Pattern Registry - manages all step patterns
|
|
26
98
|
*/
|
|
@@ -59,7 +131,10 @@ export class PatternRegistry {
|
|
|
59
131
|
findPattern(step: ParsedStep): StepPattern | null {
|
|
60
132
|
// Match against text with ref CONTENTS masked, so a verb substring inside a selector/data name
|
|
61
133
|
// (e.g. "click" in `{{double_click_guard_account.email}}`) can't hijack the verb classification.
|
|
62
|
-
|
|
134
|
+
// THEN normalise `tap` → `click`, so the mobile phrasing reaches the whole shared vocabulary
|
|
135
|
+
// instead of only the three patterns that happen to key on "tap". Masking first is what keeps a
|
|
136
|
+
// `[Tap Water]` ref from being read as a verb.
|
|
137
|
+
const matchStep: ParsedStep = { ...step, text: normalizeVerbs(maskRefsForMatching(step.text)) };
|
|
63
138
|
for (const pattern of this.patterns) {
|
|
64
139
|
if (this.matchesPattern(matchStep, pattern.matcher)) {
|
|
65
140
|
return pattern;
|
|
@@ -73,37 +148,109 @@ export class PatternRegistry {
|
|
|
73
148
|
* Prefers resolver (returns template data) over generator (renders directly).
|
|
74
149
|
*/
|
|
75
150
|
generateStep(step: ParsedStep, context: PatternContext): MappedStep | null {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
151
|
+
// Matchers see only the STEP; some decisions need context (a resolver reading the
|
|
152
|
+
// test-data value, e.g. row-count's numeric check). A resolver may therefore return
|
|
153
|
+
// null to say "not mine after all" — the search continues with the next matching
|
|
154
|
+
// pattern in priority order instead of crashing or silently dropping the step.
|
|
155
|
+
// Matching mirrors findPattern exactly: refs masked FIRST (so `[Tap Water]` is not a
|
|
156
|
+
// verb), then `tap` → `click` normalisation for the mobile vocabulary.
|
|
157
|
+
const matchStep: ParsedStep = { ...step, text: normalizeVerbs(maskRefsForMatching(step.text)) };
|
|
158
|
+
for (const pattern of this.patterns) {
|
|
159
|
+
if (!this.matchesPattern(matchStep, pattern.matcher)) continue;
|
|
80
160
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
161
|
+
// Platform gate — BEFORE any template lookup. A pattern that declares its platforms and is
|
|
162
|
+
// matched on another one is a vocabulary mismatch, not a missing file: report it in its own
|
|
163
|
+
// terms (SG-W020) rather than letting it fail later as a confusing absent template.
|
|
164
|
+
const platformMiss = PatternRegistry.platformMiss(pattern, context, step);
|
|
165
|
+
if (platformMiss) return platformMiss;
|
|
84
166
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
);
|
|
91
|
-
}
|
|
167
|
+
try {
|
|
168
|
+
// Prefer resolver (framework-agnostic) over generator (legacy)
|
|
169
|
+
if (pattern.resolver) {
|
|
170
|
+
const resolved = pattern.resolver(step, context);
|
|
171
|
+
if (!resolved) continue; // resolver declined — fall through
|
|
92
172
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
173
|
+
// Auto-inject parent scoping if step has parentRef
|
|
174
|
+
// Skip for table-* patterns — they resolve the table name internally from step text
|
|
175
|
+
if (step.parentRef && step.parentType && !pattern.name.startsWith('table-')) {
|
|
176
|
+
resolved.data.parentLocator = PatternRegistry.resolveParentLocator(
|
|
177
|
+
step.parentRef, step.parentType, context
|
|
178
|
+
);
|
|
179
|
+
}
|
|
99
180
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
181
|
+
const code = context.templateEngine.renderStep(resolved.templateName, resolved.data);
|
|
182
|
+
return {
|
|
183
|
+
code,
|
|
184
|
+
comment: resolved.comment,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (pattern.generator) {
|
|
189
|
+
return pattern.generator(step, context);
|
|
190
|
+
}
|
|
103
191
|
|
|
192
|
+
return null;
|
|
193
|
+
} catch (err: any) {
|
|
194
|
+
// The step matched a pattern the ACTIVE adapter has no template for. Before this guard the
|
|
195
|
+
// unhandled throw killed the ENTIRE feature file and named neither the step nor the adapter.
|
|
196
|
+
const missing = TEMPLATE_NOT_FOUND.exec(String(err?.message ?? ''));
|
|
197
|
+
if (!missing) throw err;
|
|
198
|
+
return PatternRegistry.unsupportedTemplate(missing[1], pattern, context, step);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
104
201
|
return null;
|
|
105
202
|
}
|
|
106
203
|
|
|
204
|
+
/**
|
|
205
|
+
* SG-W020 — the matched pattern declares `platforms` and the active one is not among them.
|
|
206
|
+
*/
|
|
207
|
+
private static platformMiss(
|
|
208
|
+
pattern: StepPattern, context: PatternContext, step: ParsedStep
|
|
209
|
+
): MappedStep | null {
|
|
210
|
+
const platform: Platform = context.platform ?? 'web';
|
|
211
|
+
if (!pattern.platforms || pattern.platforms.includes(platform)) return null;
|
|
212
|
+
|
|
213
|
+
const adapter = context.adapterName ?? platform;
|
|
214
|
+
const supported = pattern.platforms.join('/');
|
|
215
|
+
const alternative = pattern.platformAlternative
|
|
216
|
+
?? 'use a supported step form, or tag the scenario @manual';
|
|
217
|
+
const reason =
|
|
218
|
+
`step "${step.text}" uses pattern "${pattern.name}", which is ${supported}-only — ` +
|
|
219
|
+
`the ${adapter} adapter cannot express it. Instead: ${alternative}.`;
|
|
220
|
+
|
|
221
|
+
context.diagnostics?.add('SG-W020', reason, {
|
|
222
|
+
feature: context.featureName,
|
|
223
|
+
step: step.text,
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
code: unsupportedStepCode(reason),
|
|
227
|
+
comment: `${supported}-only step on the ${adapter} adapter — see SG-W020`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* SG-E020 — the pattern is in-platform but the active adapter ships no template for it. A gap in
|
|
233
|
+
* the adapter, not in the authored Gherkin: name every coordinate a reader needs to act on it.
|
|
234
|
+
*/
|
|
235
|
+
private static unsupportedTemplate(
|
|
236
|
+
templateName: string, pattern: StepPattern, context: PatternContext, step: ParsedStep
|
|
237
|
+
): MappedStep {
|
|
238
|
+
const adapter = context.adapterName ?? context.platform ?? 'web';
|
|
239
|
+
const reason =
|
|
240
|
+
`step "${step.text}" needs template "${templateName}.hbs", which the ${adapter} adapter ` +
|
|
241
|
+
`does not ship (pattern "${pattern.name}"). Rephrase with a supported step form, or tag ` +
|
|
242
|
+
`the scenario @manual.`;
|
|
243
|
+
|
|
244
|
+
context.diagnostics?.add('SG-E020', reason, {
|
|
245
|
+
feature: context.featureName,
|
|
246
|
+
step: step.text,
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
code: unsupportedStepCode(reason),
|
|
250
|
+
comment: `no ${adapter} template for "${templateName}" — see SG-E020`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
107
254
|
/**
|
|
108
255
|
* Resolve parent scoping to a Playwright locator string.
|
|
109
256
|
* Tries YAML lookup first, falls back to auto-infer from parentType.
|
|
@@ -3,6 +3,22 @@ import { MappedStep } from '../step-mapper';
|
|
|
3
3
|
import { SelectorResolver } from '../utils/selector-resolver';
|
|
4
4
|
import { DataResolver } from '../utils/data-resolver';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The subset of DiagnosticsCollector the pattern layer needs. Structural, not a class import,
|
|
8
|
+
* so the framework-agnostic pattern SPI keeps depending on nothing concrete.
|
|
9
|
+
*/
|
|
10
|
+
export interface DiagnosticsSink {
|
|
11
|
+
add(code: any, message: string, context?: { feature?: string; step?: string }): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The platform a pattern (and its templates) can compile for. Drawn from the ACTIVE adapter:
|
|
16
|
+
* `appium` → 'mobile', everything else → 'web'. A pattern that omits `platforms` is assumed to
|
|
17
|
+
* work on BOTH — the historical assumption, kept as the default so no existing pattern changes
|
|
18
|
+
* behaviour.
|
|
19
|
+
*/
|
|
20
|
+
export type Platform = 'web' | 'mobile';
|
|
21
|
+
|
|
6
22
|
/**
|
|
7
23
|
* Pattern matcher function or regex
|
|
8
24
|
*/
|
|
@@ -44,6 +60,12 @@ export interface PatternContext {
|
|
|
44
60
|
scenarioSteps?: ParsedStep[];
|
|
45
61
|
/** Effective Gherkin keyword (Given/When/Then), resolved from And/But */
|
|
46
62
|
effectiveKeyword?: string;
|
|
63
|
+
/** The platform the ACTIVE adapter compiles for. Absent = 'web' (back-compat default). */
|
|
64
|
+
platform?: Platform;
|
|
65
|
+
/** The adapter's own name ('playwright' | 'appium' | …) — quoted verbatim in SG-E020/SG-W020. */
|
|
66
|
+
adapterName?: string;
|
|
67
|
+
/** Compile-diagnostics channel, so the pattern layer can report instead of throwing. */
|
|
68
|
+
diagnostics?: DiagnosticsSink;
|
|
47
69
|
/** App profile (qa/app.yaml merged over presets/defaults) — widget variants, recipes,
|
|
48
70
|
* loading indicator, table row conventions. Typed loosely to keep the SPI decoupled;
|
|
49
71
|
* the concrete shape is ResolvedAppProfile. */
|
|
@@ -69,4 +91,17 @@ export interface StepPattern {
|
|
|
69
91
|
resolver?: PatternResolver;
|
|
70
92
|
generator?: PatternGenerator;
|
|
71
93
|
priority?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Platforms this pattern can compile for. ABSENT = both (the default — every pattern that
|
|
96
|
+
* predates the platform SPI). Declaring it narrows the pattern: matching it on any other
|
|
97
|
+
* platform emits SG-W020 and compiles to a loud runtime `throw` instead of silently
|
|
98
|
+
* producing code that references a driver the platform does not have.
|
|
99
|
+
*/
|
|
100
|
+
platforms?: ReadonlyArray<Platform>;
|
|
101
|
+
/**
|
|
102
|
+
* Free-text alternative named in the SG-W020 message when this pattern is used off-platform
|
|
103
|
+
* (e.g. "`@api` / `@query`, or tag the scenario @manual"). Optional — a generic pointer is
|
|
104
|
+
* emitted when absent.
|
|
105
|
+
*/
|
|
106
|
+
platformAlternative?: string;
|
|
72
107
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParsedStep } from '../gherkin-parser';
|
|
2
2
|
import { TemplateEngine } from './template-engine';
|
|
3
3
|
import { adapterRegistry } from './adapters';
|
|
4
|
-
import { PatternRegistry, PatternContext } from './patterns';
|
|
4
|
+
import { PatternRegistry, PatternContext, Platform } from './patterns';
|
|
5
5
|
import { SelectorResolver } from './utils/selector-resolver';
|
|
6
6
|
import { DataResolver } from './utils/data-resolver';
|
|
7
7
|
import { DiagnosticsCollector } from './diagnostics';
|
|
@@ -49,6 +49,10 @@ export class StepMapper {
|
|
|
49
49
|
private diagnostics?: DiagnosticsCollector;
|
|
50
50
|
/** App profile (qa/app.yaml merged over presets/defaults) — the per-project BEHAVIOR layer */
|
|
51
51
|
private appProfile: ResolvedAppProfile;
|
|
52
|
+
/** The platform the ACTIVE adapter compiles for — drives the pattern-layer platform gate */
|
|
53
|
+
private platform: Platform;
|
|
54
|
+
/** The active adapter's own name ('playwright' | 'appium') — quoted in SG-E020/SG-W020 */
|
|
55
|
+
private adapterName: string;
|
|
52
56
|
|
|
53
57
|
constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string; runtimeData?: boolean; framework?: string } = {}) {
|
|
54
58
|
this.verbose = options.verbose ?? false;
|
|
@@ -60,7 +64,13 @@ export class StepMapper {
|
|
|
60
64
|
// mobile→Appium). The adapter owns its templatesDir; resolving it here keeps step
|
|
61
65
|
// rendering and the file skeleton (code-generator) on the same framework.
|
|
62
66
|
const framework = options.framework || 'web';
|
|
63
|
-
const
|
|
67
|
+
const adapter = adapterRegistry.getAdapter(framework);
|
|
68
|
+
const templatesDir = adapter.templatesDir;
|
|
69
|
+
// The platform gate keys on the ADAPTER, not on the requested framework name: 'mobile' and
|
|
70
|
+
// 'appium' are aliases of the same adapter, so asking the adapter itself keeps one source of
|
|
71
|
+
// truth as more aliases appear.
|
|
72
|
+
this.adapterName = adapter.frameworkName;
|
|
73
|
+
this.platform = adapter.frameworkName === 'appium' ? 'mobile' : 'web';
|
|
64
74
|
this.templateEngine = new TemplateEngine(templatesDir);
|
|
65
75
|
this.patternRegistry = new PatternRegistry();
|
|
66
76
|
this.selectorResolver = new SelectorResolver(undefined, options.screenName);
|
|
@@ -175,6 +185,28 @@ export class StepMapper {
|
|
|
175
185
|
this.lastPrimaryKeyword = keyword;
|
|
176
186
|
}
|
|
177
187
|
|
|
188
|
+
// SG-W015 — `see [X] card with {{n}}` where n is a bare number compiles to a TEXT
|
|
189
|
+
// assertion (toHaveText('3')), not a count. An author reaching for "3 cards are shown"
|
|
190
|
+
// gets an assertion that can only pass if the card's TEXT is the digit — a wrong test
|
|
191
|
+
// with no error. Count semantics live on row/table ("see [T] table with {{n}}",
|
|
192
|
+
// "see [Ref] row with {{n}}") and lists ("should have count"); on the container types
|
|
193
|
+
// below, a bare-number text expectation is almost never what the author meant. A text
|
|
194
|
+
// element asserting a numeric readout ("[Row Quantity] text with 4") stays silent.
|
|
195
|
+
const COUNT_MISREAD_TYPES = new Set(['card', 'item', 'items', 'cards', 'option', 'options']);
|
|
196
|
+
if (this.diagnostics && step.dataRef && step.selectorRef &&
|
|
197
|
+
COUNT_MISREAD_TYPES.has((step.elementType || '').toLowerCase()) &&
|
|
198
|
+
/\bsees?\s+\[/.test(step.text) && /\bwith\b/.test(step.text)) {
|
|
199
|
+
// peekBaseValue reads the literal from test-data even in runtime-data mode,
|
|
200
|
+
// where resolveData returns an opaque marker (never numeric-looking).
|
|
201
|
+
let resolvedValue: string | undefined;
|
|
202
|
+
try { resolvedValue = this.dataResolver.peekBaseValue(step.dataRef, this.featureName); } catch { /* unresolved at compile time → cannot judge */ }
|
|
203
|
+
if (resolvedValue !== undefined && /^\d+$/.test(resolvedValue.trim())) {
|
|
204
|
+
this.diagnostics.add('SG-W015',
|
|
205
|
+
`\`see [${step.selectorRef}] ${step.elementType} with {{${step.dataRef}}}\` compiles to a TEXT assertion toHaveText('${resolvedValue.trim()}'), NOT a count of ${step.elementType}s. For a count use \`see [Ref] row with {{n}}\` / \`see [T] table with {{n}}\` (or a list "should have count"); if '${resolvedValue.trim()}' really is the ${step.elementType}'s text, assert it on a text/label element instead`,
|
|
206
|
+
{ feature: this.featureName, step: step.text });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
178
210
|
// SG-W007 — a two-step-form browser-alert handler (`click [OK] alert`) written AFTER
|
|
179
211
|
// an action step registers its page.once('dialog') listener too late: a dialog
|
|
180
212
|
// triggered by the earlier action auto-dismisses with no error (issue #472).
|
|
@@ -212,10 +244,12 @@ export class StepMapper {
|
|
|
212
244
|
const selectorRef = step.selectorRef || '';
|
|
213
245
|
|
|
214
246
|
if (selectorRef.toLowerCase() === 'main') {
|
|
215
|
-
// Exit frame scope
|
|
247
|
+
// Exit frame scope — routed through the adapter (C1): the Appium twin performs the
|
|
248
|
+
// real webview→native context switch; a bare comment here would silently no-op on mobile.
|
|
216
249
|
this.inFrameScope = false;
|
|
217
250
|
this.templateEngine.setBaseContext({ inFrame: false });
|
|
218
|
-
|
|
251
|
+
const code = this.templateEngine.renderStep('frame-exit-action', {});
|
|
252
|
+
return { code, comment: 'Exit frame scope' };
|
|
219
253
|
}
|
|
220
254
|
|
|
221
255
|
// Enter frame scope
|
|
@@ -228,7 +262,9 @@ export class StepMapper {
|
|
|
228
262
|
// Use selectorRef as-is
|
|
229
263
|
}
|
|
230
264
|
this.templateEngine.setBaseContext({ inFrame: true, frameSelector });
|
|
231
|
-
|
|
265
|
+
// C1 — routed through the adapter instead of building `page.frameLocator(...)` inline:
|
|
266
|
+
// the Appium adapter's own template performs its webview-context switch instead.
|
|
267
|
+
const code = this.templateEngine.renderStep('frame-enter-action', { frameSelector });
|
|
232
268
|
return { code, comment: `Switch to ${selectorRef} frame` };
|
|
233
269
|
}
|
|
234
270
|
|
|
@@ -236,6 +272,7 @@ export class StepMapper {
|
|
|
236
272
|
if (/\buse dialog\b/i.test(step.text)) {
|
|
237
273
|
this.inDialogScope = true;
|
|
238
274
|
this.templateEngine.setBaseContext({ inDialog: true });
|
|
275
|
+
this.warnDialogScopeIgnoredOnMobile(step);
|
|
239
276
|
return { code: '', comment: 'Enter dialog scope' };
|
|
240
277
|
}
|
|
241
278
|
if (/\b(?:close|dismiss|exit)\s+dialog\b/i.test(step.text)) {
|
|
@@ -246,6 +283,7 @@ export class StepMapper {
|
|
|
246
283
|
// Given User is on [...] dialog — set dialog scope (3 cases)
|
|
247
284
|
if (step.text.includes('is on') && step.elementType === 'dialog') {
|
|
248
285
|
this.inDialogScope = true;
|
|
286
|
+
this.warnDialogScopeIgnoredOnMobile(step);
|
|
249
287
|
// setBaseContext MERGES — clear every root key so a previous dialog scope in the
|
|
250
288
|
// same scenario cannot leak its root into this one.
|
|
251
289
|
const contextVars: Record<string, any> = {
|
|
@@ -333,6 +371,7 @@ export class StepMapper {
|
|
|
333
371
|
console.log(` ✓ Pattern matched: ${step.text.substring(0, 50)}...`);
|
|
334
372
|
}
|
|
335
373
|
this.checkDataRefConsumed(step, mappedStep);
|
|
374
|
+
this.checkPageAssertionNoOpOnMobile(step, mappedStep);
|
|
336
375
|
this.checkPageAssertionFallback(step, mappedStep);
|
|
337
376
|
this.checkDeclaredUrlHasNoQuery(step, mappedStep);
|
|
338
377
|
return mappedStep;
|
|
@@ -493,6 +532,11 @@ export class StepMapper {
|
|
|
493
532
|
* includeHidden + visible filter: when a modal (e.g. Radix Dialog) is open the
|
|
494
533
|
* background gets aria-hidden="true", which empties plain getByRole() even though
|
|
495
534
|
* the cells are still rendered — measure what the user SEES, not the a11y tree.
|
|
535
|
+
*
|
|
536
|
+
* C2 — this method used to build the Playwright code string itself (referencing `tableRow`,
|
|
537
|
+
* a binding that only exists in the Playwright output). It now only gathers data and routes
|
|
538
|
+
* through the adapter template; the Appium twin throws (HTML table roles), matching the
|
|
539
|
+
* `table-*` house style.
|
|
496
540
|
*/
|
|
497
541
|
private generateRowScopedColumnAssertion(step: ParsedStep): MappedStep {
|
|
498
542
|
const columnRef = step.selectorRef || '';
|
|
@@ -515,24 +559,9 @@ export class StepMapper {
|
|
|
515
559
|
}
|
|
516
560
|
|
|
517
561
|
const isGiven = this.lastPrimaryKeyword === 'Given';
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if (columnIndex !== undefined) {
|
|
522
|
-
// Exact cell: nth(index) + toHaveText
|
|
523
|
-
if (isGiven) {
|
|
524
|
-
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
525
|
-
} else {
|
|
526
|
-
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ visible: true }).nth(${columnIndex})).toHaveText('${escapedValue}');`;
|
|
527
|
-
}
|
|
528
|
-
} else {
|
|
529
|
-
// Fallback: filter by text
|
|
530
|
-
if (isGiven) {
|
|
531
|
-
code = `await tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '${escapedValue}' }).filter({ visible: true }).waitFor();`;
|
|
532
|
-
} else {
|
|
533
|
-
code = `await expect(tableRow.getByRole('cell', { includeHidden: true }).filter({ hasText: '${escapedValue}' }).filter({ visible: true })).toBeVisible();`;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
562
|
+
const code = this.templateEngine.renderStep('row-scoped-column-assertion', {
|
|
563
|
+
columnIndex, cellValue, isGiven,
|
|
564
|
+
});
|
|
536
565
|
|
|
537
566
|
return {
|
|
538
567
|
code,
|
|
@@ -543,6 +572,57 @@ export class StepMapper {
|
|
|
543
572
|
/**
|
|
544
573
|
* Create pattern context for generators
|
|
545
574
|
*/
|
|
575
|
+
/**
|
|
576
|
+
* SG-W022 — a `page` assertion that emitted no executable code on mobile.
|
|
577
|
+
*
|
|
578
|
+
* A native app has no URL, so the Appium `page-assertion` template is a bare comment. The step
|
|
579
|
+
* therefore READS as an assertion and checks nothing: the scenario passes whatever the app is
|
|
580
|
+
* showing. That is strictly worse than the missing-template crashes this pass closed — a crash is
|
|
581
|
+
* visible on the first run, a false pass hides until someone trusts it. (Its `is on [X] page` twin
|
|
582
|
+
* routes to `route-assertion`, which throws loudly; only this half was silent.)
|
|
583
|
+
*
|
|
584
|
+
* Keyed on the EMITTED CODE rather than on a template name, so any future page-assertion path
|
|
585
|
+
* that degrades to a comment is caught by the same check without being enumerated here. Scoped to
|
|
586
|
+
* the `see` form: the `is on` twin is mobile's app-launch directive, whose empty output is right.
|
|
587
|
+
*/
|
|
588
|
+
private checkPageAssertionNoOpOnMobile(step: ParsedStep, mapped: MappedStep): void {
|
|
589
|
+
if (this.platform !== 'mobile' || !this.diagnostics) return;
|
|
590
|
+
if (step.elementType !== 'page') return;
|
|
591
|
+
// Only the ASSERTION form. `Given User is on [X] page` is the app-LAUNCH directive on mobile —
|
|
592
|
+
// it emits a comment because wdio capabilities already launched the app, which is correct and
|
|
593
|
+
// must not be warned about. `Then User see [X] page` is the one that claims to check something.
|
|
594
|
+
if (!/\bsees?\b/i.test(step.text)) return;
|
|
595
|
+
// Strip comments; whatever remains is what actually runs. Block comments are handled as well as
|
|
596
|
+
// line ones: a template written with `/* … */` would otherwise read as executable code and
|
|
597
|
+
// silently defeat this check — the diagnostic would go quiet exactly when it was needed.
|
|
598
|
+
const executable = (mapped.code || '')
|
|
599
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
600
|
+
.split('\n')
|
|
601
|
+
.map((l) => l.replace(/\/\/.*$/, '').trim())
|
|
602
|
+
.filter(Boolean)
|
|
603
|
+
.join('');
|
|
604
|
+
if (executable) return;
|
|
605
|
+
this.diagnostics.add('SG-W022',
|
|
606
|
+
'a native app has no URL, so this page assertion compiled to a COMMENT — it asserts nothing and the scenario passes regardless. Assert a marker that is actually on the screen instead (`Then User see [Some Element] ...`), or tag the scenario @manual',
|
|
607
|
+
{ feature: this.featureName, step: step.text });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* SG-W021 — dialog scope is a no-op on the Appium adapter.
|
|
612
|
+
*
|
|
613
|
+
* `setBaseContext({ inDialog: true })` records the scope faithfully, and every Playwright locator
|
|
614
|
+
* partial consults it. No Appium template does. So on mobile the step reads as "narrow everything
|
|
615
|
+
* that follows to this dialog" and in fact narrows nothing — the following steps search the whole
|
|
616
|
+
* screen. Scope that is silently discarded is what produced this entire defect class, so it is
|
|
617
|
+
* reported rather than left to be discovered from a passing test.
|
|
618
|
+
*/
|
|
619
|
+
private warnDialogScopeIgnoredOnMobile(step: ParsedStep): void {
|
|
620
|
+
if (this.platform !== 'mobile') return;
|
|
621
|
+
this.diagnostics?.add('SG-W021',
|
|
622
|
+
'dialog scope has no effect on the mobile adapter — no Appium template reads `inDialog`, so the following steps resolve against the whole screen, not the dialog. Scope the elements themselves instead (a `scope: dialog` entry, or accessibility-ids unique to the dialog)',
|
|
623
|
+
{ feature: this.featureName, step: step.text });
|
|
624
|
+
}
|
|
625
|
+
|
|
546
626
|
private createPatternContext(): PatternContext {
|
|
547
627
|
return {
|
|
548
628
|
stepCounter: this.stepCounter,
|
|
@@ -555,6 +635,9 @@ export class StepMapper {
|
|
|
555
635
|
dataResolver: this.dataResolver,
|
|
556
636
|
renderLocator: (resolved) => this.templateEngine.renderStep('locator', resolved),
|
|
557
637
|
appProfile: this.appProfile,
|
|
638
|
+
platform: this.platform,
|
|
639
|
+
adapterName: this.adapterName,
|
|
640
|
+
diagnostics: this.diagnostics,
|
|
558
641
|
baseURL: this.baseURL,
|
|
559
642
|
featureName: this.featureName,
|
|
560
643
|
screenName: this.screenName,
|