@sun-asterisk/sungen 3.2.31 → 3.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +13 -2
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +46 -17
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +39 -18
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +14 -7
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +2 -12
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
- package/dist/cli/commands/results-sanitize.js +2 -13
- package/dist/cli/commands/results-sanitize.js.map +1 -1
- package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
- package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
- package/dist/cli/commands/setup-mobile-env.js +65 -0
- package/dist/cli/commands/setup-mobile-env.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +6 -10
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +21 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +32 -1
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/locale-variants.d.ts +7 -1
- package/dist/exporters/locale-variants.d.ts.map +1 -1
- package/dist/exporters/locale-variants.js +23 -16
- package/dist/exporters/locale-variants.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +13 -7
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +232 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +2 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +7 -1
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +7 -0
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +27 -1
- package/dist/exporters/matrix/wording.js.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +2 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +7 -2
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
- 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.hbs +4 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +122 -15
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +70 -4
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +69 -3
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +13 -3
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +16 -10
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +15 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +81 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +187 -13
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +18 -0
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +39 -8
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +84 -4
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +229 -82
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +35 -8
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability.d.ts +7 -1
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +15 -12
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
- package/dist/harness/config-file.d.ts +17 -0
- package/dist/harness/config-file.d.ts.map +1 -0
- package/dist/harness/config-file.js +100 -0
- package/dist/harness/config-file.js.map +1 -0
- package/dist/harness/depth-lint.d.ts.map +1 -1
- package/dist/harness/depth-lint.js +2 -1
- package/dist/harness/depth-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +6 -2
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/keyword-match.d.ts +59 -0
- package/dist/harness/keyword-match.d.ts.map +1 -0
- package/dist/harness/keyword-match.js +86 -0
- package/dist/harness/keyword-match.js.map +1 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +2 -1
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +42 -7
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +7 -1
- package/dist/harness/perf.d.ts.map +1 -1
- package/dist/harness/perf.js +13 -10
- package/dist/harness/perf.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +9 -2
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +16 -4
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/read-text.d.ts +6 -0
- package/dist/harness/read-text.d.ts.map +1 -1
- package/dist/harness/read-text.js +7 -1
- package/dist/harness/read-text.js.map +1 -1
- package/dist/harness/secret-scan.d.ts +27 -1
- package/dist/harness/secret-scan.d.ts.map +1 -1
- package/dist/harness/secret-scan.js +57 -4
- package/dist/harness/secret-scan.js.map +1 -1
- package/dist/harness/sensors.d.ts +12 -0
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +41 -18
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-branches.d.ts +7 -20
- package/dist/harness/spec-branches.d.ts.map +1 -1
- package/dist/harness/spec-branches.js +85 -13
- package/dist/harness/spec-branches.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts +16 -0
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +131 -12
- package/dist/harness/spec-coverage.js.map +1 -1
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/write-atomic.d.ts +2 -0
- package/dist/harness/write-atomic.d.ts.map +1 -0
- package/dist/harness/write-atomic.js +62 -0
- package/dist/harness/write-atomic.js.map +1 -0
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +15 -7
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
- package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
- package/dist/orchestrator/appium-environment-setup.js +157 -0
- package/dist/orchestrator/appium-environment-setup.js.map +1 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +11 -6
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
- package/dist/orchestrator/project-deps-sync.js +22 -1
- package/dist/orchestrator/project-deps-sync.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +9 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +63 -7
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
- package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides-catalog.js +63 -0
- package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
- package/dist/orchestrator/security-overrides.d.ts +51 -0
- package/dist/orchestrator/security-overrides.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides.js +165 -0
- package/dist/orchestrator/security-overrides.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
- package/dist/orchestrator/templates/appium-environment/README.md +83 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
- package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
- package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
- package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
- package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
- package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
- package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
- package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
- package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-checked-state.js +26 -3
- package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
- package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
- package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
- package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-mock.js +10 -3
- package/dist/orchestrator/templates/specs-mock.js.map +1 -1
- package/dist/orchestrator/templates/specs-mock.ts +12 -4
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +14 -2
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +24 -2
- package/dist/orchestrator/templates/wdio.conf.ts +24 -1
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +12 -3
- package/src/cli/commands/capability.ts +63 -19
- package/src/cli/commands/delivery.ts +38 -16
- package/src/cli/commands/generate.ts +15 -7
- package/src/cli/commands/init.ts +3 -0
- package/src/cli/commands/results-merge.ts +2 -8
- package/src/cli/commands/results-sanitize.ts +2 -9
- package/src/cli/commands/setup-mobile-env.ts +64 -0
- package/src/cli/index.ts +2 -0
- package/src/dashboard/snapshot-builder.ts +6 -8
- package/src/exporters/feature-parser.ts +32 -2
- package/src/exporters/locale-variants.ts +23 -17
- package/src/exporters/matrix/build.ts +14 -8
- package/src/exporters/matrix/carry-manual-results.ts +229 -0
- package/src/exporters/matrix/export.ts +10 -4
- package/src/exporters/matrix/wording.ts +26 -1
- package/src/exporters/xlsx-exporter.ts +7 -2
- package/src/generators/test-generator/adapters/adapter-interface.ts +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +94 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
- 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.hbs +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +17 -2
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
- package/src/generators/test-generator/code-generator.ts +119 -19
- package/src/generators/test-generator/diagnostics.ts +70 -4
- package/src/generators/test-generator/patterns/index.ts +16 -9
- package/src/generators/test-generator/patterns/types.ts +15 -0
- package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
- package/src/generators/test-generator/step-mapper.ts +196 -13
- package/src/generators/test-generator/template-engine.ts +18 -0
- package/src/generators/test-generator/utils/data-resolver.ts +41 -12
- package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +256 -85
- package/src/harness/audit.ts +41 -12
- package/src/harness/capability.ts +23 -12
- package/src/harness/catalog/universal-viewpoints.yaml +5 -5
- package/src/harness/config-file.ts +66 -0
- package/src/harness/depth-lint.ts +2 -1
- package/src/harness/flow-check.ts +6 -2
- package/src/harness/keyword-match.ts +85 -0
- package/src/harness/next-step.ts +2 -1
- package/src/harness/parse.ts +42 -7
- package/src/harness/perf.ts +15 -7
- package/src/harness/quality-gates.ts +16 -4
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/read-text.ts +7 -1
- package/src/harness/secret-scan.ts +60 -4
- package/src/harness/sensors.ts +50 -19
- package/src/harness/spec-branches.ts +72 -14
- package/src/harness/spec-coverage.ts +133 -13
- package/src/harness/viewpoint-ledger.ts +2 -1
- package/src/harness/write-atomic.ts +22 -0
- package/src/orchestrator/ai-skills/writer.ts +18 -3
- package/src/orchestrator/appium-environment-setup.ts +127 -0
- package/src/orchestrator/assets-drift.ts +10 -5
- package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
- package/src/orchestrator/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +69 -7
- package/src/orchestrator/security-overrides-catalog.ts +69 -0
- package/src/orchestrator/security-overrides.ts +149 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +8 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +64 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
- package/src/orchestrator/templates/appium-environment/README.md +83 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
- package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
- package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
- package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
- package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
- package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
- package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
- package/src/orchestrator/templates/specs-checked-state.ts +31 -6
- package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/src/orchestrator/templates/specs-mock.ts +12 -4
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-url-assert.ts +24 -2
- package/src/orchestrator/templates/wdio.conf.ts +24 -1
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
writeMobileCapabilities,
|
|
17
17
|
installMobileDriver,
|
|
18
18
|
} from './mobile-runtime-scaffolder';
|
|
19
|
+
import { emitAppiumEnvironment, runAppiumEnvironmentSetup, appiumEnvironmentRerunCommand } from './appium-environment-setup';
|
|
20
|
+
import { ensureSecurityOverrides, reportSecurityOverrides } from './security-overrides';
|
|
21
|
+
import { readTextFile } from '../harness/read-text';
|
|
19
22
|
|
|
20
23
|
export { MobileInitOptions };
|
|
21
24
|
|
|
@@ -93,13 +96,21 @@ export class ProjectInitializer {
|
|
|
93
96
|
private cwd: string;
|
|
94
97
|
private createdItems: string[] = [];
|
|
95
98
|
private skippedItems: string[] = [];
|
|
99
|
+
/**
|
|
100
|
+
* Actions the user asked for that could NOT be performed. Kept apart from `skippedItems`:
|
|
101
|
+
* that list renders under "Already exists", which is a statement of fact about the project —
|
|
102
|
+
* printing "package.json scripts (invalid JSON)" under it tells the user the opposite of what
|
|
103
|
+
* happened. An action that was not done needs its own heading, or the init summary reads as a
|
|
104
|
+
* clean success while `npm test` is missing.
|
|
105
|
+
*/
|
|
106
|
+
private notDoneItems: string[] = [];
|
|
96
107
|
/** set by createCodexMCPConfig when the project isn't Codex-trusted (config would never load) */
|
|
97
108
|
private codexTrustWarning = false;
|
|
98
109
|
private agents: AdapterKey[];
|
|
99
110
|
private force: boolean;
|
|
100
111
|
private mobile: MobileInitOptions;
|
|
101
112
|
|
|
102
|
-
constructor(private baseUrl: string, opts: { agents?: AdapterKey[]; force?: boolean; enabled?: boolean; mobileOnly?: boolean; appPackage?: string; appActivity?: string; appApk?: string; androidUdid?: string; iosBundleId?: string; iosDevice?: string; iosVersion?: string } = {}) {
|
|
113
|
+
constructor(private baseUrl: string, opts: { agents?: AdapterKey[]; force?: boolean; enabled?: boolean; mobileOnly?: boolean; appPackage?: string; appActivity?: string; appApk?: string; androidUdid?: string; iosBundleId?: string; iosDevice?: string; iosVersion?: string; skipEnvSetup?: boolean } = {}) {
|
|
103
114
|
this.baseCwd = process.cwd();
|
|
104
115
|
this.cwd = this.baseCwd;
|
|
105
116
|
this.agents = opts.agents && opts.agents.length ? opts.agents : (['claude'] as AdapterKey[]);
|
|
@@ -114,6 +125,8 @@ export class ProjectInitializer {
|
|
|
114
125
|
iosBundleId: opts.iosBundleId,
|
|
115
126
|
iosDevice: opts.iosDevice,
|
|
116
127
|
iosVersion: opts.iosVersion,
|
|
128
|
+
// `--skip-env-setup`: emit appium-environment/ but do not run it (CI, or the manual path).
|
|
129
|
+
skipEnvSetup: opts.skipEnvSetup ?? false,
|
|
117
130
|
};
|
|
118
131
|
}
|
|
119
132
|
|
|
@@ -132,6 +145,7 @@ export class ProjectInitializer {
|
|
|
132
145
|
: this.baseCwd;
|
|
133
146
|
this.createdItems = [];
|
|
134
147
|
this.skippedItems = [];
|
|
148
|
+
this.notDoneItems = [];
|
|
135
149
|
|
|
136
150
|
console.log(`🚀 Initializing Sungen ${this.mobileOnly ? 'mobile-only ' : ''}project...\n`);
|
|
137
151
|
|
|
@@ -436,9 +450,17 @@ export class ProjectInitializer {
|
|
|
436
450
|
|
|
437
451
|
if (existed) {
|
|
438
452
|
try {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
453
|
+
// via readTextFile: a UTF-8 BOM is the one thing `JSON.parse` rejects that every other tool
|
|
454
|
+
// reading this file ignores, and it is invisible in an editor. A config the rest of the
|
|
455
|
+
// toolchain loads fine must not cost the user their MCP servers here.
|
|
456
|
+
config = JSON.parse(readTextFile(filePath));
|
|
457
|
+
} catch (e) {
|
|
458
|
+
// Anything still unparseable is genuinely broken. Say WHERE — "is not valid JSON" alone
|
|
459
|
+
// leaves the user to find a missing comma in a file they did not know was the problem.
|
|
460
|
+
this.notDoneItems.push(
|
|
461
|
+
`${displayName} (${(e as Error).message.split('\n')[0]}) — MCP servers not registered; `
|
|
462
|
+
+ `fix the JSON and re-run \`sungen init\``,
|
|
463
|
+
);
|
|
442
464
|
return;
|
|
443
465
|
}
|
|
444
466
|
}
|
|
@@ -484,6 +506,13 @@ export class ProjectInitializer {
|
|
|
484
506
|
});
|
|
485
507
|
}
|
|
486
508
|
|
|
509
|
+
if (this.notDoneItems.length > 0) {
|
|
510
|
+
console.log('\n⚠ Not done — needs your attention:');
|
|
511
|
+
this.notDoneItems.forEach(item => {
|
|
512
|
+
console.log(` ✗ ${item}`);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
487
516
|
if (this.codexTrustWarning) {
|
|
488
517
|
console.log('\n⚠ Codex: this project is not trusted yet, so .codex/config.toml (the MCP browser');
|
|
489
518
|
console.log(' config for run-test) will be SILENTLY IGNORED by Codex. To fix, either:');
|
|
@@ -500,6 +529,8 @@ export class ProjectInitializer {
|
|
|
500
529
|
console.log(` ${stepIndex++}. cd ${projectName}`);
|
|
501
530
|
}
|
|
502
531
|
if (this.mobileOnly) {
|
|
532
|
+
console.log(` ${stepIndex++}. Open a NEW terminal if the environment setup wrote your shell profile (PATH / JAVA_HOME / ANDROID_HOME)`);
|
|
533
|
+
console.log(` ${stepIndex++}. Environment not ready, or a new machine? Re-run: ${appiumEnvironmentRerunCommand()} (idempotent)`);
|
|
503
534
|
console.log(` ${stepIndex++}. Fill in .env.appium with your device/app details (see .env.appium.example)`);
|
|
504
535
|
console.log(` ${stepIndex++}. Create your first screen: sungen add --screen <name>`);
|
|
505
536
|
console.log(` ${stepIndex++}. Use AI (Copilot/Claude) to generate Gherkin + selectors from the live app`);
|
|
@@ -642,8 +673,17 @@ export class ProjectInitializer {
|
|
|
642
673
|
this.createdItems.push(...result.created);
|
|
643
674
|
this.skippedItems.push(...result.skipped);
|
|
644
675
|
|
|
676
|
+
// appium-environment/ provisioning bundle — emitted BEFORE the driver install so it lands even when
|
|
677
|
+
// npm is offline, run AFTER it so its Appium step finds the project-local CLI. One summary line, not 17.
|
|
678
|
+
const env = emitAppiumEnvironment(this.cwd);
|
|
679
|
+
if (env.created.length > 0) this.createdItems.push(`appium-environment/ (${env.created.length} files)`);
|
|
680
|
+
else this.skippedItems.push('appium-environment/');
|
|
681
|
+
|
|
645
682
|
// Best-effort driver install — non-fatal so offline init still scaffolds.
|
|
646
683
|
installMobileDriver(this.cwd);
|
|
684
|
+
|
|
685
|
+
// Provision JDK / Android SDK / Xcode CLT / env vars / platform drivers — non-fatal by contract.
|
|
686
|
+
runAppiumEnvironmentSetup(this.cwd, { skip: this.mobile.skipEnvSetup });
|
|
647
687
|
}
|
|
648
688
|
|
|
649
689
|
/**
|
|
@@ -671,6 +711,13 @@ export class ProjectInitializer {
|
|
|
671
711
|
// Ensure standard scripts exist in package.json
|
|
672
712
|
this.ensurePackageScripts(packageJsonPath);
|
|
673
713
|
|
|
714
|
+
// Pin the transitive packages that would otherwise make `npm audit` red here. This MUST happen
|
|
715
|
+
// before the install below: npm reads `overrides` at resolution time, so a lockfile written
|
|
716
|
+
// first would already hold the vulnerable versions. See security-overrides.ts for why the pins
|
|
717
|
+
// cannot simply live in sungen's own manifest.
|
|
718
|
+
const overridePlan = ensureSecurityOverrides(packageJsonPath);
|
|
719
|
+
reportSecurityOverrides(overridePlan);
|
|
720
|
+
|
|
674
721
|
// Mobile-only drops @playwright/test (no web runner needed). `ajv`/`ajv-formats` back the API
|
|
675
722
|
// Driver's `expect {{name.body}} matches schema [Ref]` (loaded lazily; harmless if unused).
|
|
676
723
|
// fast-xml-parser: the API driver's XML request/response support lazy-requires it
|
|
@@ -682,7 +729,7 @@ export class ProjectInitializer {
|
|
|
682
729
|
|
|
683
730
|
let missingDeps: string[] = requiredDeps;
|
|
684
731
|
try {
|
|
685
|
-
const packageJson = JSON.parse(
|
|
732
|
+
const packageJson = JSON.parse(readTextFile(packageJsonPath));
|
|
686
733
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
687
734
|
missingDeps = requiredDeps.filter(d => !deps[d]);
|
|
688
735
|
} catch {
|
|
@@ -691,6 +738,12 @@ export class ProjectInitializer {
|
|
|
691
738
|
|
|
692
739
|
if (missingDeps.length === 0) {
|
|
693
740
|
console.log('✓ All dependencies already installed\n');
|
|
741
|
+
// Nothing to install means nothing re-resolves, so overrides just written would sit in
|
|
742
|
+
// package.json while the lockfile keeps the vulnerable tree. Re-resolve once so they land.
|
|
743
|
+
if (overridePlan?.add.length) {
|
|
744
|
+
console.log('📦 Re-resolving the tree so the new security overrides take effect...\n');
|
|
745
|
+
execSync('npm install', execOpts);
|
|
746
|
+
}
|
|
694
747
|
} else {
|
|
695
748
|
console.log(`📦 Installing ${missingDeps.join(', ')}...\n`);
|
|
696
749
|
execSync(`npm install -D ${missingDeps.join(' ')}`, execOpts);
|
|
@@ -711,8 +764,17 @@ export class ProjectInitializer {
|
|
|
711
764
|
private ensurePackageScripts(packageJsonPath: string): void {
|
|
712
765
|
let packageJson: Record<string, any>;
|
|
713
766
|
try {
|
|
714
|
-
|
|
715
|
-
|
|
767
|
+
// via readTextFile: a package.json carrying a UTF-8 BOM is one npm reads without complaint,
|
|
768
|
+
// so it must not be treated as unparseable here. That was the trigger that actually reached
|
|
769
|
+
// this catch — genuinely broken JSON aborts `npm install` a few lines below instead.
|
|
770
|
+
packageJson = JSON.parse(readTextFile(packageJsonPath));
|
|
771
|
+
} catch (e) {
|
|
772
|
+
// Returning here used to add nothing and say nothing, so init reported success and the user
|
|
773
|
+
// met `npm error Missing script: "test"` later with nothing pointing back to this run.
|
|
774
|
+
this.notDoneItems.push(
|
|
775
|
+
`package.json scripts (${(e as Error).message.split('\n')[0]}) — `
|
|
776
|
+
+ `fix the JSON and re-run \`sungen init\`, or add the scripts by hand`,
|
|
777
|
+
);
|
|
716
778
|
return;
|
|
717
779
|
}
|
|
718
780
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm `overrides` a sungen project needs in order to pass `npm audit`.
|
|
3
|
+
*
|
|
4
|
+
* Why this lives in the CLI instead of in `packages/core/package.json`: npm honours `overrides`
|
|
5
|
+
* ONLY in the root `package.json` of the project doing the installing. A block declared by a
|
|
6
|
+
* DEPENDENCY is inert — it is not read, not merged, and not carried in the published tarball. So
|
|
7
|
+
* the monorepo root can (and does) pin these for itself while every consumer of the published
|
|
8
|
+
* packages still resolves the vulnerable versions. Repo green, install red, and nobody working in
|
|
9
|
+
* the repo ever sees what a consumer sees. Issue #764.
|
|
10
|
+
*
|
|
11
|
+
* Sungen already writes to the consuming project's `package.json` (standard scripts on `init`,
|
|
12
|
+
* family version pinning on `update`), so the overrides go in on the same two passes. That makes
|
|
13
|
+
* the fix reachable the ordinary way: a new project is clean from its first install, an existing
|
|
14
|
+
* one becomes clean on the next `sungen update`.
|
|
15
|
+
*
|
|
16
|
+
* Every entry below is here because NOTHING ELSE CLEARS IT — the offending package is either at its
|
|
17
|
+
* latest published version already, or is hard-pinned by an unmaintained parent. Bumping sungen's
|
|
18
|
+
* own declared ranges does not move any of them: `@sungen/driver-mobile` is already on the newest
|
|
19
|
+
* appium (3.x) and webdriverio (9.x), and npm's own `fix available` for that chain is a DOWNGRADE
|
|
20
|
+
* to appium 1.22.3 / webdriverio 8.14.6, not a fix.
|
|
21
|
+
*
|
|
22
|
+
* Measured on a clean-room install of the published 3.2.32 family (2026-09-23): 25 findings
|
|
23
|
+
* (12 moderate, 13 high) without this block, 0 with it.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface SecurityOverride {
|
|
27
|
+
/** Package to pin, as it appears in `overrides`. */
|
|
28
|
+
pkg: string;
|
|
29
|
+
/** Range to pin it to — the lowest published version clear of the advisory. */
|
|
30
|
+
range: string;
|
|
31
|
+
/** Advisory + the path that drags the vulnerable copy in, so a reader can re-check the entry. */
|
|
32
|
+
why: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The pinned set. Keep it MINIMAL: an entry earns its place only while a clean install is red
|
|
37
|
+
* without it. When an upstream finally ships a fix inside its own declared range, delete the entry
|
|
38
|
+
* rather than raising it — a stale override silently holds a whole subtree back.
|
|
39
|
+
*/
|
|
40
|
+
export const SECURITY_OVERRIDES: SecurityOverride[] = [
|
|
41
|
+
{
|
|
42
|
+
pkg: 'uuid',
|
|
43
|
+
range: '^11.1.1',
|
|
44
|
+
// exceljs 4.4.0 IS the latest (published 2024-12-20) and hard-declares "uuid": "^8.3.0".
|
|
45
|
+
// There is no newer exceljs to bump to, which is why npm reports fixAvailable: false.
|
|
46
|
+
why: 'GHSA-w5hq-g745-h8pq (moderate) — uuid@8.3.2 via exceljs@4.4.0, which is unmaintained and pins ^8.3.0',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pkg: 'morgan',
|
|
50
|
+
range: '^1.12.1',
|
|
51
|
+
// @appium/base-driver pins the exact version "1.11.0", so no resolution reaches 1.12.x on its own.
|
|
52
|
+
why: 'GHSA-jxfw-x594-9x9m (moderate) — morgan@1.11.0 exact-pinned by @appium/base-driver',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
pkg: 'serialize-javascript',
|
|
56
|
+
range: '^7.1.2',
|
|
57
|
+
why: 'GHSA-5c6j-r48x-rmvq (high) + GHSA-qj8w-gfj5-8c6v (moderate) — serialize-javascript@6.0.2 via mocha (^6.0.2)',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
pkg: '@puppeteer/browsers',
|
|
61
|
+
range: '^3.2.3',
|
|
62
|
+
// The vulnerable package is extract-zip, whose latest (2.0.1) IS the flagged version — there is
|
|
63
|
+
// nothing to pin it to. @puppeteer/browsers 3.x dropped extract-zip for modern-tar, so the pin
|
|
64
|
+
// goes on the parent instead. @wdio/utils asks for ^2.2.0 but imports only install, canDownload,
|
|
65
|
+
// resolveBuildId, detectBrowserPlatform, Browser, ChromeReleaseChannel and computeExecutablePath
|
|
66
|
+
// — all seven still exported by 3.2.3, verified against @wdio/utils@9.32.0.
|
|
67
|
+
why: 'GHSA-jmr9-qjv8-65gv + GHSA-7pqw-9j4j-h8q3 (high) — extract-zip@2.0.1 via @puppeteer/browsers@2.x ← @wdio/utils; extract-zip has no fixed release, 3.x replaced it with modern-tar',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { SECURITY_OVERRIDES, SecurityOverride } from './security-overrides-catalog';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Merge the security-override catalog into a consuming project's `package.json`.
|
|
7
|
+
*
|
|
8
|
+
* The catalog itself — which packages, which ranges, and why each one cannot be cleared any other
|
|
9
|
+
* way — lives in `security-overrides-catalog.ts`. That is the file to edit when an advisory moves;
|
|
10
|
+
* this one holds only the merge, the write and the reporting, which do not change when it does.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { SECURITY_OVERRIDES } from './security-overrides-catalog';
|
|
14
|
+
export type { SecurityOverride } from './security-overrides-catalog';
|
|
15
|
+
|
|
16
|
+
export interface OverridePlan {
|
|
17
|
+
/** Entries missing from the project, to be written. */
|
|
18
|
+
add: SecurityOverride[];
|
|
19
|
+
/** Entries the project already pins itself — left untouched, reported so the user can judge. */
|
|
20
|
+
existing: Array<{ pkg: string; declared: string; wanted: string }>;
|
|
21
|
+
/** Set when the pins were needed but the write failed — `add` is then empty, nothing was applied. */
|
|
22
|
+
writeFailed?: boolean;
|
|
23
|
+
/** Lockfile of a package manager that ignores `overrides`, when the project has one. */
|
|
24
|
+
foreignLockfile?: 'yarn' | 'pnpm';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decide what a project's `overrides` block is missing. Pure — no I/O, so the tests and `--dry-run`
|
|
29
|
+
* can plan without touching anything.
|
|
30
|
+
*
|
|
31
|
+
* A pin the project already declares is NEVER rewritten, even when it differs from ours. Someone who
|
|
32
|
+
* typed a range into their own `package.json` had a reason (a private mirror, a pinned fork, a
|
|
33
|
+
* downgrade forced by another tool), and silently overwriting it would be the more surprising bug.
|
|
34
|
+
* It is reported instead, so a still-vulnerable pin is visible rather than swallowed.
|
|
35
|
+
*/
|
|
36
|
+
export function planSecurityOverrides(pkgJson: Record<string, any>): OverridePlan {
|
|
37
|
+
const current: Record<string, unknown> =
|
|
38
|
+
pkgJson.overrides && typeof pkgJson.overrides === 'object' ? pkgJson.overrides : {};
|
|
39
|
+
|
|
40
|
+
const add: SecurityOverride[] = [];
|
|
41
|
+
const existing: OverridePlan['existing'] = [];
|
|
42
|
+
|
|
43
|
+
for (const entry of SECURITY_OVERRIDES) {
|
|
44
|
+
const declared = current[entry.pkg];
|
|
45
|
+
if (typeof declared === 'string') {
|
|
46
|
+
if (declared !== entry.range) existing.push({ pkg: entry.pkg, declared, wanted: entry.range });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// A nested override object ({ "uuid": { ".": "^11" } }) is a deliberate, more specific shape —
|
|
50
|
+
// treat it as the project's own decision and leave it alone.
|
|
51
|
+
if (declared !== undefined) {
|
|
52
|
+
existing.push({ pkg: entry.pkg, declared: JSON.stringify(declared), wanted: entry.range });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
add.push(entry);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { add, existing };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write the missing overrides into a project's `package.json`.
|
|
63
|
+
*
|
|
64
|
+
* Best-effort by design, exactly like the rest of the project-file touching in `init` / `update`: an
|
|
65
|
+
* unreadable or malformed `package.json` means we skip silently rather than fail the command the
|
|
66
|
+
* user actually ran. The audit gate is not worth losing a scaffold over.
|
|
67
|
+
*
|
|
68
|
+
* Must run BEFORE the project's `npm install`, or the first resolution pass writes a lockfile that
|
|
69
|
+
* already pins the vulnerable versions.
|
|
70
|
+
*
|
|
71
|
+
* @param dryRun plan only — report what would change and write nothing
|
|
72
|
+
* @returns the plan that was applied, or null when there was no `package.json` to apply it to
|
|
73
|
+
*/
|
|
74
|
+
export function ensureSecurityOverrides(packageJsonPath: string, dryRun = false): OverridePlan | null {
|
|
75
|
+
let pkgJson: Record<string, any>;
|
|
76
|
+
try {
|
|
77
|
+
pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const plan = planSecurityOverrides(pkgJson);
|
|
83
|
+
plan.foreignLockfile = detectForeignLockfile(path.dirname(packageJsonPath));
|
|
84
|
+
|
|
85
|
+
if (plan.add.length && !dryRun) {
|
|
86
|
+
const overrides: Record<string, unknown> =
|
|
87
|
+
pkgJson.overrides && typeof pkgJson.overrides === 'object' ? pkgJson.overrides : {};
|
|
88
|
+
for (const entry of plan.add) overrides[entry.pkg] = entry.range;
|
|
89
|
+
pkgJson.overrides = overrides;
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8');
|
|
92
|
+
} catch {
|
|
93
|
+
// Read-only checkout, root-owned file, read-only container mount. `sungen update` in
|
|
94
|
+
// particular has ALREADY reinstalled globally by the time it gets here, so throwing would
|
|
95
|
+
// abandon the AI-rules refresh and the family sync over a hardening step. Report and move on.
|
|
96
|
+
return { add: [], existing: plan.existing, foreignLockfile: plan.foreignLockfile, writeFailed: true };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return plan;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* npm is the only package manager that reads a top-level `overrides` block. Yarn wants
|
|
105
|
+
* `resolutions`, pnpm wants `pnpm.overrides` — so on those projects the block we write is inert and
|
|
106
|
+
* the install stays vulnerable. We do NOT guess at the equivalent field (getting a resolutions
|
|
107
|
+
* pattern subtly wrong is worse than not writing one), but silence would be the same class of bug
|
|
108
|
+
* as #764 itself: a fix that looks applied and is not. So: say so.
|
|
109
|
+
*/
|
|
110
|
+
function detectForeignLockfile(dir: string): 'yarn' | 'pnpm' | undefined {
|
|
111
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
112
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Print what `ensureSecurityOverrides` did, in the voice the surrounding commands already use. */
|
|
117
|
+
export function reportSecurityOverrides(plan: OverridePlan | null, dryRun = false): void {
|
|
118
|
+
if (!plan) return;
|
|
119
|
+
|
|
120
|
+
if (plan.writeFailed) {
|
|
121
|
+
console.warn(
|
|
122
|
+
'⚠️ Could not write the security overrides into package.json (read-only file?). Nothing else\n' +
|
|
123
|
+
' failed. Add this block by hand to keep `npm audit` clean:\n' +
|
|
124
|
+
` "overrides": ${JSON.stringify(Object.fromEntries(SECURITY_OVERRIDES.map((o) => [o.pkg, o.range])), null, 2).replace(/\n/g, '\n ')}\n`,
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (plan.add.length) {
|
|
130
|
+
const verb = dryRun ? '[dry-run] would add' : 'Added';
|
|
131
|
+
console.log(`🔒 ${verb} ${plan.add.length} security override(s) to package.json — npm honours these only here, not from sungen's own manifest:`);
|
|
132
|
+
for (const entry of plan.add) console.log(` • ${entry.pkg}@${entry.range} — ${entry.why}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (plan.existing.length) {
|
|
137
|
+
console.log('🔒 Left your own overrides alone (sungen never rewrites a pin you declared):');
|
|
138
|
+
for (const e of plan.existing) console.log(` – ${e.pkg}: ${e.declared} (sungen would use ${e.wanted})`);
|
|
139
|
+
console.log(' If `npm audit` still flags one of these, raise it by hand.\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (plan.foreignLockfile && (plan.add.length || plan.existing.length)) {
|
|
143
|
+
const field = plan.foreignLockfile === 'pnpm' ? '"pnpm": { "overrides": … }' : '"resolutions"';
|
|
144
|
+
console.warn(
|
|
145
|
+
`⚠️ This project has a ${plan.foreignLockfile} lockfile. ${plan.foreignLockfile} ignores npm's "overrides" —\n` +
|
|
146
|
+
` mirror the block into ${field} as well, or the pins do nothing here.\n`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -93,6 +93,20 @@ every variant is N/A) and never a bare state word. **`N/A` leaves the denominato
|
|
|
93
93
|
and actual result share one column `P`; the header row is frozen; ISO dates; landscape print with
|
|
94
94
|
repeated header rows.
|
|
95
95
|
|
|
96
|
+
**Re-running delivery keeps the tester's Manual results** (#756, since 3.2.33). Before overwriting,
|
|
97
|
+
the CLI reads the existing workbook's **Manual** sheet and carries each variant's `L`–`P` cells into
|
|
98
|
+
the new one by `VP-…` ref, verbatim. A hidden `_sungen_manual` sheet stamps each case's fingerprint,
|
|
99
|
+
so a result recorded against an older procedure is reset to `Pending` with the previous result
|
|
100
|
+
moved into Note. Relay the CLI's `Manual results:` lines to the user: `reset to Pending` means those
|
|
101
|
+
cases must be run again by hand; `not carried` names results whose case left the feature. The
|
|
102
|
+
**Auto** sheet is not carried (the run report owns its Result), and rows a tester added by hand
|
|
103
|
+
have no ref and are not carried. Never tell the user to copy or rename the file to protect manual
|
|
104
|
+
results. A second `not carried` line names a duplicated row (only the first copy is kept) — tell
|
|
105
|
+
the user to merge it by hand. If the CLI prints `not written: cannot read …` (locked or damaged) or
|
|
106
|
+
`not written: … has no "Manual" sheet` (renamed), the unit was deliberately NOT written to protect
|
|
107
|
+
the results: ask the user to close the file or rename the sheet back to `Manual`, or move the file
|
|
108
|
+
aside. Never delete, rename or move it yourself.
|
|
109
|
+
|
|
96
110
|
`Coverage` sheet — requirement coverage table (every FR/TR/NFR id with an explicit status),
|
|
97
111
|
target × category grid with explicit `—` gaps, dispositions, manifest. CSV mirrors the same model
|
|
98
112
|
flat with a `Level` column (`item`/`variant`) + a requirement-coverage appendix.
|
|
@@ -124,8 +124,14 @@ needs any of these, it is a **finding for QA** — surface it in the run summary
|
|
|
124
124
|
| `SG-E027` | A selectors.yaml entry declares a **`type:` that is not a selector type** — `type: 'testId'` (capital I), `'xpth'`, `'rol'`, `'test-id'`. YAML is untyped at run time, so this used to compile clean and resolve through the strategy switch's `default:` as `getByPlaceholder('<the value>')`: a locator that cannot match, surfacing much later as a timeout naming placeholder text nobody wrote. Reported once per entry, naming the entry key and the nearest valid spelling; every step using that entry now compiles to a `throw` | Fix the spelling in selectors.yaml. Valid types: `placeholder`, `role`, `testid`, `label`, `text`, `locator`, `id`, `upload`, `page`, `column`, `frame`, `table`, and the mobile strategies `accessibility-id`, `xpath`, `android-uiautomator`, `ios-predicate`, `css`. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
125
125
|
| `SG-W030` | A selectors.yaml **`expanded.class`/`expanded.attribute`** value carries a character a class or attribute name cannot — whitespace, a quote, a backslash, `$`, `{`, `}`. The value is pasted into the generated spec (string literal, regex, `classList` argument) and selectors.yaml is often generated from the page under test, so it is refused and the field dropped: the step falls back to driving and asserting `aria-expanded`. Reported once per entry and field. The checked/selected signal is refused outright instead of warned about, because dropping it leaves the step with no state to read at all | Use the plain class or attribute name (Tailwind arbitrary values such as `bg-[#eff6ff]` or `!mt-0` are accepted); never paste a selector fragment or quoted text into the field |
|
|
126
126
|
| `SG-E031` | A selectors.yaml **`checked:`/`selected:` declaration the compiler refuses**: the two spellings disagree (`checked.class` and `selected.class` with different values), a class token or attribute name carries whitespace / a quote / a backslash, or one entry declares both a `class` and an `attribute`. Every one of them is silent at run time — the generated matcher tests a single token, so the step would simply time out blaming your component — and an entry declaring two signals lets an action drive one while the assertion after it reads the other. Refused, not dropped: with no signal the step is back on the *"has no on/off state to set"* failure the declaration exists to remove | Declare ONE spelling and ONE of `class` / `attribute`, as a single plain token: `checked: { class: 'is-checked' }`, `selected: { class: 'border-link' }`, or `checked: { attribute: 'data-checked' }`. This is a selectors.yaml-only fix, allowed inside the run-test auto-fix loop |
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
| `SG-W032` | A step carries the row filter **`in [T] table with {{v}}`** but the pattern that compiled it dropped the clause, so the generated code runs against the WHOLE table. `Then User see [Status] text in [Job Table] table with {{job_title}}` asserts on every `.status` in the table and passes on a row the sentence never named. The `table-*` family (positioned rows included) resolves its own container and is not flagged, nor is the two-`with` form, which scopes the row correctly, nor a step that WRITES — a write needs a value, so its `with {{v}}` IS that value and `fill [Search] field in [T] table with {{v}}` is an ordinary container-scoped write | Rewrite with the two-`with` form: `User see [Status] text with <expected> in [Job Row] row with {{job_title}}`. Point the second `[…]` at a ROW entry in selectors.yaml, not at the table entry — a filter applied to the table narrows nothing |
|
|
128
|
+
| `SG-W034` | A `see [X] column with {{v}}` step runs inside a table whose rows are declared with a **`rows:` shape**, so the row is not an ARIA row and holds no ARIA cells — but the assertion's only other way to reach a cell is `getByRole('cell')`, which matches nothing there. `columns.<col>.index` cannot rescue it: it indexes that same empty set. Before this the step failed as a timeout blaming the app. Not reported on a real `<table>`, where the role fallback is correct | Name the cell on the column: `columns: { title: { index: 0, header: 'Title', elements: { cell: { type: locator, value: '.job-title' } } } }`. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
129
|
+
| `SG-E035` | **`table match data:` on a table declared with a `rows:` shape** and no matching `columns:` entries. The assertion reads its header row from `thead tr` and addresses cells as `td, th`; a <div> list has neither — forever, not as a render race — so the step could only wait out every timeout and fail on an empty header row. Refused at compile time, naming the columns it needs | Declare each DataTable column on the table entry with the selector that matches its cell inside a row: `columns: { title: { index: 0, header: 'Title', elements: { cell: { type: locator, value: '.job-title' } } } }`. Declaring columns also removes the runtime header read on a real `<table>`. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
130
|
+
| `SG-W036` | A **`remember [X] … as {{v}}` capture through a `locator`/`css` selector with no `nth:`**. A capture reads ONE element and the step takes the FIRST match — the right default, and what the documented `nth: 0` workaround asked for after Playwright's strict mode turned a list-shaped selector into a hard failure naming the count. But the first of many is a decision: if the selector describes every item in a list, the captured value is the first row's. Not reported for testid/label/placeholder/role+name/text entries, which name a particular thing | Declare the index (`nth: 0`) to say the first one is meant, or narrow the selector to the single element. This is a selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
131
|
+
| `SG-W037` | A **`type: page` entry saying something about the QUERY that the compiler cannot use**. Two causes: the entry's `value` carries a `?`/`#`, which is DROPPED (a page value is the path only — the pattern is matched against `u.pathname`, which never holds a query), or `query:` declares something other than `any`, which is ignored rather than read as permissive so a typo cannot quietly switch off the bare-page assertion. Both used to be silent, and the first is the trap: an author who hit "a bare `see [X] page` requires NO query" reaches for the query in the value, gets byte-identical output, and no hint why | To assert the query, declare it on the STEP (`see [X] page with {{v}}`). If the query is generated at run time — `/admins/detail?id=<the row just clicked>`, which no literal can express — add `query: any` to the entry: the query is then not part of that page's identity. selectors.yaml-only fix — allowed inside the run-test auto-fix loop |
|
|
132
|
+
| `SG-E038` | A **`{{var}}` the compiler cannot resolve** — the feature has no test-data file of its own, the key is absent, or it holds a list/map where a value was needed. This used to emit the PLACEHOLDER TEXT as the value: `fill [Partner Customer ID] field with {{valid_partner_id}}` compiled to `fill('${valid_partner_id}')`, typing those 20 literal characters into the app with `generate` exiting 0. A field with no format validation accepts the string, the server sees an unknown user, and the scenario fails at the assertion it was written to fail at — a suite that looks correct while feeding the system garbage. Every run also shares the SAME literal, so a scenario needing a fresh user silently reuses one. The step now compiles to a `throw` naming the ref and the file | Add the key to the file the message names. Note a feature resolves `{{var}}` against a data file named after **itself** — `qa/screens/<screen>/test-data/<feature>.yaml` — so the second `.feature` in a screen directory needs its own data file, not the screen-named one. `sungen data crosscheck` reports the same gap before you compile |
|
|
133
|
+
|
|
134
|
+
A diagnostic never changes generation's own exit code — `sungen generate --strict` turns any collected diagnostic into a non-zero exit for CI gates. They are not all inert, though: **`SG-W001`, `SG-W011`, `SG-E020`, `SG-E021`, `SG-W020`, `SG-E023`, `SG-E024`, `SG-E025`, `SG-E026`, `SG-E027`, `SG-E035` and `SG-E038` additionally compile the offending step to a runtime `throw`, and `SG-E031` refuses the whole file** — a step that reads as a check and silently checks nothing is worse than one that fails loudly. Generation always COMPLETES: the rest of the scenario and the rest of the file still compile. `SG-E022` is the odd one out — it blames the compiler, not your `.feature`, and throws nothing (the file is written so you can inspect it).
|
|
129
135
|
|
|
130
136
|
### Runtime error → `Test data "<key>" references ${QA_*} but the environment variable is not set`
|
|
131
137
|
|
|
@@ -154,6 +154,7 @@ two content-filtered queries can hit different rows if the table re-renders in b
|
|
|
154
154
|
# dropdown | select | combobox → tag probed at runtime (toHaveValue native, toHaveText custom trigger)
|
|
155
155
|
# 5. State: User see [T] button is disabled | checkbox is checked | dialog with {{v}} is hidden
|
|
156
156
|
# 6. Attribute (toHaveAttribute): User see [T] image | link with {{v}}
|
|
157
|
+
# WHICH attribute comes from the entry's `attribute:` — any name, not just href/src
|
|
157
158
|
# 7. Count: User see [T] row with {{count}}
|
|
158
159
|
# 8. Page: User see [T] page | page with {{url}} ← `with` also asserts the query string
|
|
159
160
|
# NEVER a state: `page is hidden` / `page is empty` … are NOT step forms (see Pattern 8 below)
|
|
@@ -291,7 +292,22 @@ job table:
|
|
|
291
292
|
value: 'div.job-list'
|
|
292
293
|
rows: 'div.job-row' # what EVERY row matches — NOT a row label's entry
|
|
293
294
|
```
|
|
294
|
-
Two different keys, two meanings: `rows:` on the table = every row (the shape); a `type: locator` entry on a **row label** = ONE positioned row (`tbody tr:first-child`). A shape put on a row label fails at run time with a `toHaveCount(1)` message that points back here. A
|
|
295
|
+
Two different keys, two meanings: `rows:` on the table = every row (the shape); a `type: locator` entry on a **row label** = ONE positioned row (`tbody tr:first-child`). A shape put on a row label fails at run time with a `toHaveCount(1)` message that points back here. A table located by CSS — or by a role that holds no rows, such as `role: list` or `role: region` — with no `rows:` gets `SG-W016`. On a shaped table `table row {{n}}` counts from the first data row (no header offset).
|
|
296
|
+
|
|
297
|
+
**A shaped row has no CELLS either, so the two cell-reading steps need one more declaration.** `see [Col] column with {{v}}` reaches the cell through `getByRole('cell')`, and `table match data:` reads its header row from `thead tr` and addresses cells as `td, th` — a `<div>` row has none of those. Name the cell on the column:
|
|
298
|
+
```yaml
|
|
299
|
+
job table:
|
|
300
|
+
type: locator
|
|
301
|
+
value: 'div.job-list'
|
|
302
|
+
rows: 'div.job-row'
|
|
303
|
+
columns:
|
|
304
|
+
title:
|
|
305
|
+
index: 0
|
|
306
|
+
header: 'Title'
|
|
307
|
+
elements:
|
|
308
|
+
cell: { type: locator, value: '.job-title' } # relative to ONE row
|
|
309
|
+
```
|
|
310
|
+
With it, `see [Title] column with {{v}}` compiles to `tableRow.locator('.job-title')` and `table match data:` stops reading `thead` altogether — the header-to-column mapping is then known at compile time, so there is nothing left to race with. Without it on a shaped table, `see [Col] column` warns (`SG-W034`) and `table match data:` is refused outright (`SG-E035`) rather than waiting out every timeout against a header row that will never exist.
|
|
295
311
|
|
|
296
312
|
### Browser storage `[web]`
|
|
297
313
|
|
|
@@ -556,6 +572,7 @@ award:
|
|
|
556
572
|
| `page` | relative URL | — | navigation |
|
|
557
573
|
| `upload` | — | — | file inputs |
|
|
558
574
|
| `frame` | iframe selector | — | iframes |
|
|
575
|
+
| `id` | `#the-id` (a bare `the-id` also works) | — | an element whose only stable handle is its DOM id |
|
|
559
576
|
|
|
560
577
|
Options: `nth` `exact` `scope` `match` `variant` `frame` `contenteditable` `columns` `checked` `selected`
|
|
561
578
|
|
|
@@ -569,6 +586,39 @@ when the state differs, then assert it; `is checked` / `is unchecked` / `is sele
|
|
|
569
586
|
the same entry (`is focused`, …) keep their native matchers. The value must be ONE plain class token or
|
|
570
587
|
attribute name — whitespace, a quote or a backslash is refused at compile time, naming the entry.
|
|
571
588
|
|
|
589
|
+
`attribute:` names the attribute an `image`/`link` assertion reads, and it takes ANY attribute name
|
|
590
|
+
straight from the YAML — nothing is hardcoded to `href`/`src`. To assert several attributes of ONE
|
|
591
|
+
element, declare several keys with the SAME locator and different `attribute:`; each key is then a
|
|
592
|
+
separate reference in the Gherkin:
|
|
593
|
+
```yaml
|
|
594
|
+
job link: # the link's TEXT
|
|
595
|
+
type: locator
|
|
596
|
+
value: 'a.job-link'
|
|
597
|
+
job link href: # the same element, its href
|
|
598
|
+
type: locator
|
|
599
|
+
value: 'a.job-link'
|
|
600
|
+
attribute: href
|
|
601
|
+
job link target:
|
|
602
|
+
type: locator
|
|
603
|
+
value: 'a.job-link'
|
|
604
|
+
attribute: target
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**A `page` value that is an ABSOLUTE URL now anchors the HOST as well as the path.** `value:
|
|
608
|
+
'https://admin.example.com/jobs'` means "that path on that host": `see [X] page`, `is on [X] page`
|
|
609
|
+
and `should remain on` all fail on `https://admin-stg.example.com/jobs`. Before, only `wait for [X]
|
|
610
|
+
page` checked the host and the assertions matched the path on any host at all — so an assertion
|
|
611
|
+
naming one environment silently passed in another. If the page is the same in every environment,
|
|
612
|
+
declare it **relative** (`value: '/jobs'`) and let `baseURL` supply the host; keep the absolute form
|
|
613
|
+
for a destination that really is on another host.
|
|
614
|
+
|
|
615
|
+
`anchor:` is a `page` entry's rendered-content proof: `Then User see [X] page` asserts the URL AND
|
|
616
|
+
that the anchor element is present, so a route guard that leaves the URL alone cannot pass it. Three
|
|
617
|
+
things it does NOT do, each verified rather than assumed: it is read only by the `Then`-side page
|
|
618
|
+
assertion, so `Given User is on [X] page` ignores it; the `should see route` / `should remain on`
|
|
619
|
+
forms ignore it too — they compile through the route assertion, which is never handed an anchor; and
|
|
620
|
+
nothing warns when a page entry declares one and no step asserts that page.
|
|
621
|
+
|
|
572
622
|
`scope` (e.g. `scope: dialog`) is `[web]`-effective only — no Appium template reads `inDialog`, so
|
|
573
623
|
on mobile a dialog-scoped ref still resolves against the whole screen (`SG-W021`).
|
|
574
624
|
|
|
@@ -580,6 +630,7 @@ on mobile a dialog-scoped ref still resolves against the whole screen (`SG-W021`
|
|
|
580
630
|
|---|---|
|
|
581
631
|
| `@spec:<id>` | **Traceability**: this scenario proves requirement `<id>` from `requirements/spec.md` (`@spec:FR-003`). Repeatable — a scenario may prove several. Read by `sungen audit` (requirement coverage) and by delivery (the Coverage sheet's requirement table). Without it the link is only inferred from wording and cannot survive an edit. |
|
|
582
632
|
| `@manual` | Skip in generation |
|
|
633
|
+
| `@known-defect` | The scenario is EXPECTED to be red — it asserts a live defect. It still runs and still reports as failed (never skipped, never "fixed"), and it stays a pass-through tag so `--grep @known-defect` selects it. **`[mobile]`** Its failure does NOT escalate the next scenario's `@reset:` tier: a red that was designed in is not evidence of leftover state |
|
|
583
634
|
| `@auth:role` | Use auth storage state for role |
|
|
584
635
|
| `@no-auth` | Disable inherited auth |
|
|
585
636
|
| `@steps:name` | Define reusable step block (base scenario) |
|
|
@@ -645,6 +696,14 @@ Rules that matter when generating:
|
|
|
645
696
|
relaunch does not undo a half-written local DB. One rung, one scenario, and never from `none`.
|
|
646
697
|
A **retry** of the failed scenario is NOT escalated — it re-runs at its declared tier, so a
|
|
647
698
|
state-dependent failure fails both attempts and is reported honestly instead of passing on attempt 2.
|
|
699
|
+
A scenario tagged **`@known-defect`** escalates nothing either: its red was designed in, so making
|
|
700
|
+
the next scenario pay a harder reset for it buys nothing, on every run, forever.
|
|
701
|
+
- **Escalation never picks a tier this session cannot perform.** Reinstalling needs the app's build
|
|
702
|
+
file, so when the app is attached by package the ladder caps at `data` (Android) / `soft` (iOS) and
|
|
703
|
+
says it capped, instead of throwing. Nothing asked for the escalated tier, and the throw would land
|
|
704
|
+
in `beforeEach` — a hook failure, which skips every remaining scenario in the FILE. The cap never
|
|
705
|
+
goes BELOW the declared tier: a tier you asked for that the session cannot do still throws by name,
|
|
706
|
+
because that one is an unmet contract rather than a recovery the runner chose.
|
|
648
707
|
- **`SUNGEN_RESET_TIER=data` raises the floor for a whole run** without regenerating specs — CI can run
|
|
649
708
|
stricter than local dev. It never lowers a declared tier.
|
|
650
709
|
- **`data`/`full` on iOS reinstall the app**, because `mobile: clearApp` is Android-only (UiAutomator2).
|
|
@@ -724,7 +783,9 @@ npx playwright test --grep "@smoke|@high" # smoke OR high
|
|
|
724
783
|
| Serial (default) | `test.describe.serial()` | Shared | `beforeAll` (1 goto) | Skip remaining |
|
|
725
784
|
| `@parallel` | `test.describe()` | Fresh per test | `beforeEach` (N goto) | Continue |
|
|
726
785
|
|
|
727
|
-
|
|
786
|
+
**Multiple auth groups need the default isolation.** A feature mixing `@auth:user` with `@no-auth` compiles as-is — features are parallel by default, and `@parallel` only names that default. What breaks it is `@serial`: one shared browser context cannot carry two different sessions, so the compiler errors and asks you to drop `@serial` or split the feature.
|
|
787
|
+
|
|
788
|
+
`@no-auth` is a demand for an **empty** session, not merely the absence of `@auth:`. Its scenarios compile into their own `test.describe('no-auth')` with an inline empty `storageState`, so they stay signed out even when `playwright.config.ts` sets a global one. An untagged scenario is different again: it inherits whatever that config says.
|
|
728
789
|
|
|
729
790
|
### `@flow` tag (E2E cross-screen testing)
|
|
730
791
|
|
|
@@ -806,6 +867,7 @@ dashboard:
|
|
|
806
867
|
| Assertion clause the pattern drops | `Then User see [Code] text matches "<format>"` compiling to a bare `toBeVisible()` | Use the exact supported phrasing for the clause (see the `matches` / `contains` / `has text` forms above). A clause that gets swallowed leaves an assertion that passes whatever the element says, so the step is rejected with `SG-E023` instead. If the step still compiles bare, you are on an older global sungen — run `npx sungen` |
|
|
807
868
|
| Mobile lifecycle step with no app id | `When User restart the app` in a feature whose `Path:` carries only a route | Declare the app id on the Feature: `Path: com.example.app/.MainActivity \| com.example.app`. The lifecycle steps resolve the app from `Path`; without it the whole spec would die at runtime, so the step is rejected with `SG-E024`. For a mobile unit `--path` carries the APP ID, `--reach` carries the navigation recipe |
|
|
808
869
|
| Missing target type | `fill [email] with {{v}}` | `fill [email] field with {{v}}` |
|
|
870
|
+
| Asserting a cell with the row filter | `Then User see [Status] text in [Job Table] table with {{v}}` | The single `with` is read as the EXPECTED VALUE, not as a row filter, so the assertion runs against the WHOLE table and can pass on a row the sentence never named. Use the two-`with` form — `Then User see [Status] text with <expected> in [Job Row] row with {{v}}` — pointing the second `[…]` at a ROW entry, not at the table entry (a filter applied to the table narrows nothing). Warned with `SG-W032`. A `fill` is NOT flagged: a write needs a value, so its `with {{v}}` is that value and the step is an ordinary container-scoped write |
|
|
809
871
|
| Background with scope | `Background: ... And User is on [X] dialog` | Use `@steps` + `@extend` for scope-dependent flows |
|
|
810
872
|
| `is on` after When | `When ... And User is on [X] dialog` | `And User see [X] dialog` or separate Given |
|
|
811
873
|
| Literal URL navigate | `User navigate to "/dashboard"` | `User is on [Dashboard] page` (add page selector in `selectors.yaml`) |
|
|
@@ -128,7 +128,10 @@ back to the feature's own path (or `/<ref>/`) and warns `SG-W013`: a test that r
|
|
|
128
128
|
|
|
129
129
|
If the label is used by only ONE type, the base key (no suffix) is fine — suffix only on collision.
|
|
130
130
|
|
|
131
|
-
Type aliases (normalized automatically)
|
|
131
|
+
Type aliases (normalized automatically). The suffix and the step may use **different spellings of
|
|
132
|
+
the same type** — `confirm--modal` resolves for `[Confirm] dialog` and `confirm--dialog` resolves
|
|
133
|
+
for `[Confirm] modal`. Prefer the normalized suffix for consistency; either is found.
|
|
134
|
+
|
|
132
135
|
| Gherkin type | Normalized suffix |
|
|
133
136
|
|---|---|
|
|
134
137
|
| title, label, caption, message | `text` |
|
|
@@ -143,6 +146,15 @@ Type aliases (normalized automatically):
|
|
|
143
146
|
| column | `columnheader` |
|
|
144
147
|
| list-item | `listitem` |
|
|
145
148
|
|
|
149
|
+
When the step names a type from this table, only a key whose suffix is a spelling of that type is
|
|
150
|
+
used — `[Confirm] dialog` will not fall back to an unrelated `confirm--popup` entry. It reports the
|
|
151
|
+
keys it looked for, so the fix is to add one of them.
|
|
152
|
+
|
|
153
|
+
A bare `[Confirm]` keeps the older, looser fallback and matches any `confirm--*` entry, because with
|
|
154
|
+
no type there is nothing to match a suffix against. So does a `[Ref]` followed by a word that is not
|
|
155
|
+
an element type at all — `Then User see [Quick Panel] is hidden` reads `is` as that word. This is
|
|
156
|
+
what keeps state-clause steps working on a label whose keys are all suffixed.
|
|
157
|
+
|
|
146
158
|
## Nth-Suffixed Keys
|
|
147
159
|
|
|
148
160
|
When targeting a specific occurrence by index, add `--N` suffix:
|