@sun-asterisk/sungen 3.2.24 → 3.2.25
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/context-router.d.ts.map +1 -1
- package/dist/capabilities/context-router.js +15 -7
- package/dist/capabilities/context-router.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +6 -48
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts +14 -0
- package/dist/cli/commands/changed.d.ts.map +1 -0
- package/dist/cli/commands/changed.js +128 -0
- package/dist/cli/commands/changed.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +25 -1
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.js +1 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +19 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -0
- package/dist/cli/commands/results-merge.js +191 -0
- package/dist/cli/commands/results-merge.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +100 -12
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/exporters/api-catalog-loader.d.ts.map +1 -1
- package/dist/exporters/api-catalog-loader.js +10 -3
- package/dist/exporters/api-catalog-loader.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +9 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +55 -3
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +10 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +6 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +10 -0
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/export.js +1 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +5 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +4 -1
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +7 -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 +17 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +68 -8
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/results-merge.d.ts +46 -0
- package/dist/exporters/results-merge.d.ts.map +1 -0
- package/dist/exporters/results-merge.js +148 -0
- package/dist/exporters/results-merge.js.map +1 -0
- package/dist/exporters/selector-key-resolver.d.ts +4 -0
- package/dist/exporters/selector-key-resolver.d.ts.map +1 -1
- package/dist/exporters/selector-key-resolver.js +101 -1
- package/dist/exporters/selector-key-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +8 -1
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.d.ts.map +1 -1
- package/dist/exporters/xlsx-report-builder.js +5 -4
- package/dist/exporters/xlsx-report-builder.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +2 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +52 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +20 -10
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +2 -11
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +2 -11
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/dist/generators/test-generator/code-generator.d.ts +6 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +80 -7
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +60 -6
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +59 -5
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/expect-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/expect-patterns.js +36 -2
- package/dist/generators/test-generator/patterns/expect-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +19 -12
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +58 -38
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +23 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts +18 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js +23 -0
- package/dist/generators/test-generator/patterns/unsupported-step-code.js.map +1 -0
- package/dist/generators/test-generator/recipe-expander.js +9 -1
- package/dist/generators/test-generator/recipe-expander.js.map +1 -1
- package/dist/generators/test-generator/reset-tier.d.ts +68 -0
- package/dist/generators/test-generator/reset-tier.d.ts.map +1 -0
- package/dist/generators/test-generator/reset-tier.js +94 -0
- package/dist/generators/test-generator/reset-tier.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +39 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +144 -5
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +2 -10
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +16 -2
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/format-pattern.d.ts +35 -0
- package/dist/generators/test-generator/utils/format-pattern.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/format-pattern.js +82 -0
- package/dist/generators/test-generator/utils/format-pattern.js.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts +42 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js +142 -0
- package/dist/generators/test-generator/utils/js-literal-scanner.js.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts +18 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js +25 -0
- package/dist/generators/test-generator/utils/mask-refs-for-matching.js.map +1 -0
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts +9 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/runtime-data-transformer.js +124 -63
- package/dist/generators/test-generator/utils/runtime-data-transformer.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +20 -1
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts +14 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js +26 -0
- package/dist/generators/test-generator/utils/spec-syntax-check.js.map +1 -0
- package/dist/harness/annotation-overrides.d.ts +0 -12
- package/dist/harness/annotation-overrides.d.ts.map +1 -1
- package/dist/harness/annotation-overrides.js +9 -5
- package/dist/harness/annotation-overrides.js.map +1 -1
- package/dist/harness/audit.js +4 -4
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability-plan.d.ts +2 -1
- package/dist/harness/capability-plan.d.ts.map +1 -1
- package/dist/harness/capability-plan.js +8 -3
- package/dist/harness/capability-plan.js.map +1 -1
- package/dist/harness/capability.d.ts +11 -0
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +8 -0
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/drivers.yaml +5 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +5 -1
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/run-manifest.d.ts +58 -0
- package/dist/harness/run-manifest.d.ts.map +1 -0
- package/dist/harness/run-manifest.js +264 -0
- package/dist/harness/run-manifest.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +25 -4
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/lockstep-version.d.ts +22 -0
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -0
- package/dist/orchestrator/lockstep-version.js +58 -0
- package/dist/orchestrator/lockstep-version.js.map +1 -0
- package/dist/orchestrator/project-deps-sync.d.ts +39 -0
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -0
- package/dist/orchestrator/project-deps-sync.js +175 -0
- package/dist/orchestrator/project-deps-sync.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +5 -2
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/dist/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/dist/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/dist/orchestrator/templates/env.appium.example +10 -0
- package/dist/orchestrator/templates/specs-api.d.ts +4 -2
- package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-api.js +233 -10
- package/dist/orchestrator/templates/specs-api.js.map +1 -1
- package/dist/orchestrator/templates/specs-api.ts +214 -11
- package/dist/orchestrator/templates/specs-regex-escape.d.ts +13 -0
- package/dist/orchestrator/templates/specs-regex-escape.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.js +19 -0
- package/dist/orchestrator/templates/specs-regex-escape.js.map +1 -0
- package/dist/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/dist/orchestrator/templates/specs-test-data.ts +53 -6
- package/dist/orchestrator/templates/wdio.conf.ts +28 -9
- package/package.json +8 -6
- package/src/capabilities/context-router.ts +13 -5
- package/src/cli/commands/capability.ts +4 -38
- package/src/cli/commands/changed.ts +104 -0
- package/src/cli/commands/delivery.ts +27 -2
- package/src/cli/commands/generate.ts +1 -1
- package/src/cli/commands/results-merge.ts +153 -0
- package/src/cli/commands/update.ts +72 -13
- package/src/cli/index.ts +4 -0
- package/src/exporters/api-catalog-loader.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +59 -3
- package/src/exporters/csv-exporter.ts +11 -2
- package/src/exporters/json-exporter.ts +7 -2
- package/src/exporters/matrix/build.ts +9 -0
- package/src/exporters/matrix/export.ts +1 -1
- package/src/exporters/matrix/render-csv.ts +5 -1
- package/src/exporters/matrix/render-xlsx.ts +4 -1
- package/src/exporters/matrix/types.ts +7 -0
- package/src/exporters/playwright-report-parser.ts +68 -9
- package/src/exporters/results-merge.ts +150 -0
- package/src/exporters/selector-key-resolver.ts +81 -1
- package/src/exporters/types.ts +8 -1
- package/src/exporters/xlsx-report-builder.ts +5 -4
- package/src/generators/gherkin-parser/index.ts +55 -1
- package/src/generators/test-generator/adapters/adapter-interface.ts +23 -1
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/date-picker-unsupported.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/lifecycle-missing-app-id.hbs +5 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/matches-format-assertion.hbs +15 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +185 -26
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +17 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/attribute-assertion.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-value-assertion.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-filter-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/matches-format-assertion.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +4 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/navigation/wait-for-element-with-text.hbs +4 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/recipe-expect-text.hbs +1 -1
- package/src/generators/test-generator/code-generator.ts +86 -7
- package/src/generators/test-generator/diagnostics.ts +60 -6
- package/src/generators/test-generator/patterns/expect-patterns.ts +37 -2
- package/src/generators/test-generator/patterns/index.ts +58 -33
- package/src/generators/test-generator/patterns/types.ts +36 -0
- package/src/generators/test-generator/patterns/unsupported-step-code.ts +19 -0
- package/src/generators/test-generator/recipe-expander.ts +9 -1
- package/src/generators/test-generator/reset-tier.ts +93 -0
- package/src/generators/test-generator/step-mapper.ts +149 -6
- package/src/generators/test-generator/template-engine.ts +18 -3
- package/src/generators/test-generator/utils/format-pattern.ts +79 -0
- package/src/generators/test-generator/utils/js-literal-scanner.ts +132 -0
- package/src/generators/test-generator/utils/mask-refs-for-matching.ts +21 -0
- package/src/generators/test-generator/utils/runtime-data-transformer.ts +126 -76
- package/src/generators/test-generator/utils/selector-resolver.ts +22 -1
- package/src/generators/test-generator/utils/spec-syntax-check.ts +22 -0
- package/src/harness/annotation-overrides.ts +10 -5
- package/src/harness/audit.ts +4 -4
- package/src/harness/capability-plan.ts +10 -5
- package/src/harness/capability.ts +16 -0
- package/src/harness/catalog/drivers.yaml +5 -0
- package/src/harness/parse.ts +5 -1
- package/src/harness/run-manifest.ts +254 -0
- package/src/harness/viewpoint-ledger.ts +26 -5
- package/src/index.ts +1 -0
- package/src/orchestrator/lockstep-version.ts +46 -0
- package/src/orchestrator/project-deps-sync.ts +175 -0
- package/src/orchestrator/project-initializer.ts +5 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +109 -0
- package/src/orchestrator/templates/ai-src/config/claude.md +1 -1
- package/src/orchestrator/templates/ai-src/config/copilot.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md +49 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +110 -6
- package/src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md +8 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +15 -0
- package/src/orchestrator/templates/env.appium.example +10 -0
- package/src/orchestrator/templates/specs-api.ts +214 -11
- package/src/orchestrator/templates/specs-regex-escape.ts +16 -0
- package/src/orchestrator/templates/specs-test-data.ts +53 -6
- package/src/orchestrator/templates/wdio.conf.ts +28 -9
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { scanSegments, type Segment } from './js-literal-scanner';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Wire format of a runtime test-data marker: `__SUNGEN_TD$<key>$__`.
|
|
3
5
|
*
|
|
@@ -14,12 +16,12 @@
|
|
|
14
16
|
* swallowed one `_` of the `___` run and decoding produced `get('code_')` plus
|
|
15
17
|
* a detached `ACCESS_TOKEN`. `$` is also identifier-safe — the whole marker
|
|
16
18
|
* still parses as ONE bare JS token, so it survives the bare-identifier
|
|
17
|
-
* (numeric) positions
|
|
19
|
+
* (numeric) positions the code context handles.
|
|
18
20
|
*
|
|
19
|
-
* Single source of truth: every
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Single source of truth: every context below is built from these constants.
|
|
22
|
+
* Invariant: every marker producer goes through DataResolver.encodeMarker and
|
|
23
|
+
* every consumer through this module — a drifting hand-copied char class is what
|
|
24
|
+
* let the suffix bug hide.
|
|
23
25
|
*/
|
|
24
26
|
const MARKER_KEY = '[A-Za-z0-9_-]+';
|
|
25
27
|
// Templates that embed a value into a regex literal run it through the escapeRegex
|
|
@@ -27,100 +29,148 @@ const MARKER_KEY = '[A-Za-z0-9_-]+';
|
|
|
27
29
|
// arrive here spelled `__SUNGEN_TD\$key\$__`. `\\\\?` accepts that optional backslash
|
|
28
30
|
// before each delimiter; the replacement consumes it, so no escape leaks to output.
|
|
29
31
|
const DELIM = '\\\\?\\$';
|
|
30
|
-
/** Marker with no capture group — for "contains a marker"
|
|
32
|
+
/** Marker with no capture group — for "contains a marker" probes. */
|
|
31
33
|
const MARKER = `__SUNGEN_TD${DELIM}${MARKER_KEY}${DELIM}__`;
|
|
32
34
|
/** Marker capturing the encoded key. */
|
|
33
35
|
const MARKER_CAP = `__SUNGEN_TD${DELIM}(${MARKER_KEY})${DELIM}__`;
|
|
34
36
|
|
|
35
37
|
const MARKER_PATTERN = new RegExp(MARKER);
|
|
36
|
-
|
|
37
|
-
const RE_COMMENT_LINE = new RegExp(`^(\\s*)\\/\\/(.*)${MARKER_CAP}(.*)$`, 'gm');
|
|
38
|
-
// Pass 1: a quoted string literal containing at least one marker. Covers ', " and
|
|
39
|
-
// backtick containers; `\\.` lets an ESCAPED quote (`'it\'s {{x}}'`) sit inside the
|
|
40
|
-
// body instead of terminating the literal early. The single-char branch excludes `\`
|
|
41
|
-
// so every backslash is consumed by exactly ONE branch — an ambiguous `(?!\1).`
|
|
42
|
-
// alternative enumerates 2^n segmentations when a marker-less escape-heavy literal
|
|
43
|
-
// (escapeQuotes output: multi-line Flutter content-desc) fails to match, hanging
|
|
44
|
-
// the generate run. `\n` exclusion just makes the dot's no-newline behavior explicit.
|
|
45
|
-
const RE_STRING_LITERAL = new RegExp('([\'"`])((?:\\\\.|(?!\\1)[^\\\\\\n])*?' + MARKER + '(?:\\\\.|(?!\\1)[^\\\\\\n])*)\\1', 'g');
|
|
46
|
-
// Pass 2: a single-line regex literal containing at least one marker.
|
|
47
|
-
const RE_REGEX_LITERAL = new RegExp(`\\/((?:[^/\\\\\\n]|\\\\.)*?${MARKER}(?:[^/\\\\\\n]|\\\\.)*)\\/([gimsuy]*)`, 'g');
|
|
48
|
-
// Whole-literal probe: the body is EXACTLY one marker (bare accessor call, no template).
|
|
38
|
+
/** Whole-literal probe: the body is EXACTLY one marker (bare accessor call, no template). */
|
|
49
39
|
const RE_WHOLE_MARKER = new RegExp(`^${MARKER_CAP}$`);
|
|
50
|
-
|
|
40
|
+
/** Every marker occurrence, capturing the key. */
|
|
51
41
|
const RE_MARKER_G = new RegExp(MARKER_CAP, 'g');
|
|
52
42
|
|
|
53
43
|
/**
|
|
54
44
|
* Replace __SUNGEN_TD$…$__ markers with testData.get() calls in generated code.
|
|
55
|
-
*
|
|
45
|
+
*
|
|
46
|
+
* The code is scanned ONCE into literal/regex/comment/code segments and each
|
|
47
|
+
* segment is rewritten for the context it is actually in. This replaced four
|
|
48
|
+
* independent global-regex passes whose ordering was the defect: the string pass
|
|
49
|
+
* ran first and, finding no marker inside a genuine literal, re-matched from that
|
|
50
|
+
* literal's CLOSING quote — pairing it with the OPENING quote of the next literal
|
|
51
|
+
* and rewriting the regex literal between them into a broken backtick template
|
|
52
|
+
* (`getByText('Tìm thấy`)` … `state: `visible' })`, a .spec.ts that would not
|
|
53
|
+
* parse at all). See js-literal-scanner.ts for the full field report.
|
|
56
54
|
*/
|
|
57
55
|
export function transformToRuntimeData(code: string, accessor: string = 'testData'): string {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// — e.g. a URL `https://careerforum.aki-inc.jp/` in a selector name — is NOT mistaken
|
|
62
|
-
// for a comment. Before this anchor, such a URL preceding a marker on the same line
|
|
63
|
-
// made Pass 0 strip the marker to its bare key, so Pass 1 never wrapped it in
|
|
64
|
-
// testData.get() (silent: `toHaveAttribute('href', 'official_site_url')`). Generated
|
|
65
|
-
// comments are always full-line, so anchoring loses nothing.
|
|
66
|
-
code = code.replace(
|
|
67
|
-
RE_COMMENT_LINE,
|
|
68
|
-
(_, indent, before, enc, after) => `${indent}//${before}${decodeKey(enc)}${after}`
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Pass 1: String literal context — a literal may carry SEVERAL markers
|
|
72
|
-
// ('/{{code}}/car-search?{{prefill}}' compiles to two markers in ONE literal).
|
|
73
|
-
// Match the whole quoted literal that contains at least one marker, then
|
|
74
|
-
// interpolate EVERY marker inside it. The old shape (one marker + lazy
|
|
75
|
-
// prefix/suffix) swallowed trailing markers into its suffix verbatim; they
|
|
76
|
-
// escaped the quote context (the literal had become a backtick template) and
|
|
77
|
-
// Pass 3's numeric catch-all emitted them as literal
|
|
78
|
-
// `Number(testData.get(...))` TEXT inside the template — a compiling spec
|
|
79
|
-
// navigating to a garbage URL.
|
|
80
|
-
// 'marker' → testData.get('key')
|
|
81
|
-
// 'a__m1__b__m2__c' → `a${testData.get('k1')}b${testData.get('k2')}c`
|
|
82
|
-
code = code.replace(
|
|
83
|
-
RE_STRING_LITERAL,
|
|
84
|
-
(_, _quote, body) => {
|
|
85
|
-
const whole = RE_WHOLE_MARKER.exec(body);
|
|
86
|
-
if (whole) return `${accessor}.get('${decodeKey(whole[1])}')`;
|
|
87
|
-
return `\`${interpolateMarkers(body, accessor)}\``;
|
|
88
|
-
}
|
|
89
|
-
);
|
|
56
|
+
if (!MARKER_PATTERN.test(code)) return code;
|
|
57
|
+
return scanSegments(code).map((seg) => transformSegment(seg, accessor)).join('');
|
|
58
|
+
}
|
|
90
59
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
code = code.replace(
|
|
95
|
-
RE_REGEX_LITERAL,
|
|
96
|
-
(_, body, flags) => {
|
|
97
|
-
const flagStr = flags ? `, '${flags}'` : '';
|
|
98
|
-
const whole = RE_WHOLE_MARKER.exec(body);
|
|
99
|
-
if (whole) return `new RegExp(${accessor}.get('${decodeKey(whole[1])}')${flagStr})`;
|
|
100
|
-
return `new RegExp(\`${interpolateMarkers(body, accessor)}\`${flagStr})`;
|
|
101
|
-
}
|
|
102
|
-
);
|
|
60
|
+
export function hasRuntimeDataMarkers(code: string): boolean {
|
|
61
|
+
return MARKER_PATTERN.test(code);
|
|
62
|
+
}
|
|
103
63
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
64
|
+
function transformSegment(seg: Segment, accessor: string): string {
|
|
65
|
+
if (!MARKER_PATTERN.test(seg.text)) return seg.text;
|
|
66
|
+
switch (seg.kind) {
|
|
67
|
+
// Decode to the plain key: a marker in a comment documents the ref, it is not code.
|
|
68
|
+
case 'comment':
|
|
69
|
+
return seg.text.replace(RE_MARKER_G, (_, enc) => decodeKey(enc));
|
|
70
|
+
case 'string':
|
|
71
|
+
return transformStringLiteral(seg.text, accessor);
|
|
72
|
+
case 'regex':
|
|
73
|
+
return transformRegexLiteral(seg.text, accessor);
|
|
74
|
+
// Bare-token position (e.g. `toHaveCount(__marker__)` from the table/list count
|
|
75
|
+
// templates). testData.get() returns a string, so coerce with Number().
|
|
76
|
+
case 'code':
|
|
77
|
+
return seg.text.replace(RE_MARKER_G, (_, enc) => `Number(${accessor}.get('${decodeKey(enc)}'))`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
111
80
|
|
|
112
|
-
|
|
81
|
+
/**
|
|
82
|
+
* 'marker' → testData.get('key')
|
|
83
|
+
* 'a__m1__b__m2__c' → `a${testData.get('k1')}b${testData.get('k2')}c`
|
|
84
|
+
*
|
|
85
|
+
* A literal may carry SEVERAL markers ('/{{code}}/car-search?{{prefill}}' compiles
|
|
86
|
+
* to two markers in ONE literal), so every marker inside it is interpolated.
|
|
87
|
+
*/
|
|
88
|
+
function transformStringLiteral(text: string, accessor: string): string {
|
|
89
|
+
const quote = text[0];
|
|
90
|
+
const body = text.slice(1, -1);
|
|
91
|
+
const whole = RE_WHOLE_MARKER.exec(body);
|
|
92
|
+
if (whole) return `${accessor}.get('${decodeKey(whole[1])}')`;
|
|
93
|
+
return `\`${interpolateMarkers(escapeForTemplate(body, quote === '`'), accessor)}\``;
|
|
113
94
|
}
|
|
114
95
|
|
|
115
|
-
|
|
116
|
-
|
|
96
|
+
/**
|
|
97
|
+
* /…$marker$…/flags → new RegExp(`…`, 'flags'). Same several-markers rule as above.
|
|
98
|
+
*
|
|
99
|
+
* Two regex-only twists on top of the string path:
|
|
100
|
+
* - The runtime VALUE is literal text (test-data holds data, not patterns), so every
|
|
101
|
+
* accessor is wrapped in the runtime escapeRegExp helper — a value containing
|
|
102
|
+
* `( [ $ . * +` must not throw a SyntaxError or silently widen the match. The helper
|
|
103
|
+
* lives in the synced specs/regex-escape.ts; the generator imports it when the
|
|
104
|
+
* transformed body references it. Templates that intend PATTERN semantics (attribute
|
|
105
|
+
* `pattern:`) emit a string container instead of a regex literal, so they skip this.
|
|
106
|
+
* - Marker-less SEGMENTS get their backslashes doubled for the template context: regex
|
|
107
|
+
* source text like a hand-written `\s*` (or escapeRegex output `\)`) means one thing
|
|
108
|
+
* in a /…/ literal but loses its backslash when the template cooks one escape level —
|
|
109
|
+
* the constructed RegExp would read `s*` / a bare `)`. Segments are escaped AFTER the
|
|
110
|
+
* markers are cut out, so an escapeRegex-spelled marker delimiter (`\$`) is never
|
|
111
|
+
* double-escaped into something the marker regex can no longer see.
|
|
112
|
+
*/
|
|
113
|
+
function transformRegexLiteral(text: string, accessor: string): string {
|
|
114
|
+
// Flags never contain `/`, so the last slash always closes the pattern.
|
|
115
|
+
const close = text.lastIndexOf('/');
|
|
116
|
+
const body = text.slice(1, close);
|
|
117
|
+
const flags = text.slice(close + 1);
|
|
118
|
+
const flagStr = flags ? `, '${flags}'` : '';
|
|
119
|
+
const whole = RE_WHOLE_MARKER.exec(body);
|
|
120
|
+
if (whole) return `new RegExp(escapeRegExp(${accessor}.get('${decodeKey(whole[1])}'))${flagStr})`;
|
|
121
|
+
return `new RegExp(\`${interpolateMarkersInRegexBody(body, accessor)}\`${flagStr})`;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function decodeKey(encoded: string): string {
|
|
120
125
|
return encoded.replace(/_D_/g, '.');
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
/** Replace every marker in a
|
|
128
|
+
/** Replace every marker in a body with a `${accessor.get('key')}` interpolation. */
|
|
124
129
|
function interpolateMarkers(body: string, accessor: string): string {
|
|
125
130
|
return body.replace(RE_MARKER_G, (_, enc) => `\${${accessor}.get('${decodeKey(enc)}')}`);
|
|
126
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Regex-body variant: markers are cut out FIRST, each accessor is wrapped in the
|
|
135
|
+
* runtime escapeRegExp helper, and only the marker-less segments between them are
|
|
136
|
+
* re-escaped for the template context (backslashes doubled, backtick/`${` neutralized).
|
|
137
|
+
*/
|
|
138
|
+
function interpolateMarkersInRegexBody(body: string, accessor: string): string {
|
|
139
|
+
let out = '';
|
|
140
|
+
let last = 0;
|
|
141
|
+
for (const m of body.matchAll(RE_MARKER_G)) {
|
|
142
|
+
out += escapeRegexSegmentForTemplate(body.slice(last, m.index!)) +
|
|
143
|
+
`\${escapeRegExp(${accessor}.get('${decodeKey(m[1])}'))}`;
|
|
144
|
+
last = m.index! + m[0].length;
|
|
145
|
+
}
|
|
146
|
+
return out + escapeRegexSegmentForTemplate(body.slice(last));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Regex pattern text moving into a template literal: the template layer cooks one
|
|
151
|
+
* level of escapes BEFORE RegExp ever sees the string, so `\)` arrives as a bare `)`
|
|
152
|
+
* (SyntaxError: unmatched paren) and `\.` as a match-anything dot. Double the
|
|
153
|
+
* backslashes and neutralize the template's own metacharacters so the constructed
|
|
154
|
+
* RegExp source equals the original pattern text.
|
|
155
|
+
*/
|
|
156
|
+
const escapeRegexSegmentForTemplate = (segment: string): string =>
|
|
157
|
+
segment.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Both rewrites move a body into a backtick template, where a raw backtick or `${`
|
|
161
|
+
* would be read as syntax rather than text — a `'…'` selector value or a regex
|
|
162
|
+
* pattern is allowed to contain either (escapeQuotes/escapeRegex escape neither).
|
|
163
|
+
* Escape before interpolating so the `${…}` this module inserts is the only live one.
|
|
164
|
+
* An already-backtick container needs no pass: its body was valid template text.
|
|
165
|
+
*
|
|
166
|
+
* PRECONDITION: the body must already have its backslashes DOUBLED — which every caller
|
|
167
|
+
* satisfies, because every dynamic value reaches a literal through escapeQuotes or
|
|
168
|
+
* escapeRegex (template-engine.ts) and both double `\` before anything else. An unpaired
|
|
169
|
+
* backslash immediately before a backtick would make the pass below emit `\\\``, i.e. an
|
|
170
|
+
* escaped backslash followed by a LIVE backtick, ending the template early. If you
|
|
171
|
+
* hand-write a backslash into a .hbs template, keep it paired.
|
|
172
|
+
*/
|
|
173
|
+
function escapeForTemplate(body: string, alreadyTemplate: boolean): string {
|
|
174
|
+
if (alreadyTemplate) return body;
|
|
175
|
+
return body.replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
176
|
+
}
|
|
@@ -229,6 +229,24 @@ export class SelectorResolver {
|
|
|
229
229
|
* - Legacy format: "login:email_field" or "login.email_field" (with screen prefix)
|
|
230
230
|
*/
|
|
231
231
|
resolveSelector(selectorRef: string, featureName?: string, elementType?: string, nth?: number): ResolvedSelector {
|
|
232
|
+
// A [Reference] is a selectors.yaml KEY, looked up verbatim (generateKey only lowercases and
|
|
233
|
+
// normalizes whitespace) — it is never interpolated. Embedding a {{var}} in it is therefore
|
|
234
|
+
// unsupported, and silence was the harmful part: with no matching entry the ref fell through
|
|
235
|
+
// to auto-infer and produced getByRole(..., { name: '{{var}}' }) — an accessible name that can
|
|
236
|
+
// never match — so the run failed far from the cause. Fail here instead, naming the two
|
|
237
|
+
// supported ways to target dynamic text. Browser-storage keys legitimately embed vars but are
|
|
238
|
+
// not selectors and never reach this method (see driver-ui storage-patterns).
|
|
239
|
+
const templateVarInRef = /\{\{\s*([^}]+?)\s*\}\}/.exec(selectorRef);
|
|
240
|
+
if (templateVarInRef) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Selector reference "[${selectorRef}]" embeds a template variable ({{${templateVarInRef[1]}}}). ` +
|
|
243
|
+
`A [Reference] is a selectors.yaml key and is matched verbatim, not interpolated. ` +
|
|
244
|
+
`To target text that varies (locale, @cases column), keep the reference static and put the ` +
|
|
245
|
+
`variable in the entry instead — e.g. "confirm answer": { type: role, value: button, name: "{{${templateVarInRef[1]}}}" } ` +
|
|
246
|
+
`used as [Confirm Answer] button. To pick an element by its text inside a container, use ` +
|
|
247
|
+
`\`with {{${templateVarInRef[1]}}}\` (e.g. User click [Answer List] item with {{${templateVarInRef[1]}}}).`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
232
250
|
const contextName = featureName || this.featureName;
|
|
233
251
|
|
|
234
252
|
// Check if it's legacy format (contains : or has dot with screen prefix)
|
|
@@ -590,7 +608,9 @@ export class SelectorResolver {
|
|
|
590
608
|
});
|
|
591
609
|
}
|
|
592
610
|
|
|
593
|
-
// If type is 'locator', use locator field (or value as fallback) as the CSS locator directly
|
|
611
|
+
// If type is 'locator', use locator field (or value as fallback) as the CSS locator directly.
|
|
612
|
+
// `exact` travels along: a locator LIST filtered by a test-data value (hasText) matches by
|
|
613
|
+
// substring by default, and `exact: true` in the YAML opts into the anchored full-text match.
|
|
594
614
|
if (type === 'locator') {
|
|
595
615
|
const locatorValue = locator && locator.trim() ? locator : value;
|
|
596
616
|
return withExtras({
|
|
@@ -598,6 +618,7 @@ export class SelectorResolver {
|
|
|
598
618
|
value: locatorValue,
|
|
599
619
|
locator: locatorValue,
|
|
600
620
|
nth,
|
|
621
|
+
exact,
|
|
601
622
|
});
|
|
602
623
|
}
|
|
603
624
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compiler self-check for emitted spec files.
|
|
5
|
+
*
|
|
6
|
+
* Every template and post-processor in the generator assembles TEXT, so a delimiter
|
|
7
|
+
* bug in any one of them produces a file Playwright cannot parse AT ALL: one
|
|
8
|
+
* SyntaxError, zero scenarios run, and nothing points at the compiler. That is how the
|
|
9
|
+
* runtime-data quote-pairing defect reached a user (3.2.23) — `sungen generate` printed
|
|
10
|
+
* success and the breakage only surfaced a step later, at `npx playwright test`.
|
|
11
|
+
*
|
|
12
|
+
* Parsing what we are about to write costs one parse per spec file and makes the
|
|
13
|
+
* compiler name its own bad output at the moment it makes it (SG-E022).
|
|
14
|
+
*/
|
|
15
|
+
export function findSyntaxError(code: string): string | null {
|
|
16
|
+
try {
|
|
17
|
+
parse(code, { sourceType: 'module', plugins: ['typescript'] });
|
|
18
|
+
return null;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return error instanceof Error ? error.message : String(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Parses `name(a={{x}},b="lit",c=3)` overrides into a map of JS expressions, e.g.
|
|
5
5
|
* `{ a: "testData.raw('x')", b: "\"lit\"", c: "3" }`. Used by the DB and API capability drivers'
|
|
6
|
-
* precondition codegen; lives in core so both drivers can share it. Gherkin tags carry no
|
|
7
|
-
*
|
|
6
|
+
* precondition codegen; lives in core so both drivers can share it. Gherkin tags carry no
|
|
7
|
+
* whitespace, but a QUOTED value can still embed `{{ref}}` next to static text
|
|
8
|
+
* (`user_email="pre-{{id}}-x"`) — those interpolate through literalExpr; shipping the braces
|
|
9
|
+
* verbatim would bind `{{...}}` text as a SQL parameter or POST it in a live request body.
|
|
10
|
+
* Flows thread a prior response via a whole-value ref, e.g.
|
|
8
11
|
* `@api:get_profile(token={{login.body.token}})`, with the auth scheme declared in the catalog header.
|
|
9
|
-
* A `{{ref}}` override binds into a typed sink (DB query param, JSON request body), so it
|
|
10
|
-
* via `testData.raw()` (native type, still interpolates embedded refs) rather than `get()`
|
|
12
|
+
* A bare `{{ref}}` override binds into a typed sink (DB query param, JSON request body), so it
|
|
13
|
+
* resolves via `testData.raw()` (native type, still interpolates embedded refs) rather than `get()`
|
|
11
14
|
* (Gherkin-text stringify) — a numeric override otherwise reaches the DB/API as a JSON string.
|
|
12
15
|
*/
|
|
16
|
+
import { literalExpr } from '../generators/test-generator/patterns/expect-patterns';
|
|
17
|
+
|
|
13
18
|
export function parseQueryOverrides(raw?: string): Record<string, string> {
|
|
14
19
|
const out: Record<string, string> = {};
|
|
15
20
|
if (!raw) return out;
|
|
@@ -22,7 +27,7 @@ export function parseQueryOverrides(raw?: string): Record<string, string> {
|
|
|
22
27
|
const v = val.match(/^\{\{\s*([^}]+?)\s*\}\}$/);
|
|
23
28
|
const q = val.match(/^["'](.*)["']$/);
|
|
24
29
|
if (v) out[key] = `testData.raw(${JSON.stringify(v[1])})`;
|
|
25
|
-
else if (q) out[key] =
|
|
30
|
+
else if (q) out[key] = literalExpr(q[1]);
|
|
26
31
|
else if (/^-?\d+(?:\.\d+)?$/.test(val)) out[key] = val;
|
|
27
32
|
else out[key] = JSON.stringify(val);
|
|
28
33
|
}
|
package/src/harness/audit.ts
CHANGED
|
@@ -460,7 +460,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
// #595 — a viewpoint item naming a browser-level gesture, answered with an in-app control.
|
|
463
|
-
for (const g of browserGestureSubstitutions(viewpointPath, scenarios).slice(0, 4)) {
|
|
463
|
+
for (const g of browserGestureSubstitutions(viewpointPath, scenarios, platform).slice(0, 4)) {
|
|
464
464
|
findings.push(`VIEWPOINT-GESTURE-SUBSTITUTED: the viewpoint asks for "${g.gesture}" ("${g.item.slice(0, 80)}…") but the suite exercises an in-app control instead. They are different mechanisms — an app button runs the screen's own re-hydrate logic, browser history does not, and a defect that only shows on history navigation lives in exactly that gap. The step vocabulary has no reload/back step (#593), so defer it with \`@manual\` naming the gesture rather than substituting silently.`);
|
|
465
465
|
}
|
|
466
466
|
// #595 — a flow whose requirement list is a hand restatement of the screen specs it
|
|
@@ -643,7 +643,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
643
643
|
if (intent.capabilitySuggestions) {
|
|
644
644
|
// The catalog unit id (`flows/<f>`, `api/<a>`) carries the unit KIND, which gates which
|
|
645
645
|
// drivers can serve it at all.
|
|
646
|
-
const plan = buildPlan(screenDir, screenName, catalogScreenName);
|
|
646
|
+
const plan = buildPlan(screenDir, screenName, catalogScreenName, platform);
|
|
647
647
|
if (plan.recommendations.length) {
|
|
648
648
|
const recs = plan.recommendations.map((r) => `\`sungen capability add ${r.driver}\` (automates ${r.count})`).join(' · ');
|
|
649
649
|
findings.push(`CAPABILITY-SUGGESTION: ${plan.capabilityManual} @manual scenario(s) are capability-manual (a driver could automate them) — ${recs}. Recommend-only: nothing is installed automatically; the ${plan.judgmentManual} judgment-manual (M6/M8/M9) correctly stay manual.`);
|
|
@@ -651,8 +651,8 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
651
651
|
// Named, not dropped: without this the scenarios read as "nothing could help", and the
|
|
652
652
|
// operator's only clue was an install that dead-ends (#597).
|
|
653
653
|
if (plan.unavailable.length) {
|
|
654
|
-
const un = plan.unavailable.map((r) => `${r.driver} (would automate ${r.count})`).join(' · ');
|
|
655
|
-
findings.push(`CAPABILITY-UNAVAILABLE: ${un}
|
|
654
|
+
const un = plan.unavailable.map((r) => `${r.driver} (would automate ${r.count}) — ${r.why}`).join(' · ');
|
|
655
|
+
findings.push(`CAPABILITY-UNAVAILABLE: ${un}. Do NOT install: the scenarios stay @manual until the driver gains that support, and installing it buys an install, a dead end and a manual rollback. ${plan.unavailable.some((r) => /no .* support/.test(r.why)) ? 'On this platform, verify the same behaviour through `@api`/`@query` against the real dependency instead.' : 'Automating the same behaviour on the owning SCREEN unit is the available route today.'}`);
|
|
656
656
|
}
|
|
657
657
|
}
|
|
658
658
|
// TQ-11b — automation-ready (pending capability): @requires:<cap> scenarios whose cap isn't enabled.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
-
import { loadDriverCatalog, unitKindOf, driverServesUnit } from './capability';
|
|
11
|
+
import { loadDriverCatalog, driverMeta, unitKindOf, driverServesUnit, driverServesPlatform } from './capability';
|
|
12
12
|
import { readTextFile } from './read-text';
|
|
13
13
|
import { featureFilesFor } from './unit-paths';
|
|
14
14
|
|
|
@@ -197,11 +197,11 @@ export interface CapabilityPlan {
|
|
|
197
197
|
* dropped: the scenarios stay manual for a reason the operator needs to know, and a silent
|
|
198
198
|
* omission would read as "nothing could help here" (#597).
|
|
199
199
|
*/
|
|
200
|
-
unavailable: { driver: string; reason: string; count: number; unitKind: string }[];
|
|
200
|
+
unavailable: { driver: string; reason: string; count: number; unitKind: string; why: string }[];
|
|
201
201
|
keep: { code: string; count: number }[];
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
export function buildPlan(screenDir: string, screenName: string, unitId = screenName): CapabilityPlan {
|
|
204
|
+
export function buildPlan(screenDir: string, screenName: string, unitId = screenName, platform?: string): CapabilityPlan {
|
|
205
205
|
const scenarios = featureFilesFor(screenDir, screenName).flatMap(parseScenarios);
|
|
206
206
|
const catalog = loadDriverCatalog();
|
|
207
207
|
// Recommending a driver that cannot serve this unit KIND costs an install, a dead end and a
|
|
@@ -241,14 +241,19 @@ export function buildPlan(screenDir: string, screenName: string, unitId = screen
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
// Two axes, same guarantee: a driver must be able to serve this unit KIND and this PLATFORM.
|
|
245
|
+
const serves = (d: string): boolean => driverServesUnit(d, unitKind) && driverServesPlatform(d, platform);
|
|
246
|
+
const servable = [...recByDriver.entries()].filter(([d]) => serves(d));
|
|
245
247
|
const unavailable = [...recByDriver.entries()]
|
|
246
|
-
.filter(([d]) => !
|
|
248
|
+
.filter(([d]) => !serves(d))
|
|
247
249
|
.map(([driver, v]) => ({
|
|
248
250
|
driver,
|
|
249
251
|
reason: v.reason,
|
|
250
252
|
count: v.scenarios.length,
|
|
251
253
|
unitKind,
|
|
254
|
+
why: !driverServesUnit(driver, unitKind)
|
|
255
|
+
? `does not support a ${unitKind} unit`
|
|
256
|
+
: `has no ${platform} support (its step patterns are ${(driverMeta(driver)?.platforms ?? []).join('/')}-only)`,
|
|
252
257
|
}))
|
|
253
258
|
.sort((a, b) => b.count - a.count);
|
|
254
259
|
const recommendations = servable
|
|
@@ -81,6 +81,15 @@ export interface DriverMeta {
|
|
|
81
81
|
* driver on a flow: install, dead end, manual rollback (#597).
|
|
82
82
|
*/
|
|
83
83
|
units?: Array<'screen' | 'flow' | 'api'>;
|
|
84
|
+
/**
|
|
85
|
+
* Platforms this driver can serve. Absent = all.
|
|
86
|
+
*
|
|
87
|
+
* The same guarantee as `units`, on the other axis. Step patterns already gate themselves
|
|
88
|
+
* (`platforms: ['web']` → SG-W020 at compile time), but the PLANNER did not know, so a mobile
|
|
89
|
+
* project was still told to install a web-only driver — install, dead end, manual rollback,
|
|
90
|
+
* exactly as in #597 (#626).
|
|
91
|
+
*/
|
|
92
|
+
platforms?: string[];
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
/** The unit kind of a catalog unit id: `flows/<f>` → flow, `api/<a>` → api, else screen. */
|
|
@@ -97,6 +106,13 @@ export function driverServesUnit(driverId: string, kind: 'screen' | 'flow' | 'ap
|
|
|
97
106
|
return !units || units.includes(kind);
|
|
98
107
|
}
|
|
99
108
|
|
|
109
|
+
/** Can this driver serve that platform? No declared platform on either side → assume yes. */
|
|
110
|
+
export function driverServesPlatform(driverId: string, platform?: string): boolean {
|
|
111
|
+
const platforms = driverMeta(driverId)?.platforms;
|
|
112
|
+
if (!platforms || !platform) return true;
|
|
113
|
+
return platforms.includes(platform.toLowerCase());
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
export function capabilitiesPath(cwd: string): string {
|
|
101
117
|
return path.join(cwd, 'qa', 'capabilities.yaml');
|
|
102
118
|
}
|
|
@@ -66,6 +66,11 @@ drivers:
|
|
|
66
66
|
# (mockCatalogPath is hardcoded to that shape), so it cannot serve a flow. Recommending
|
|
67
67
|
# it for one sent an operator through an install, a dead end and a manual rollback (#597).
|
|
68
68
|
units: [screen]
|
|
69
|
+
# `platforms:` is the same guarantee on the other axis. Absent → every platform.
|
|
70
|
+
# `mock.install` is Playwright route interception (page.route) and its step patterns already
|
|
71
|
+
# declare `platforms: ['web']`, so on mobile they compile to SG-W020. The PLANNER did not know
|
|
72
|
+
# that, so a mobile project was still told to install it — the #597 dead end, one axis over.
|
|
73
|
+
platforms: [web]
|
|
69
74
|
mail-file:
|
|
70
75
|
kind: capability
|
|
71
76
|
package: "@sungen/driver-mail-file"
|
package/src/harness/parse.ts
CHANGED
|
@@ -230,7 +230,11 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
230
230
|
// assert persisted state — without this they'd be scored `shallow`, penalizing
|
|
231
231
|
// exactly the checks the review encourages. `tab order` is a STRUCTURAL
|
|
232
232
|
// assertion (a data table of focus stops) with the same no-{{ref}} property.
|
|
233
|
-
|
|
233
|
+
// `matches "<format>"` belongs here for the same reason: it carries no {{ref}} because
|
|
234
|
+
// the contract IS the shape ("Format: yyyy/mm/dd ~ yyyy/mm/dd"), and it is a stricter
|
|
235
|
+
// check than the value assert it replaces — scoring it shallow would penalize the
|
|
236
|
+
// stronger assertion.
|
|
237
|
+
if (/\{\{|contains|match data|matches\s+"|toHaveText|in (local|session) storage|tab order/i.test(step.text)) hasData = true;
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
240
|
|