@sun-asterisk/sungen 3.2.28 → 3.2.29
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/capabilities/discover.d.ts +29 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +144 -10
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +18 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +25 -0
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +9 -0
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.js +1 -1
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/exporters/feature-parser.d.ts +1 -1
- package/dist/exporters/feature-parser.js +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +14 -1
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/item-labels.d.ts +35 -0
- package/dist/exporters/matrix/item-labels.d.ts.map +1 -0
- package/dist/exporters/matrix/item-labels.js +90 -0
- package/dist/exporters/matrix/item-labels.js.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts +14 -0
- package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-coverage-sheet.js +160 -0
- package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-overview-sheet.d.ts +20 -0
- package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-overview-sheet.js +36 -0
- package/dist/exporters/matrix/render-overview-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts +30 -0
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-testcase-sheet.js +156 -0
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +41 -31
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +72 -425
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/testcase-summary-band.d.ts +30 -0
- package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -0
- package/dist/exporters/matrix/testcase-summary-band.js +75 -0
- package/dist/exporters/matrix/testcase-summary-band.js.map +1 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +13 -0
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.d.ts +38 -0
- package/dist/exporters/matrix/xlsx-cells.d.ts.map +1 -0
- package/dist/exporters/matrix/xlsx-cells.js +52 -0
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -0
- package/dist/exporters/xlsx-report-builder.d.ts +12 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +10 -2
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +7 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +3 -3
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts +53 -0
- package/dist/generators/test-generator/adapters/appium/os-support.d.ts.map +1 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js +200 -0
- package/dist/generators/test-generator/adapters/appium/os-support.js.map +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +7 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +7 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +5 -1
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +38 -2
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +37 -1
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +33 -6
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +71 -7
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +8 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +40 -1
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +100 -17
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +23 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +78 -12
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +3 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +51 -28
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +4 -0
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +32 -2
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +145 -0
- package/dist/harness/parse.d.ts +6 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +1 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/repair.d.ts.map +1 -1
- package/dist/harness/repair.js +8 -1
- package/dist/harness/repair.js.map +1 -1
- package/dist/harness/sensors.d.ts +48 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +26 -1
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts +10 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +23 -2
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +48 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +11 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +10 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +58 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/dist/orchestrator/templates/readme.md +1 -1
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/wdio.conf.ts +105 -1
- package/package.json +3 -3
- package/src/capabilities/discover.ts +146 -10
- package/src/capabilities/registry.ts +30 -0
- package/src/cli/commands/audit.ts +9 -0
- package/src/cli/commands/capability.ts +1 -1
- package/src/dashboard/templates/index.html +1 -1
- package/src/exporters/feature-parser.ts +1 -1
- package/src/exporters/matrix/build.ts +13 -1
- package/src/exporters/matrix/item-labels.ts +78 -0
- package/src/exporters/matrix/render-coverage-sheet.ts +168 -0
- package/src/exporters/matrix/render-overview-sheet.ts +56 -0
- package/src/exporters/matrix/render-testcase-sheet.ts +178 -0
- package/src/exporters/matrix/render-xlsx.ts +67 -448
- package/src/exporters/matrix/testcase-summary-band.ts +79 -0
- package/src/exporters/matrix/wording.ts +13 -0
- package/src/exporters/matrix/xlsx-cells.ts +56 -0
- package/src/exporters/xlsx-report-builder.ts +16 -2
- package/src/generators/gherkin-parser/index.ts +1 -1
- package/src/generators/test-generator/adapters/appium/os-support.ts +176 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/capture-variable.hbs +24 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +6 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-element-with-text.hbs +4 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +9 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +34 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +14 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/go-back-action.hbs +21 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +16 -0
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +7 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +7 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-match-data.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +12 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +14 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/testid.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +8 -8
- package/src/generators/test-generator/code-generator.ts +5 -1
- package/src/generators/test-generator/diagnostics.ts +39 -2
- package/src/generators/test-generator/patterns/index.ts +78 -8
- package/src/generators/test-generator/patterns/types.ts +8 -0
- package/src/generators/test-generator/step-mapper.ts +107 -17
- package/src/generators/test-generator/utils/selector-resolver.ts +88 -12
- package/src/harness/audit.ts +55 -30
- package/src/harness/capability-plan.ts +33 -1
- package/src/harness/catalog/drivers.yaml +1 -1
- package/src/harness/catalog/universal-viewpoints.yaml +145 -0
- package/src/harness/parse.ts +7 -1
- package/src/harness/repair.ts +8 -1
- package/src/harness/sensors.ts +75 -1
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/orchestrator/templates/ai-src/commands/delivery.md +4 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +48 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +18 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +16 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +11 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +10 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +88 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +58 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md +13 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +59 -10
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-f-mobile-native.md +419 -0
- package/src/orchestrator/templates/readme.md +1 -1
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/wdio.conf.ts +105 -1
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
|
|
2
|
-
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
2
|
+
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
3
3
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
4
4
|
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
5
5
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
6
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
|
-
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
8
|
-
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
9
|
-
{{~#default}}locator('{{value}}'){{/default~}}
|
|
7
|
+
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
10
10
|
{{~/switch}}{{else~}}
|
|
11
11
|
{{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
|
|
12
|
-
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
12
|
+
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
13
13
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
14
14
|
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
15
15
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
16
16
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
17
|
-
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
18
|
-
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
19
|
-
{{~#default}}locator('{{value}}'){{/default~}}
|
|
17
|
+
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
18
|
+
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
19
|
+
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
20
20
|
{{~/switch}}{{else}}{{#switch strategy~}}
|
|
21
21
|
{{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
|
|
22
22
|
{{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
page.locator('{{selector}}')
|
|
1
|
+
page.locator('{{escapeQuotes selector}}')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{{pageRoot}}.getByTestId('{{value}}')
|
|
1
|
+
{{pageRoot}}.getByTestId('{{escapeQuotes value}}')
|
package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{{#if parentLocator}}{{parentLocator}}.{{#switch strategy~}}
|
|
2
|
-
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
2
|
+
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
3
3
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
4
4
|
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
5
5
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
6
6
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
7
|
-
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
8
|
-
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
9
|
-
{{~#default}}locator('{{value}}'){{/default~}}
|
|
7
|
+
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
8
|
+
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
9
|
+
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
10
10
|
{{~/switch}}{{> locator-nth}}{{else~}}
|
|
11
11
|
{{#if scope}}{{#if (eq scope "dialog")}}{{pageRoot}}.{{else}}{{pageRoot}}.getByLabel('{{escapeQuotes scope}}').{{/if}}{{#switch strategy~}}
|
|
12
|
-
{{~#case 'testid'}}getByTestId('{{value}}'){{/case~}}
|
|
12
|
+
{{~#case 'testid'}}getByTestId('{{escapeQuotes value}}'){{/case~}}
|
|
13
13
|
{{~#case 'role'}}{{#if name}}getByRole('{{role}}', { name: '{{escapeQuotes name}}'{{#if exact}}, exact: true{{/if}} }){{else}}getByRole('{{role}}'{{#if exact}}, { exact: true }{{/if}}){{/if}}{{/case~}}
|
|
14
14
|
{{~#case 'placeholder'}}getByPlaceholder('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
15
15
|
{{~#case 'label'}}getByLabel('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
16
16
|
{{~#case 'text'}}getByText('{{escapeQuotes value}}'{{#if exact}}, { exact: true }{{/if}}){{/case~}}
|
|
17
|
-
{{~#case 'locator'}}locator('{{value}}'){{/case~}}
|
|
18
|
-
{{~#case 'id'}}locator('{{value}}'){{/case~}}
|
|
19
|
-
{{~#default}}locator('{{value}}'){{/default~}}
|
|
17
|
+
{{~#case 'locator'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
18
|
+
{{~#case 'id'}}locator('{{escapeQuotes value}}'){{/case~}}
|
|
19
|
+
{{~#default}}locator('{{escapeQuotes value}}'){{/default~}}
|
|
20
20
|
{{~/switch}}{{> locator-nth}}{{else}}{{#switch strategy~}}
|
|
21
21
|
{{~#case 'testid'}}{{> locator-strategies/testid}}{{/case~}}
|
|
22
22
|
{{~#case 'role'}}{{> locator-strategies/role}}{{/case~}}
|
|
@@ -562,7 +562,11 @@ export class CodeGenerator {
|
|
|
562
562
|
this.stepMapper.setFlowMode(isFlowFeature);
|
|
563
563
|
|
|
564
564
|
// Set feature context for data resolution and navigation
|
|
565
|
-
|
|
565
|
+
// The feature's declared OS rides along so the pattern layer can refuse an Android-only step
|
|
566
|
+
// in a feature that says it runs on iOS (SG-E025). Read from the tag, not from the adapter.
|
|
567
|
+
const declaredOs = (feature.tags || [])
|
|
568
|
+
.find((tg) => tg.startsWith('@platform:'))?.split(':')[1] as 'mobile' | 'android' | 'ios' | undefined;
|
|
569
|
+
this.stepMapper.setFeatureContext(featureName, feature.path, declaredOs);
|
|
566
570
|
|
|
567
571
|
// Warn if multiple auth tags at feature level
|
|
568
572
|
if (feature.tags && feature.tags.some(tag => tag.startsWith('@auth:'))) {
|
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
* "3 cards are shown" gets an assertion that can only pass if the card's text
|
|
46
46
|
* IS the digit. Count semantics exist on row/table ("see [T] table with {{n}}",
|
|
47
47
|
* "see [Ref] row with {{n}}") and lists ("should have count") only
|
|
48
|
+
* SG-W016 — a table step on a table located by CSS/testid whose value does not mention `table`
|
|
49
|
+
* and that declares no `rows:`. The table templates find rows by `getByRole('row')`;
|
|
50
|
+
* on a <div> list that is 0 elements, so `is hidden` / `is empty` pass without checking
|
|
51
|
+
* anything and counts/clicks time out (issue #688). A heuristic — the compiler cannot
|
|
52
|
+
* see the DOM — so a warning that names both ways out: `rows: '<row shape>'` on the
|
|
53
|
+
* table entry, or `role: table`. Once per table per feature
|
|
48
54
|
* SG-E024 — a mobile app-lifecycle step (restart / reinstall) in a feature that declares no
|
|
49
55
|
* `Path:` app id. The templates resolve the app through a helper the test-file only
|
|
50
56
|
* emits when the id is present, so the spec used to compile clean and then die at
|
|
@@ -74,7 +80,10 @@
|
|
|
74
80
|
* (11), the clause vanished, and the step compiled to `toHaveURL(...)` — asserting the
|
|
75
81
|
* page IS displayed, the exact opposite of what it reads as, and passing while doing so.
|
|
76
82
|
* The pattern layer declares what it accounts for via `StepPattern.states`; anything
|
|
77
|
-
* undeclared reports here and compiles to a loud `throw
|
|
83
|
+
* undeclared reports here and compiles to a loud `throw`. One shape is NOT the author's:
|
|
84
|
+
* a pattern NAMED for the state (`is-hidden`) with no `states` at all is a
|
|
85
|
+
* @sungen/driver-* older than core — the message then names `sungen update` as the
|
|
86
|
+
* fix, and discovery prefers core's own copy of a bundled driver over a stale project one
|
|
78
87
|
* SG-W022 — a `page` assertion on the mobile adapter that emitted NO executable code. A native app
|
|
79
88
|
* has no URL, so `Then User see [X] page` compiles to a bare comment: the step reads as
|
|
80
89
|
* an assertion, checks nothing, and the scenario PASSES regardless. Worse than the
|
|
@@ -91,6 +100,33 @@
|
|
|
91
100
|
* mobile suite already carrying one of these steps. The remedy is a feature-level reset
|
|
92
101
|
* tier (`@reset:data`), which is what the message names — app state is reset between
|
|
93
102
|
* scenarios in the `beforeEach` hook, not mid-scenario by a step.
|
|
103
|
+
* SG-E025 — a feature that declares `@platform:ios` uses a step the Appium adapter does NOT serve
|
|
104
|
+
* on iOS. The template ships and the step compiles, so nothing about the feature file
|
|
105
|
+
* looks wrong; on a simulator it either throws deep in the run (set-connectivity,
|
|
106
|
+
* location-services, open-notifications) or — worse — silently does nothing
|
|
107
|
+
* (hide-keyboard, guarded `if (!driver.isIOS …)`). Derived from the template body via
|
|
108
|
+
* the per-OS axis of the step-support matrix, so it cannot disagree with the adapter.
|
|
109
|
+
* SCOPE, stated plainly because the name oversells it: only `@platform:ios` is caught.
|
|
110
|
+
* A feature tagged `@platform:mobile` runs on whichever OS `MOBILE_PLATFORM` selects at
|
|
111
|
+
* RUN time, so the compiler cannot know — the matrix, not this code, is the net that
|
|
112
|
+
* covers that case. Reported as an E-code and compiled to a loud throw, because the
|
|
113
|
+
* feature has declared the one OS where the step is known not to work.
|
|
114
|
+
* SG-W026 — a mobile ACTIVE-NAVIGATION step that emitted NO executable code, so a promised screen
|
|
115
|
+
* change never happens. `When User open [Settings] page` compiles to the one-line
|
|
116
|
+
* `navigation` template — a comment noting the app was already launched by the wdio
|
|
117
|
+
* capabilities — so the step reads as "go to Settings" and goes nowhere. Pair it with a
|
|
118
|
+
* `page` assertion (itself a no-op, reported as SG-W022) and the scenario passes without
|
|
119
|
+
* ever touching the app. Completes a half-finished class: `route-assertion` already
|
|
120
|
+
* throws and `page-assertion` already reports W022; only `navigation` was silent.
|
|
121
|
+
* Scoped to the ACTIVE forms (`open [X] page`, `navigate to [X] page`). The `is on [X]
|
|
122
|
+
* page|screen` anchor is EXCLUDED on purpose: that is mobile's app-launch directive and
|
|
123
|
+
* its empty output is correct, so warning on it would cry wolf on valid features — and a
|
|
124
|
+
* diagnostic that cries wolf gets filtered out, taking the real ones with it.
|
|
125
|
+
* Like W021/W022/W023 this stays a WARNING by default. `generate --strict` already fails
|
|
126
|
+
* on ANY diagnostic, so this code is blocking under it from the day it lands — which
|
|
127
|
+
* means a project running --strict today with an `open [X] page` step starts failing.
|
|
128
|
+
* That is the intended outcome (the step was never navigating), but it is a migration
|
|
129
|
+
* step, not a silent one: the message names the tap/gesture rewrite.
|
|
94
130
|
* SG-W024 — two or more scenarios in one feature share a name and at least ONE carries a `@reset:`
|
|
95
131
|
* tag. The generated per-scenario lookup is keyed on the scenario NAME (a @cases title is
|
|
96
132
|
* built at runtime, so no compile-time key can match the rendered title), so every
|
|
@@ -118,7 +154,7 @@
|
|
|
118
154
|
* Generation always COMPLETES — the rest of the scenario and the rest of the file still compile.
|
|
119
155
|
*/
|
|
120
156
|
|
|
121
|
-
export type DiagnosticCode = 'SG-W001' | 'SG-W002' | 'SG-W003' | 'SG-W005' | 'SG-W006' | 'SG-W007' | 'SG-W008' | 'SG-W009' | 'SG-W010' | 'SG-W011' | 'SG-W012' | 'SG-W013' | 'SG-W014' | 'SG-W015' | 'SG-E020' | 'SG-E021' | 'SG-W020' | 'SG-W021' | 'SG-W022' | 'SG-W023' | 'SG-W024' | 'SG-E022' | 'SG-E023' | 'SG-E024';
|
|
157
|
+
export type DiagnosticCode = 'SG-W001' | 'SG-W002' | 'SG-W003' | 'SG-W005' | 'SG-W006' | 'SG-W007' | 'SG-W008' | 'SG-W009' | 'SG-W010' | 'SG-W011' | 'SG-W012' | 'SG-W013' | 'SG-W014' | 'SG-W015' | 'SG-W016' | 'SG-E020' | 'SG-E021' | 'SG-W020' | 'SG-W021' | 'SG-W022' | 'SG-W023' | 'SG-W024' | 'SG-W026' | 'SG-E025' | 'SG-E022' | 'SG-E023' | 'SG-E024';
|
|
122
158
|
|
|
123
159
|
export interface Diagnostic {
|
|
124
160
|
code: DiagnosticCode;
|
|
@@ -142,6 +178,7 @@ export class DiagnosticsCollector {
|
|
|
142
178
|
return this.items.length;
|
|
143
179
|
}
|
|
144
180
|
|
|
181
|
+
|
|
145
182
|
/** One line per diagnostic, ready for CLI output. */
|
|
146
183
|
formatLines(): string[] {
|
|
147
184
|
return this.items.map((d) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ParsedStep } from '../../gherkin-parser';
|
|
2
2
|
import { MappedStep } from '../step-mapper';
|
|
3
|
-
import { StepPattern, PatternContext, Platform } from './types';
|
|
3
|
+
import { StepPattern, PatternContext, Platform, StateClause } from './types';
|
|
4
4
|
import { capabilityRegistry } from '../../../capabilities/registry';
|
|
5
5
|
import { discoverAndRegisterCapabilities } from '../../../capabilities/discover';
|
|
6
6
|
import { maskRefsForMatching } from '../utils/mask-refs-for-matching';
|
|
7
7
|
import { unsupportedStepCode } from './unsupported-step-code';
|
|
8
|
+
import { osSupportOfTemplate } from '../adapters/appium/os-support';
|
|
8
9
|
|
|
9
10
|
// Both helpers moved out of this file so the Gherkin parser (state-clause extraction) and the
|
|
10
11
|
// step-mapper (SG-W001 no-pattern fallback) can use them without importing the pattern registry.
|
|
@@ -31,12 +32,18 @@ const TAP_GESTURE_FORMS = /\bdouble[-\s]?tap|\btap(?:s)?\b[^.]*\b(?:top of|at th
|
|
|
31
32
|
* The rewrite is ADDITIVE — `tap` becomes `tap click`, it does not become `click`. Two reasons, and
|
|
32
33
|
* the first one cost a full golden run to learn:
|
|
33
34
|
*
|
|
34
|
-
* 1. `@sungen/driver-mobile`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
35
|
+
* 1. `@sungen/driver-mobile` can be registered on a WEB project. (It is NOT bundled — an earlier
|
|
36
|
+
* version of this comment said it was, which was never true: it is not in `BUNDLED_DRIVERS`
|
|
37
|
+
* and not a core dependency. Discovery simply loaded whatever it could resolve, which in this
|
|
38
|
+
* monorepo is everything; discovery now gates an opt-in platform driver by the project
|
|
39
|
+
* `platform:`, so this reaches web only when both drivers are present and no platform is
|
|
40
|
+
* declared.) Substituting the verb re-keys `mobile-tap` onto "click", where its
|
|
41
|
+
* priority 15 outranks the generic `click-element` (10) — so it captured every plain click on
|
|
42
|
+
* every WEB feature and relabelled it a tap. 8 golden fixtures caught it. Adding a verb instead
|
|
43
|
+
* of swapping one means text with no "tap" in it is returned untouched, so web output cannot
|
|
44
|
+
* change here BY CONSTRUCTION rather than by our vigilance. `platforms:` does not remove the
|
|
45
|
+
* need for that: `mobile-tap` resolves to `click-action`, which both adapters ship, so it is
|
|
46
|
+
* one of the two mobile patterns that must stay UNDECLARED.
|
|
40
47
|
* 2. Both vocabularies then see their own verb, and ordinary PRIORITY decides the winner —
|
|
41
48
|
* no matcher has to learn about the other. `tap … and accept alert` reaches
|
|
42
49
|
* `click-with-alert` (21); `tap … in [T] table row 2` reaches `table-action-in-row-nth` (19);
|
|
@@ -143,6 +150,12 @@ export class PatternRegistry {
|
|
|
143
150
|
const resolved = pattern.resolver(step, context);
|
|
144
151
|
if (!resolved) continue; // resolver declined — fall through
|
|
145
152
|
|
|
153
|
+
// iOS gate — AFTER the resolver, because it needs the template the step actually reached.
|
|
154
|
+
// The sibling of platformMiss above: that one refuses a pattern off-platform, this one
|
|
155
|
+
// refuses a template the declared OS cannot serve.
|
|
156
|
+
const iosMiss = PatternRegistry.iosUnsupported(resolved.templateName, context, step);
|
|
157
|
+
if (iosMiss) return iosMiss;
|
|
158
|
+
|
|
146
159
|
// State-clause gate. Deliberately AFTER the resolver, not before it: a resolver may return
|
|
147
160
|
// null to say "not mine after all", and the search then continues to a lower-priority
|
|
148
161
|
// pattern that may well be the one that asserts the state. Gating earlier judged a pattern
|
|
@@ -190,6 +203,45 @@ export class PatternRegistry {
|
|
|
190
203
|
/**
|
|
191
204
|
* SG-W020 — the matched pattern declares `platforms` and the active one is not among them.
|
|
192
205
|
*/
|
|
206
|
+
/**
|
|
207
|
+
* SG-E025 — the feature says `@platform:ios`, and the template it reached does not work there.
|
|
208
|
+
*
|
|
209
|
+
* Derived from the template body (`osSupportOfTemplate`), never from a list, so it cannot drift
|
|
210
|
+
* from the adapter the way a hand-kept table would. Two states are refused, for different
|
|
211
|
+
* reasons: `refuses` would throw deep inside a device run instead of at compile time, and `noop`
|
|
212
|
+
* would not fail at all — the step would run, do nothing, and the scenario would pass.
|
|
213
|
+
*
|
|
214
|
+
* Only an EXPLICIT `@platform:ios` is gated. `@platform:mobile` picks its OS from
|
|
215
|
+
* MOBILE_PLATFORM at run time, so gating it would mean guessing; the per-OS matrix columns are
|
|
216
|
+
* what cover that case, and they are the primary deliverable of this axis.
|
|
217
|
+
*/
|
|
218
|
+
private static iosUnsupported(
|
|
219
|
+
templateName: string, context: PatternContext, step: ParsedStep
|
|
220
|
+
): MappedStep | null {
|
|
221
|
+
if (context.featurePlatform !== 'ios') return null;
|
|
222
|
+
if ((context.platform ?? 'web') !== 'mobile') return null;
|
|
223
|
+
|
|
224
|
+
const pair = osSupportOfTemplate(templateName);
|
|
225
|
+
// No appium template of that name is a different failure, already reported by template
|
|
226
|
+
// resolution — a second message here would give one cause two names.
|
|
227
|
+
if (!pair || pair.ios === 'yes' || pair.ios === 'inherit') return null;
|
|
228
|
+
|
|
229
|
+
const why = pair.ios === 'noop'
|
|
230
|
+
? 'does nothing on iOS — it is guarded out with no error, so the step would run and the scenario would PASS having done nothing'
|
|
231
|
+
: 'throws on iOS — the template refuses there, so the failure would surface deep inside a device run instead of here';
|
|
232
|
+
const reason =
|
|
233
|
+
`step "${step.text}" compiles to \`${templateName}\`, which ${why}. ` +
|
|
234
|
+
`This feature declares @platform:ios. Either drop to @platform:android (the OS that serves this step), ` +
|
|
235
|
+
`move the step out of the iOS feature, or tag the scenario @manual:M10 (device/OS capability). ` +
|
|
236
|
+
`See the Android/iOS columns in docs/spec/step-support-matrix.md.`;
|
|
237
|
+
|
|
238
|
+
context.diagnostics?.add('SG-E025', reason, { feature: context.featureName, step: step.text });
|
|
239
|
+
return {
|
|
240
|
+
code: unsupportedStepCode(reason),
|
|
241
|
+
comment: `iOS-unsupported step in an @platform:ios feature — see SG-E025`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
193
245
|
private static platformMiss(
|
|
194
246
|
pattern: StepPattern, context: PatternContext, step: ParsedStep
|
|
195
247
|
): MappedStep | null {
|
|
@@ -241,7 +293,8 @@ export class PatternRegistry {
|
|
|
241
293
|
`("${pattern.name}") does not assert that state — the clause would be SILENTLY DROPPED and the ` +
|
|
242
294
|
`step would check something other than what it reads as. Use a step form that owns the state ` +
|
|
243
295
|
`(e.g. "User see [Ref] <type> is ${state}" on the element itself), or drop the clause if the ` +
|
|
244
|
-
`bare assertion is what you meant. If sungen cannot express the check, tag the scenario @manual
|
|
296
|
+
`bare assertion is what you meant. If sungen cannot express the check, tag the scenario @manual.` +
|
|
297
|
+
PatternRegistry.stateMissSkewHint(pattern, state);
|
|
245
298
|
|
|
246
299
|
context.diagnostics?.add('SG-E021', reason, {
|
|
247
300
|
feature: context.featureName,
|
|
@@ -253,6 +306,23 @@ export class PatternRegistry {
|
|
|
253
306
|
};
|
|
254
307
|
}
|
|
255
308
|
|
|
309
|
+
/**
|
|
310
|
+
* The one SG-E021 that is NOT the author's to fix. A pattern NAMED for the state (`is-hidden`,
|
|
311
|
+
* `should-be-disabled`) that declares no `states` at all is not under-annotated — it is a driver
|
|
312
|
+
* built before the field existed, loaded against a core that requires it (a stale project copy of
|
|
313
|
+
* @sungen/driver-ui). The generic message then tells the author to use the exact form they wrote,
|
|
314
|
+
* which cost a QA team a day of Gherkin rewrites that could never work. Name the real cause.
|
|
315
|
+
*/
|
|
316
|
+
private static stateMissSkewHint(pattern: StepPattern, state: StateClause): string {
|
|
317
|
+
const namesTheState = pattern.name.includes(state.replace(/\s+/g, '-'));
|
|
318
|
+
if (pattern.states !== undefined || !namesTheState) return '';
|
|
319
|
+
return (
|
|
320
|
+
` NOTE: "${pattern.name}" is named for this very state yet declares no states at all — that is a ` +
|
|
321
|
+
`@sungen/driver-* package older than sungen itself, not a Gherkin problem. Run \`sungen update\` in the ` +
|
|
322
|
+
`project so the drivers match the sungen version, then generate again.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
256
326
|
/**
|
|
257
327
|
* SG-E020 — the pattern is in-platform but the active adapter ships no template for it. A gap in
|
|
258
328
|
* the adapter, not in the authored Gherkin: name every coordinate a reader needs to act on it.
|
|
@@ -84,6 +84,14 @@ export interface PatternContext {
|
|
|
84
84
|
effectiveKeyword?: string;
|
|
85
85
|
/** The platform the ACTIVE adapter compiles for. Absent = 'web' (back-compat default). */
|
|
86
86
|
platform?: Platform;
|
|
87
|
+
/**
|
|
88
|
+
* The OS the FEATURE declares via `@platform:<mobile|android|ios>` — distinct from `platform`
|
|
89
|
+
* above, which is the adapter. Only `ios` is actionable at compile time: it is the single case
|
|
90
|
+
* where the compiler knows which mobile OS will run, so it can refuse a step that does nothing
|
|
91
|
+
* (or throws) there. `mobile` means both OSes and is deliberately NOT gated — the real OS is
|
|
92
|
+
* chosen at run time by MOBILE_PLATFORM, so a compile-time verdict would be a guess.
|
|
93
|
+
*/
|
|
94
|
+
featurePlatform?: 'mobile' | 'android' | 'ios';
|
|
87
95
|
/** The adapter's own name ('playwright' | 'appium' | …) — quoted verbatim in SG-E020/SG-W020. */
|
|
88
96
|
adapterName?: string;
|
|
89
97
|
/** Compile-diagnostics channel, so the pattern layer can report instead of throwing. */
|
|
@@ -9,6 +9,31 @@ import { unsupportedStepCode } from './patterns/unsupported-step-code';
|
|
|
9
9
|
import { loadAppProfile, ResolvedAppProfile } from './app-profile';
|
|
10
10
|
import { capabilityRegistry } from '../../capabilities/registry';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The correction SG-W001 owes a project that gated a platform driver away.
|
|
14
|
+
*
|
|
15
|
+
* Gating (plan `260903-1654` phase 11) keeps a mobile-only pattern out of a web project's registry.
|
|
16
|
+
* Right isolation — but `platformMiss` then has nothing to catch, so a mobile-only step falls
|
|
17
|
+
* through to SG-W001, whose sentence "sungen has no step form for it" is FALSE: the form exists in
|
|
18
|
+
* a vocabulary this project declined. Ungated, the author would have seen SG-W020 naming the
|
|
19
|
+
* alternative.
|
|
20
|
+
*
|
|
21
|
+
* It states only what discovery knows. Which STEP the skipped driver owned is unknowable here — the
|
|
22
|
+
* patterns were never loaded — and a static list of off-platform step texts would rot on the next
|
|
23
|
+
* pattern anyone adds, putting a second wrong sentence where the first one was.
|
|
24
|
+
*
|
|
25
|
+
* Returns '' when nothing was gated, so every project that predates the platform profile keeps the
|
|
26
|
+
* message it has today, word for word.
|
|
27
|
+
*/
|
|
28
|
+
function offPlatformHint(): string {
|
|
29
|
+
const gated = capabilityRegistry.gatedDrivers();
|
|
30
|
+
if (!gated.length) return '';
|
|
31
|
+
const platform = capabilityRegistry.gatedForPlatform();
|
|
32
|
+
const names = gated.map(([pkg, serves]) => `${pkg} (${serves})`).join(', ');
|
|
33
|
+
return `this project declares platform: ${platform}, so ${names} was not loaded; a ${gated[0][1]}-only step form exists but is out of scope here. Per-platform vocabulary: docs/spec/step-support-matrix.md.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
12
37
|
export interface MappedStep {
|
|
13
38
|
code: string; // Generated Playwright code
|
|
14
39
|
comment?: string; // Optional comment
|
|
@@ -28,6 +53,8 @@ export class StepMapper {
|
|
|
28
53
|
private dataResolver: DataResolver;
|
|
29
54
|
private baseURL: string;
|
|
30
55
|
private featureName?: string;
|
|
56
|
+
/** The feature's declared `@platform:` OS, for the compile-time iOS gate (SG-E025). */
|
|
57
|
+
private featurePlatform?: 'mobile' | 'android' | 'ios';
|
|
31
58
|
private screenName?: string;
|
|
32
59
|
private featurePath?: string;
|
|
33
60
|
private currentScenarioSteps?: ParsedStep[];
|
|
@@ -74,7 +101,9 @@ export class StepMapper {
|
|
|
74
101
|
this.platform = adapter.frameworkName === 'appium' ? 'mobile' : 'web';
|
|
75
102
|
this.templateEngine = new TemplateEngine(templatesDir);
|
|
76
103
|
this.patternRegistry = new PatternRegistry();
|
|
77
|
-
|
|
104
|
+
// `platform` is set just above from the ADAPTER — the resolver needs it to decline the
|
|
105
|
+
// list-member auto-infer that has no mobile equivalent (#665).
|
|
106
|
+
this.selectorResolver = new SelectorResolver(undefined, options.screenName, this.platform);
|
|
78
107
|
this.dataResolver = new DataResolver(undefined, options.screenName, options.runtimeData);
|
|
79
108
|
|
|
80
109
|
// App profile (qa/app.yaml) — loaded after PatternRegistry so capability discovery has
|
|
@@ -100,8 +129,9 @@ export class StepMapper {
|
|
|
100
129
|
/**
|
|
101
130
|
* Set feature context for data and selector resolution
|
|
102
131
|
*/
|
|
103
|
-
setFeatureContext(featureName: string, featurePath?: string): void {
|
|
132
|
+
setFeatureContext(featureName: string, featurePath?: string, featurePlatform?: 'mobile' | 'android' | 'ios'): void {
|
|
104
133
|
this.featureName = featureName;
|
|
134
|
+
this.featurePlatform = featurePlatform;
|
|
105
135
|
this.featurePath = featurePath;
|
|
106
136
|
this.selectorResolver.setFeatureContext(featureName);
|
|
107
137
|
// One StepMapper serves every feature in a run, so the step counter that names the
|
|
@@ -396,6 +426,7 @@ export class StepMapper {
|
|
|
396
426
|
this.checkDataRefConsumed(step, mappedStep);
|
|
397
427
|
this.checkPageAssertionNoOpOnMobile(step, mappedStep);
|
|
398
428
|
this.checkSetupNoOpOnMobile(step, mappedStep);
|
|
429
|
+
this.checkNavigationNoOpOnMobile(step, mappedStep);
|
|
399
430
|
this.checkPageAssertionFallback(step, mappedStep);
|
|
400
431
|
this.checkDeclaredUrlHasNoQuery(step, mappedStep);
|
|
401
432
|
const swallowed = this.checkAssertionClauseSwallowed(step, mappedStep);
|
|
@@ -422,7 +453,13 @@ export class StepMapper {
|
|
|
422
453
|
step: step.text,
|
|
423
454
|
});
|
|
424
455
|
} else {
|
|
425
|
-
|
|
456
|
+
// Two shapes, because the ORIGINAL CLAUSE is what was wrong. Appending a correction to
|
|
457
|
+
// "sungen has no step form for it" leaves the reader a sentence that contradicts itself one
|
|
458
|
+
// clause later. When the gate dropped a vocabulary, that clause is simply not written.
|
|
459
|
+
const hint = offPlatformHint();
|
|
460
|
+
reason = hint
|
|
461
|
+
? `step "${step.text}" matched no pattern here — ${hint} If that is not what you meant, rephrase using a supported step form (see the sungen-gherkin-syntax reference), or tag the scenario @manual if the check cannot be automated.`
|
|
462
|
+
: `step "${step.text}" matched no pattern — sungen has no step form for it. Rephrase using a supported step form (see the sungen-gherkin-syntax reference), or tag the scenario @manual if the check cannot be automated.`;
|
|
426
463
|
this.diagnostics?.add('SG-W001', reason, {
|
|
427
464
|
feature: this.featureName,
|
|
428
465
|
step: step.text,
|
|
@@ -665,6 +702,70 @@ export class StepMapper {
|
|
|
665
702
|
/**
|
|
666
703
|
* Create pattern context for generators
|
|
667
704
|
*/
|
|
705
|
+
/**
|
|
706
|
+
* Whether a mapped step emits anything that actually RUNS.
|
|
707
|
+
*
|
|
708
|
+
* Comments are stripped — block comments as well as line ones, because a template written with
|
|
709
|
+
* `/* … *\/` would otherwise read as executable code and silence the no-op diagnostics exactly
|
|
710
|
+
* when they were needed. Keyed on the emitted CODE rather than on a template name (MappedStep
|
|
711
|
+
* does not carry one), so any future template that degrades to a comment is caught without being
|
|
712
|
+
* enumerated anywhere.
|
|
713
|
+
*
|
|
714
|
+
* Shared by the SG-W022 / SG-W023 / SG-W026 checks — the three of them ask the same question, and
|
|
715
|
+
* three copies of this would drift.
|
|
716
|
+
*/
|
|
717
|
+
private emitsExecutableCode(mapped: MappedStep): boolean {
|
|
718
|
+
return Boolean(
|
|
719
|
+
(mapped.code || '')
|
|
720
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
721
|
+
.split('\n')
|
|
722
|
+
.map((l) => l.replace(/\/\/.*$/, '').trim())
|
|
723
|
+
.filter(Boolean)
|
|
724
|
+
.join(''),
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* SG-W026 — a mobile ACTIVE-NAVIGATION step that emitted no executable code, so a promised screen
|
|
730
|
+
* change never happens.
|
|
731
|
+
*
|
|
732
|
+
* `When User open [Settings] page` compiles to the one-line `navigation` template: a comment
|
|
733
|
+
* noting the app was already launched by the wdio capabilities. The step reads as "go to Settings"
|
|
734
|
+
* and goes nowhere. Pair it with `Then User see [X] page` — itself a no-op, reported as SG-W022 —
|
|
735
|
+
* and the whole scenario passes without ever touching the app.
|
|
736
|
+
*
|
|
737
|
+
* This completes a class that was handled in halves: `route-assertion` already throws loudly and
|
|
738
|
+
* `page-assertion` already reports W022; `navigation` alone was silent.
|
|
739
|
+
*
|
|
740
|
+
* Scoped to the ACTIVE forms. `is on [X] page` and its mobile `screen` twin are mobile's
|
|
741
|
+
* app-LAUNCH anchor, whose empty output is correct and documented as such where IS_ON_TYPES is
|
|
742
|
+
* defined — warning there would cry wolf on every valid mobile feature, and a diagnostic that
|
|
743
|
+
* cries wolf gets filtered out, taking the real ones with it. The exclusion is matched on
|
|
744
|
+
* `is on` FIRST, before the open/navigate test, because "the user is on the page they opened"
|
|
745
|
+
* phrasings contain both.
|
|
746
|
+
*
|
|
747
|
+
* Deliberately NOT a `throw`, for the same reason as SG-W023: an unfulfilled navigation weakens
|
|
748
|
+
* the scenario rather than inverting an assertion, and throwing would break every mobile suite
|
|
749
|
+
* already carrying one of these steps. `generate --strict` is the opt-in that makes it fail.
|
|
750
|
+
*/
|
|
751
|
+
private checkNavigationNoOpOnMobile(step: ParsedStep, mapped: MappedStep): void {
|
|
752
|
+
if (this.platform !== 'mobile' || !this.diagnostics) return;
|
|
753
|
+
|
|
754
|
+
const text = step.text.toLowerCase();
|
|
755
|
+
// The anchor is checked first and wins: it is legitimately empty on mobile.
|
|
756
|
+
if (/\bis\s+on\b/.test(text)) return;
|
|
757
|
+
if (!/\bopens?\b|\bnavigates?\s+to\b|\bgo(es)?\s+to\b/.test(text)) return;
|
|
758
|
+
// Only the navigation TARGET forms — `open [X] page|screen`. `open [X] dialog`/`menu` is a tap
|
|
759
|
+
// on a control, which compiles to real code and never reaches here anyway.
|
|
760
|
+
if (step.elementType && !['page', 'screen'].includes(step.elementType)) return;
|
|
761
|
+
|
|
762
|
+
if (this.emitsExecutableCode(mapped)) return;
|
|
763
|
+
|
|
764
|
+
this.diagnostics.add('SG-W026',
|
|
765
|
+
'this navigation step compiled to a COMMENT — a native app is already launched by the wdio capabilities, so nothing navigates and the following steps run against whatever screen the app happens to be on. Change screens the way a user does, with a tap/gesture on the control that opens it (`When User tap [Settings] button`), and assert a marker on the new screen; keep `is on [X] screen` only as the feature\'s opening anchor',
|
|
766
|
+
{ feature: this.featureName, step: step.text });
|
|
767
|
+
}
|
|
768
|
+
|
|
668
769
|
/**
|
|
669
770
|
* SG-W023 — a mobile SETUP step that emitted no executable code, so a declared precondition is
|
|
670
771
|
* never actually established.
|
|
@@ -699,13 +800,7 @@ export class StepMapper {
|
|
|
699
800
|
// Strip comments; whatever remains is what actually runs. Block comments too — a template written
|
|
700
801
|
// with `/* … */` would otherwise read as executable code and silence this check exactly when it
|
|
701
802
|
// was needed.
|
|
702
|
-
|
|
703
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
704
|
-
.split('\n')
|
|
705
|
-
.map((l) => l.replace(/\/\/.*$/, '').trim())
|
|
706
|
-
.filter(Boolean)
|
|
707
|
-
.join('');
|
|
708
|
-
if (executable) return;
|
|
803
|
+
if (this.emitsExecutableCode(mapped)) return;
|
|
709
804
|
|
|
710
805
|
const remedy = isBackendState
|
|
711
806
|
? 'backend state is not app state — reset it through a test API/script, or bind a `@query:` precondition, the same way the web adapter has to'
|
|
@@ -738,13 +833,7 @@ export class StepMapper {
|
|
|
738
833
|
// Strip comments; whatever remains is what actually runs. Block comments are handled as well as
|
|
739
834
|
// line ones: a template written with `/* … */` would otherwise read as executable code and
|
|
740
835
|
// silently defeat this check — the diagnostic would go quiet exactly when it was needed.
|
|
741
|
-
|
|
742
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
743
|
-
.split('\n')
|
|
744
|
-
.map((l) => l.replace(/\/\/.*$/, '').trim())
|
|
745
|
-
.filter(Boolean)
|
|
746
|
-
.join('');
|
|
747
|
-
if (executable) return;
|
|
836
|
+
if (this.emitsExecutableCode(mapped)) return;
|
|
748
837
|
this.diagnostics.add('SG-W022',
|
|
749
838
|
'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',
|
|
750
839
|
{ feature: this.featureName, step: step.text });
|
|
@@ -779,6 +868,7 @@ export class StepMapper {
|
|
|
779
868
|
renderLocator: (resolved) => this.templateEngine.renderStep('locator', resolved),
|
|
780
869
|
appProfile: this.appProfile,
|
|
781
870
|
platform: this.platform,
|
|
871
|
+
featurePlatform: this.featurePlatform,
|
|
782
872
|
adapterName: this.adapterName,
|
|
783
873
|
diagnostics: this.diagnostics,
|
|
784
874
|
baseURL: this.baseURL,
|