@sun-asterisk/sungen 3.2.29 → 3.2.30
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/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +2 -1
- 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 +6 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/inspect.js +1 -1
- package/dist/cli/commands/inspect.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +9 -2
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts +24 -0
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -0
- package/dist/cli/commands/results-sanitize.js +126 -0
- package/dist/cli/commands/results-sanitize.js.map +1 -0
- package/dist/cli/commands/script-check.d.ts.map +1 -1
- package/dist/cli/commands/script-check.js +3 -1
- package/dist/cli/commands/script-check.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +3 -0
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +3 -6
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.js +7 -10
- package/dist/exporters/api-testcase-formatter.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +4 -2
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/render-coverage-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-coverage-sheet.js +3 -1
- package/dist/exporters/matrix/render-coverage-sheet.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +3 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-overview-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-overview-sheet.js +5 -9
- package/dist/exporters/matrix/render-overview-sheet.js.map +1 -1
- package/dist/exporters/matrix/result-totals.d.ts +23 -0
- package/dist/exporters/matrix/result-totals.d.ts.map +1 -0
- package/dist/exporters/matrix/result-totals.js +21 -0
- package/dist/exporters/matrix/result-totals.js.map +1 -0
- package/dist/exporters/matrix/testcase-summary-band.d.ts.map +1 -1
- package/dist/exporters/matrix/testcase-summary-band.js +2 -6
- package/dist/exporters/matrix/testcase-summary-band.js.map +1 -1
- package/dist/exporters/qa-env-secret-denylist.js +2 -2
- package/dist/exporters/qa-env-secret-denylist.js.map +1 -1
- package/dist/exporters/report-path-sanitizer.d.ts +38 -0
- package/dist/exporters/report-path-sanitizer.d.ts.map +1 -0
- package/dist/exporters/report-path-sanitizer.js +137 -0
- package/dist/exporters/report-path-sanitizer.js.map +1 -0
- package/dist/exporters/result-variants.d.ts.map +1 -1
- package/dist/exporters/result-variants.js +2 -1
- package/dist/exporters/result-variants.js.map +1 -1
- package/dist/exporters/sensitive-key.d.ts +31 -0
- package/dist/exporters/sensitive-key.d.ts.map +1 -0
- package/dist/exporters/sensitive-key.js +50 -0
- package/dist/exporters/sensitive-key.js.map +1 -0
- package/dist/exporters/test-data-resolver.d.ts +4 -1
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +12 -2
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +3 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +17 -9
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/gherkin-parser/parent-scope-clause.d.ts +54 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.d.ts.map +1 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.js +109 -0
- package/dist/generators/gherkin-parser/parent-scope-clause.js.map +1 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +23 -18
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +4 -19
- 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/after-all.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
- package/dist/generators/test-generator/adapters/index.d.ts +1 -1
- package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/playwright-adapter.d.ts +4 -19
- 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/after-all.hbs +19 -1
- package/dist/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
- package/dist/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
- package/dist/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
- package/dist/generators/test-generator/code-generator.d.ts +37 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +160 -39
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +79 -5
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +78 -4
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +24 -5
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +92 -14
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +18 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js +40 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +63 -9
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +166 -28
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +4 -19
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +17 -12
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +7 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +19 -2
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts +41 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.js +61 -0
- package/dist/generators/test-generator/utils/dialog-scope-identity.js.map +1 -0
- package/dist/generators/test-generator/utils/escape-js-string.d.ts +17 -0
- package/dist/generators/test-generator/utils/escape-js-string.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/escape-js-string.js +29 -0
- package/dist/generators/test-generator/utils/escape-js-string.js.map +1 -0
- package/dist/generators/test-generator/utils/nearest-match.d.ts +24 -0
- package/dist/generators/test-generator/utils/nearest-match.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/nearest-match.js +56 -0
- package/dist/generators/test-generator/utils/nearest-match.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts +58 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js +119 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.d.ts +25 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.js +54 -0
- package/dist/generators/test-generator/utils/selector-entry-walk.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.d.ts +59 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.js +91 -0
- package/dist/generators/test-generator/utils/selector-nth-validation.js.map +1 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +30 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +195 -15
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-type-validation.d.ts +51 -0
- package/dist/generators/test-generator/utils/selector-type-validation.d.ts.map +1 -0
- package/dist/generators/test-generator/utils/selector-type-validation.js +77 -0
- package/dist/generators/test-generator/utils/selector-type-validation.js.map +1 -0
- package/dist/harness/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +217 -33
- package/dist/harness/data-driven-lint.js.map +1 -1
- package/dist/harness/script-check.d.ts +2 -2
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +186 -25
- package/dist/harness/script-check.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +6 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +16 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/report-sanitize-reporter-install.d.ts +24 -0
- package/dist/orchestrator/report-sanitize-reporter-install.d.ts.map +1 -0
- package/dist/orchestrator/report-sanitize-reporter-install.js +161 -0
- package/dist/orchestrator/report-sanitize-reporter-install.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +9 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
- package/dist/orchestrator/templates/playwright.config.ts +7 -0
- package/dist/orchestrator/templates/specs-checked-state.d.ts +83 -0
- package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-checked-state.js +261 -0
- package/dist/orchestrator/templates/specs-checked-state.js.map +1 -0
- package/dist/orchestrator/templates/specs-checked-state.ts +324 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts +36 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.d.ts.map +1 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js +155 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.js.map +1 -0
- package/dist/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +50 -25
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +65 -24
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +101 -28
- package/dist/utils/selector-types.d.ts +43 -1
- package/dist/utils/selector-types.d.ts.map +1 -1
- package/dist/utils/selector-types.js +51 -7
- package/dist/utils/selector-types.js.map +1 -1
- package/package.json +3 -3
- package/src/cli/commands/delivery.ts +2 -1
- package/src/cli/commands/generate.ts +5 -2
- package/src/cli/commands/inspect.ts +1 -1
- package/src/cli/commands/results-merge.ts +9 -2
- package/src/cli/commands/results-sanitize.ts +98 -0
- package/src/cli/commands/script-check.ts +3 -1
- package/src/cli/commands/update.ts +3 -0
- package/src/exporters/api-testcase-formatter.ts +5 -7
- package/src/exporters/matrix/build.ts +4 -2
- package/src/exporters/matrix/render-coverage-sheet.ts +3 -1
- package/src/exporters/matrix/render-csv.ts +3 -1
- package/src/exporters/matrix/render-overview-sheet.ts +5 -10
- package/src/exporters/matrix/result-totals.ts +37 -0
- package/src/exporters/matrix/testcase-summary-band.ts +2 -7
- package/src/exporters/qa-env-secret-denylist.ts +1 -1
- package/src/exporters/report-path-sanitizer.ts +144 -0
- package/src/exporters/result-variants.ts +2 -1
- package/src/exporters/sensitive-key.ts +48 -0
- package/src/exporters/test-data-resolver.ts +12 -2
- package/src/generators/gherkin-parser/index.ts +21 -10
- package/src/generators/gherkin-parser/parent-scope-clause.ts +130 -0
- package/src/generators/test-generator/adapters/adapter-interface.ts +25 -3
- package/src/generators/test-generator/adapters/appium/appium-adapter.ts +4 -4
- package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +18 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-at-position-action.hbs +1 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +7 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +3 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-paginate-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +4 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +54 -23
- package/src/generators/test-generator/adapters/index.ts +1 -1
- package/src/generators/test-generator/adapters/playwright/playwright-adapter.ts +4 -4
- package/src/generators/test-generator/adapters/playwright/templates/after-all.hbs +19 -1
- package/src/generators/test-generator/adapters/playwright/templates/after-each.hbs +18 -0
- package/src/generators/test-generator/adapters/playwright/templates/before-all.hbs +19 -1
- package/src/generators/test-generator/adapters/playwright/templates/imports.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/check-action.hbs +6 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-at-position-action.hbs +23 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/expand-action.hbs +8 -8
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/fill-editor-action.hbs +7 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row-nth.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-paginate-action.hbs +5 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/uncheck-action.hbs +3 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/unknown-element-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/wait-for-page.hbs +18 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-assertion.hbs +32 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/all-contain-element.hbs +5 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/checked-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/column-cell-assertion.hbs +13 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/contain-text-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/disabled-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/have-text-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +2 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/hidden-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/not-checked-assertion.hbs +2 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/selected-assertion.hbs +14 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/state-with-filter-assertion.hbs +13 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +3 -3
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-value-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-variable-assertion.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/checked-opts.hbs +8 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-nth.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +10 -8
- package/src/generators/test-generator/code-generator.ts +176 -45
- package/src/generators/test-generator/diagnostics.ts +79 -5
- package/src/generators/test-generator/patterns/index.ts +97 -16
- package/src/generators/test-generator/patterns/with-parent-scope.ts +41 -0
- package/src/generators/test-generator/step-mapper.ts +170 -26
- package/src/generators/test-generator/template-engine.ts +21 -21
- package/src/generators/test-generator/utils/data-resolver.ts +23 -2
- package/src/generators/test-generator/utils/dialog-scope-identity.ts +71 -0
- package/src/generators/test-generator/utils/escape-js-string.ts +24 -0
- package/src/generators/test-generator/utils/nearest-match.ts +55 -0
- package/src/generators/test-generator/utils/selector-entry-fields.ts +131 -0
- package/src/generators/test-generator/utils/selector-entry-walk.ts +58 -0
- package/src/generators/test-generator/utils/selector-nth-validation.ts +99 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +245 -16
- package/src/generators/test-generator/utils/selector-type-validation.ts +87 -0
- package/src/harness/data-driven-lint.ts +192 -32
- package/src/harness/script-check.ts +186 -21
- package/src/index.ts +7 -0
- package/src/orchestrator/project-initializer.ts +17 -0
- package/src/orchestrator/report-sanitize-reporter-install.ts +109 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +9 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +10 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +14 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +108 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +1 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +9 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +4 -4
- package/src/orchestrator/templates/playwright.config.ts +7 -0
- package/src/orchestrator/templates/specs-checked-state.ts +324 -0
- package/src/orchestrator/templates/specs-report-path-sanitize-reporter.ts +142 -0
- package/src/orchestrator/templates/specs-url-assert.ts +101 -28
- package/src/utils/selector-types.ts +54 -5
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Sungen URL assertion helper — auto-generated into specs/url-assert.ts. DO NOT EDIT.
|
|
4
4
|
*
|
|
5
|
-
* Every Then-side
|
|
5
|
+
* Every URL step compiles to one call here: the Then-side assertions (`see [X] page`,
|
|
6
|
+
* `is on [X] page`) to `expectPage`, and `wait for [X] page` to `waitForPage`.
|
|
6
7
|
*
|
|
7
8
|
* Why a helper and not an inline predicate. The predicate used to be emitted inline into every
|
|
8
9
|
* spec, so each correction to what "the URL matches" MEANS was a breaking change for every
|
|
@@ -28,6 +29,16 @@ export interface UrlExpectation {
|
|
|
28
29
|
* against a full URL.
|
|
29
30
|
*/
|
|
30
31
|
pathname: RegExp;
|
|
32
|
+
/**
|
|
33
|
+
* Anchored hostname pattern, emitted ONLY when the page selector's `value` is an ABSOLUTE URL
|
|
34
|
+
* (`https://app.example.com/reception/:id`) — a destination on a different host from the suite's
|
|
35
|
+
* baseURL. Absent means "whatever host we are on", which is what a relative page value says.
|
|
36
|
+
*
|
|
37
|
+
* It is a SEPARATE field rather than part of `pathname` because the two are read off different
|
|
38
|
+
* properties of the parsed URL: `u.hostname` never appears in `u.pathname`. One concatenated
|
|
39
|
+
* regex only worked while the match ran unanchored over the whole URL string (#677).
|
|
40
|
+
*/
|
|
41
|
+
hostname?: RegExp;
|
|
31
42
|
/**
|
|
32
43
|
* The `with {{v}}` value, raw from test-data: '?q=1' · '/vi/search?q=1' · '#tab' · ''.
|
|
33
44
|
* `undefined` means the step declared nothing, which is itself an assertion — see below.
|
|
@@ -83,6 +94,15 @@ function decodedPath(pathname: string): string {
|
|
|
83
94
|
}
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Is the URL at the expected LOCATION — right host, right path? The half every URL step shares.
|
|
99
|
+
* The Then-side assertion goes on to judge the query; the wait stops here (see `waitForPage`).
|
|
100
|
+
*/
|
|
101
|
+
function locationMatches(pathname: RegExp, hostname: RegExp | undefined, u: URL): boolean {
|
|
102
|
+
if (hostname && !hostname.test(u.hostname)) return false;
|
|
103
|
+
return pathname.test(u.pathname) || pathname.test(decodedPath(u.pathname));
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
/**
|
|
87
107
|
* Build the predicate for `expect(page).toHaveURL(...)`.
|
|
88
108
|
*
|
|
@@ -94,9 +114,9 @@ function decodedPath(pathname: string): string {
|
|
|
94
114
|
* A declared fragment is asserted too; a declared value carrying no query
|
|
95
115
|
* at all constrains the path only (the compiler warns: SG-W014).
|
|
96
116
|
*/
|
|
97
|
-
export function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u: URL) => boolean {
|
|
117
|
+
export function urlMatches({ pathname, hostname, declared, dataRef }: UrlExpectation): (u: URL) => boolean {
|
|
98
118
|
return (u: URL): boolean => {
|
|
99
|
-
if (!
|
|
119
|
+
if (!locationMatches(pathname, hostname, u)) return false;
|
|
100
120
|
|
|
101
121
|
// `dataRef`, not `declared`, decides which rule applies. Both "the step declared nothing" and
|
|
102
122
|
// "the step declared {{v}} but the key resolved to nothing at runtime" arrive here as
|
|
@@ -135,38 +155,43 @@ export function urlMatches({ pathname, declared, dataRef }: UrlExpectation): (u:
|
|
|
135
155
|
};
|
|
136
156
|
}
|
|
137
157
|
|
|
158
|
+
/** What `settleAndHold` needs of a page — Playwright's `Page` satisfies it; so does a test fake. */
|
|
159
|
+
type SettleablePage = {
|
|
160
|
+
url(): string;
|
|
161
|
+
waitForLoadState(state: 'networkidle', opts?: { timeout?: number }): Promise<void>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** The two runtime knobs, shared by both URL steps. */
|
|
165
|
+
interface SettleOpts { settleMs?: number; networkIdleMs?: number }
|
|
166
|
+
|
|
138
167
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
168
|
+
* A URL that MATCHES is not a page the user is still on. A client-side router pushes the
|
|
169
|
+
* destination URL before the route guard or the API has answered, so a user with no permission is
|
|
170
|
+
* "on" `/items/detail` for a few hundred milliseconds — long enough for a polling assertion to
|
|
171
|
+
* sample it and pass, or for a `waitForURL` to resolve — and is then bounced to `/forbidden` with
|
|
172
|
+
* nothing left watching. A real project shipped a permission matrix on that oracle; the 403s were
|
|
173
|
+
* found by hand (#662).
|
|
145
174
|
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* 2.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* NAME where it went, because that destination is the finding;
|
|
153
|
-
* 4. re-assert — the arrival predicate, once more, on the settled page.
|
|
175
|
+
* So both URL steps go through this: having arrived,
|
|
176
|
+
* 1. settle — wait for network to go quiet (bounded: an app that polls forever must not hang
|
|
177
|
+
* the test, so a timeout here is not a failure);
|
|
178
|
+
* 2. hold — sample the URL over a settle window; the moment it stops matching, fail and
|
|
179
|
+
* NAME where it went, because that destination is the finding.
|
|
180
|
+
* The caller decides what arriving meant and what to do afterwards.
|
|
154
181
|
*
|
|
155
182
|
* `SUNGEN_URL_SETTLE_MS` (default 1000) is the hold window; `SUNGEN_URL_NETWORKIDLE_MS` (default
|
|
156
|
-
* 3000) bounds step
|
|
183
|
+
* 3000) bounds step 1. Both are runtime knobs, so a slow environment is a config change, not a
|
|
157
184
|
* regenerate.
|
|
158
185
|
*/
|
|
159
|
-
|
|
160
|
-
page:
|
|
161
|
-
|
|
162
|
-
opts:
|
|
186
|
+
async function settleAndHold(
|
|
187
|
+
page: SettleablePage,
|
|
188
|
+
matches: (u: URL) => boolean,
|
|
189
|
+
opts: SettleOpts,
|
|
190
|
+
label: string,
|
|
191
|
+
consequence: string,
|
|
163
192
|
): Promise<void> {
|
|
164
193
|
const settleMs = opts.settleMs ?? Number(process.env.SUNGEN_URL_SETTLE_MS ?? 1000);
|
|
165
194
|
const networkIdleMs = opts.networkIdleMs ?? Number(process.env.SUNGEN_URL_NETWORKIDLE_MS ?? 3000);
|
|
166
|
-
const matches = urlMatches(expectation);
|
|
167
|
-
const assertUrl = opts.assertUrl ?? (async (m: (u: URL) => boolean) => { await expect(page as unknown as Page).toHaveURL(m); });
|
|
168
|
-
|
|
169
|
-
await assertUrl(matches);
|
|
170
195
|
const arrivedAt = page.url();
|
|
171
196
|
|
|
172
197
|
await page.waitForLoadState('networkidle', { timeout: networkIdleMs }).catch(() => undefined);
|
|
@@ -176,13 +201,61 @@ export async function expectPage(
|
|
|
176
201
|
const now = page.url();
|
|
177
202
|
if (!matches(new URL(now))) {
|
|
178
203
|
throw new Error(
|
|
179
|
-
|
|
204
|
+
`${label}: reached ${arrivedAt} and then left for ${now} after ${Date.now() - started}ms — `
|
|
180
205
|
+ 'a route guard, an error boundary or a redirect moved the user away after the URL had already '
|
|
181
|
-
+
|
|
206
|
+
+ `matched. ${consequence}`,
|
|
182
207
|
);
|
|
183
208
|
}
|
|
184
209
|
await new Promise((r) => setTimeout(r, 50));
|
|
185
210
|
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* `expectPage` is the page oracle `see [X] page` / `is on [X] page` compile to. Arriving is not
|
|
215
|
+
* enough: the URL has to be STILL matching once the page has settled.
|
|
216
|
+
* 1. arrive — the same predicate `toHaveURL` always used;
|
|
217
|
+
* 2. settle + hold — see `settleAndHold`;
|
|
218
|
+
* 3. re-assert — the arrival predicate, once more, on the settled page.
|
|
219
|
+
*/
|
|
220
|
+
export async function expectPage(
|
|
221
|
+
page: SettleablePage,
|
|
222
|
+
expectation: UrlExpectation,
|
|
223
|
+
opts: SettleOpts & { assertUrl?: (matches: (u: URL) => boolean) => Promise<void> } = {},
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
const matches = urlMatches(expectation);
|
|
226
|
+
const assertUrl = opts.assertUrl ?? (async (m: (u: URL) => boolean) => { await expect(page as unknown as Page).toHaveURL(m); });
|
|
186
227
|
|
|
187
228
|
await assertUrl(matches);
|
|
229
|
+
await settleAndHold(page, matches, opts, 'page assertion',
|
|
230
|
+
'The page the test asserted is not the page the user ended on.');
|
|
231
|
+
await assertUrl(matches);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* `waitForPage` is what `When User wait for [X] page` compiles to (#677).
|
|
236
|
+
*
|
|
237
|
+
* A bare `page.waitForURL(/…/)` resolves on the first URL sample that matches — the transient URL
|
|
238
|
+
* the router pushed before its guard decided. The wait then returned, and every step AFTER it ran
|
|
239
|
+
* against a page mid-redirect, so their failures pointed at the wrong thing. It was also the third
|
|
240
|
+
* hand-rolled copy of "does this URL match", drifting from the pair #662 consolidated here.
|
|
241
|
+
*
|
|
242
|
+
* Where this deliberately DIFFERS from `expectPage`: the query is not constrained. A Then-side page
|
|
243
|
+
* assertion is an oracle, so a bare page must be bare — `/vi/search` fails on `/vi/search?x=1`. A
|
|
244
|
+
* wait is not an oracle: `wait for [Results] page` after submitting a search legitimately lands on
|
|
245
|
+
* `/results?q=abc`, and reddening there would assert something the step never said. Prove the query
|
|
246
|
+
* with the `Then User see [Results] page with {{v}}` that follows.
|
|
247
|
+
*/
|
|
248
|
+
export async function waitForPage(
|
|
249
|
+
page: SettleablePage & { waitForURL(url: (u: URL) => boolean, opts?: { timeout?: number }): Promise<void> },
|
|
250
|
+
expectation: UrlExpectation,
|
|
251
|
+
opts: SettleOpts & { timeout?: number; waitUrl?: (matches: (u: URL) => boolean) => Promise<void> } = {},
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
const { pathname, hostname } = expectation;
|
|
254
|
+
const matches = (u: URL): boolean => locationMatches(pathname, hostname, u);
|
|
255
|
+
// Playwright's own navigation timeout governs the arrival; only the hold is sungen's.
|
|
256
|
+
const arrive = opts.waitUrl ?? (async (m: (u: URL) => boolean) => { await page.waitForURL(m, { timeout: opts.timeout }); });
|
|
257
|
+
|
|
258
|
+
await arrive(matches);
|
|
259
|
+
await settleAndHold(page, matches, opts, 'page wait',
|
|
260
|
+
'The steps after this wait would have run against a page the user is already being moved off.');
|
|
188
261
|
}
|
|
@@ -1,7 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Valid selector types used across scaffold generator, selector resolver, and validator.
|
|
3
3
|
* Single source of truth — import this instead of hardcoding.
|
|
4
|
+
*
|
|
5
|
+
* The list is partitioned deliberately. A type is either a LOCATOR STRATEGY (the resolver's
|
|
6
|
+
* strategy switch turns it into a Playwright/WDIO locator) or a NON-STRATEGY declaration (the
|
|
7
|
+
* entry describes something other than how to find an element, and a reader downstream of the
|
|
8
|
+
* resolver consumes it). A type that is in neither set but is still accepted as valid is drift:
|
|
9
|
+
* it reaches the switch's `default:` and resolves as `getByPlaceholder('<whatever value held>')`,
|
|
10
|
+
* a selector that cannot match. `upload` sat in exactly that gap — valid, documented, and
|
|
11
|
+
* silently mis-resolved (issue #690).
|
|
12
|
+
*
|
|
13
|
+
* Keep the partition total. `tests/codegen/selector-type-dispatch.run.ts` fails if a new member
|
|
14
|
+
* of VALID_SELECTOR_TYPES is classified into neither half, or if a strategy type stops being
|
|
15
|
+
* dispatched and starts resolving as a placeholder.
|
|
4
16
|
*/
|
|
5
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Types the resolver turns into a locator. Every member MUST be dispatched in
|
|
19
|
+
* `SelectorResolver.resolveFromEntry` — either by a `case` in its strategy switch or by an
|
|
20
|
+
* early return above it (`locator` and `upload` share one). The contract is on the RESOLVED
|
|
21
|
+
* STRATEGY, not on the syntax used to produce it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const STRATEGY_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "locator", "id", "upload", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate", "css"];
|
|
24
|
+
/**
|
|
25
|
+
* Types that declare something OTHER than a locator strategy, so they have no `case` by design.
|
|
26
|
+
* Each is read after resolution via `ResolvedSelector.selectorType`, or by the pattern that owns
|
|
27
|
+
* the step, which reads the entry's fields directly:
|
|
28
|
+
*
|
|
29
|
+
* page — `value` is a URL path, not a locator. The page assertion reads `selectorType`
|
|
30
|
+
* AFTER resolving (see StepMapper.checkPageStepFallback), so this type must
|
|
31
|
+
* pass through the switch rather than throw.
|
|
32
|
+
* table — the table patterns read `columns` (StepMapper's column-index lookup), `rows` (the
|
|
33
|
+
* row templates, backed by SG-W016) and `pagination` (the paginate step, #712) off
|
|
34
|
+
* the entry. `pagination` was the NOTE that used to sit here: documented, plumbed,
|
|
35
|
+
* and read by nothing — the same drift shape as #690, one level down. It has a
|
|
36
|
+
* consumer now, and SG-W004 is the gate that makes the next such field fail a check
|
|
37
|
+
* rather than wait to be grepped.
|
|
38
|
+
* column — addressed through its table's `columns` map, never resolved standalone.
|
|
39
|
+
* frame — consumed by the frame-enter/exit steps, which need the raw iframe selector.
|
|
40
|
+
*
|
|
41
|
+
* Reaching the strategy switch is legitimate for these; the placeholder fallback there is inert
|
|
42
|
+
* because no consumer reads `strategy` for them.
|
|
43
|
+
*/
|
|
44
|
+
export declare const NON_STRATEGY_SELECTOR_TYPES: readonly ["page", "column", "frame", "table"];
|
|
45
|
+
export declare const VALID_SELECTOR_TYPES: readonly ["placeholder", "role", "testid", "label", "text", "locator", "id", "upload", "accessibility-id", "xpath", "android-uiautomator", "ios-predicate", "css", "page", "column", "frame", "table"];
|
|
46
|
+
export type StrategySelectorType = (typeof STRATEGY_SELECTOR_TYPES)[number];
|
|
47
|
+
export type NonStrategySelectorType = (typeof NON_STRATEGY_SELECTOR_TYPES)[number];
|
|
6
48
|
export type SelectorType = (typeof VALID_SELECTOR_TYPES)[number];
|
|
7
49
|
//# sourceMappingURL=selector-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"selector-types.d.ts","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,oKAe1B,CAAC;AAEX;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,+CAK9B,CAAC;AAEX,eAAO,MAAM,oBAAoB,wMAGvB,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AACnF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VALID_SELECTOR_TYPES = void 0;
|
|
4
2
|
/**
|
|
5
3
|
* Valid selector types used across scaffold generator, selector resolver, and validator.
|
|
6
4
|
* Single source of truth — import this instead of hardcoding.
|
|
5
|
+
*
|
|
6
|
+
* The list is partitioned deliberately. A type is either a LOCATOR STRATEGY (the resolver's
|
|
7
|
+
* strategy switch turns it into a Playwright/WDIO locator) or a NON-STRATEGY declaration (the
|
|
8
|
+
* entry describes something other than how to find an element, and a reader downstream of the
|
|
9
|
+
* resolver consumes it). A type that is in neither set but is still accepted as valid is drift:
|
|
10
|
+
* it reaches the switch's `default:` and resolves as `getByPlaceholder('<whatever value held>')`,
|
|
11
|
+
* a selector that cannot match. `upload` sat in exactly that gap — valid, documented, and
|
|
12
|
+
* silently mis-resolved (issue #690).
|
|
13
|
+
*
|
|
14
|
+
* Keep the partition total. `tests/codegen/selector-type-dispatch.run.ts` fails if a new member
|
|
15
|
+
* of VALID_SELECTOR_TYPES is classified into neither half, or if a strategy type stops being
|
|
16
|
+
* dispatched and starts resolving as a placeholder.
|
|
7
17
|
*/
|
|
8
|
-
exports
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.VALID_SELECTOR_TYPES = exports.NON_STRATEGY_SELECTOR_TYPES = exports.STRATEGY_SELECTOR_TYPES = void 0;
|
|
20
|
+
/**
|
|
21
|
+
* Types the resolver turns into a locator. Every member MUST be dispatched in
|
|
22
|
+
* `SelectorResolver.resolveFromEntry` — either by a `case` in its strategy switch or by an
|
|
23
|
+
* early return above it (`locator` and `upload` share one). The contract is on the RESOLVED
|
|
24
|
+
* STRATEGY, not on the syntax used to produce it.
|
|
25
|
+
*/
|
|
26
|
+
exports.STRATEGY_SELECTOR_TYPES = [
|
|
9
27
|
'placeholder',
|
|
10
28
|
'role',
|
|
11
29
|
'testid',
|
|
12
30
|
'label',
|
|
13
31
|
'text',
|
|
14
|
-
'page',
|
|
15
|
-
'column',
|
|
16
32
|
'locator',
|
|
17
33
|
'id',
|
|
18
34
|
'upload',
|
|
19
|
-
'frame',
|
|
20
|
-
'table',
|
|
21
35
|
// === Mobile (Appium) strategies — resolved by the appium adapter (MOB-3) ===
|
|
22
36
|
'accessibility-id', // cross-platform — Android content-desc / iOS accessibilityIdentifier
|
|
23
37
|
'xpath', // cross-platform, last resort
|
|
@@ -25,4 +39,34 @@ exports.VALID_SELECTOR_TYPES = [
|
|
|
25
39
|
'ios-predicate', // iOS only — NSPredicate strings
|
|
26
40
|
'css', // hybrid WebView only — CSS inside a WEBVIEW context (after `switch to [X] frame`)
|
|
27
41
|
];
|
|
42
|
+
/**
|
|
43
|
+
* Types that declare something OTHER than a locator strategy, so they have no `case` by design.
|
|
44
|
+
* Each is read after resolution via `ResolvedSelector.selectorType`, or by the pattern that owns
|
|
45
|
+
* the step, which reads the entry's fields directly:
|
|
46
|
+
*
|
|
47
|
+
* page — `value` is a URL path, not a locator. The page assertion reads `selectorType`
|
|
48
|
+
* AFTER resolving (see StepMapper.checkPageStepFallback), so this type must
|
|
49
|
+
* pass through the switch rather than throw.
|
|
50
|
+
* table — the table patterns read `columns` (StepMapper's column-index lookup), `rows` (the
|
|
51
|
+
* row templates, backed by SG-W016) and `pagination` (the paginate step, #712) off
|
|
52
|
+
* the entry. `pagination` was the NOTE that used to sit here: documented, plumbed,
|
|
53
|
+
* and read by nothing — the same drift shape as #690, one level down. It has a
|
|
54
|
+
* consumer now, and SG-W004 is the gate that makes the next such field fail a check
|
|
55
|
+
* rather than wait to be grepped.
|
|
56
|
+
* column — addressed through its table's `columns` map, never resolved standalone.
|
|
57
|
+
* frame — consumed by the frame-enter/exit steps, which need the raw iframe selector.
|
|
58
|
+
*
|
|
59
|
+
* Reaching the strategy switch is legitimate for these; the placeholder fallback there is inert
|
|
60
|
+
* because no consumer reads `strategy` for them.
|
|
61
|
+
*/
|
|
62
|
+
exports.NON_STRATEGY_SELECTOR_TYPES = [
|
|
63
|
+
'page',
|
|
64
|
+
'column',
|
|
65
|
+
'frame',
|
|
66
|
+
'table',
|
|
67
|
+
];
|
|
68
|
+
exports.VALID_SELECTOR_TYPES = [
|
|
69
|
+
...exports.STRATEGY_SELECTOR_TYPES,
|
|
70
|
+
...exports.NON_STRATEGY_SELECTOR_TYPES,
|
|
71
|
+
];
|
|
28
72
|
//# sourceMappingURL=selector-types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selector-types.js","sourceRoot":"","sources":["../../src/utils/selector-types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH;;;;;GAKG;AACU,QAAA,uBAAuB,GAAG;IACrC,aAAa;IACb,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,SAAS;IACT,IAAI;IACJ,QAAQ;IACR,8EAA8E;IAC9E,kBAAkB,EAAK,sEAAsE;IAC7F,OAAO,EAAgB,8BAA8B;IACrD,qBAAqB,EAAE,wCAAwC;IAC/D,eAAe,EAAQ,iCAAiC;IACxD,KAAK,EAAkB,mFAAmF;CAClG,CAAC;AAEX;;;;;;;;;;;;;;;;;;;GAmBG;AACU,QAAA,2BAA2B,GAAG;IACzC,MAAM;IACN,QAAQ;IACR,OAAO;IACP,OAAO;CACC,CAAC;AAEE,QAAA,oBAAoB,GAAG;IAClC,GAAG,+BAAuB;IAC1B,GAAG,mCAA2B;CACtB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.30",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@babel/types": "^7.28.5",
|
|
41
41
|
"@cucumber/gherkin": "^37.0.0",
|
|
42
42
|
"@cucumber/messages": "^31.0.0",
|
|
43
|
-
"@sungen/driver-data-factory": "3.2.
|
|
44
|
-
"@sungen/driver-ui": "3.2.
|
|
43
|
+
"@sungen/driver-data-factory": "3.2.30",
|
|
44
|
+
"@sungen/driver-ui": "3.2.30",
|
|
45
45
|
"chalk": "^5.6.2",
|
|
46
46
|
"commander": "^14.0.2",
|
|
47
47
|
"dotenv": "^17.2.3",
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
hasReview,
|
|
40
40
|
} from '../../exporters/matrix/export';
|
|
41
41
|
import { MatrixFinding } from '../../exporters/matrix/types';
|
|
42
|
+
import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
|
|
42
43
|
|
|
43
44
|
const COLOR = {
|
|
44
45
|
reset: '\x1b[0m',
|
|
@@ -703,7 +704,7 @@ async function exportMatrixTarget(
|
|
|
703
704
|
return null;
|
|
704
705
|
}
|
|
705
706
|
|
|
706
|
-
log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s),
|
|
707
|
+
log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), review: ${model.manifest.approvalState} (design), test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
|
|
707
708
|
{
|
|
708
709
|
const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
|
|
709
710
|
if (mocked.length > 0) {
|
|
@@ -238,8 +238,11 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
238
238
|
const advisorySensors = capabilityRegistry.sensors('advisory');
|
|
239
239
|
const ddWarnings = scanDirs.flatMap((d) => advisorySensors.flatMap((s) => s.run({ dir: d, cwd })));
|
|
240
240
|
if (ddWarnings.length) {
|
|
241
|
-
console.log(`\n⚠️ Data-driven lint (@cases / @query) — review:`);
|
|
242
|
-
|
|
241
|
+
console.log(`\n⚠️ Data-driven lint (@cases / @query / dead test-data) — advisory, review:`);
|
|
242
|
+
const verbose = !!program.opts().verbose;
|
|
243
|
+
const shown = verbose ? ddWarnings : ddWarnings.slice(0, 20);
|
|
244
|
+
for (const w of shown) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
|
|
245
|
+
if (shown.length < ddWarnings.length) console.log(` … and ${ddWarnings.length - shown.length} more (run with -v to list all)`);
|
|
243
246
|
}
|
|
244
247
|
|
|
245
248
|
const nextStep = platform === 'mobile' ? 'npm run test:mobile' : 'npx playwright test --ui';
|
|
@@ -63,7 +63,7 @@ export function inspectProject(cwd: string): ProjectInspection {
|
|
|
63
63
|
const perfPass = model.perf ? model.perf.pass : null;
|
|
64
64
|
|
|
65
65
|
const reasons: string[] = [];
|
|
66
|
-
if (!approved) reasons.push('delivery map not approved');
|
|
66
|
+
if (!approved) reasons.push('delivery map not approved (design review state — not a test outcome)');
|
|
67
67
|
if (!frozen) reasons.push('sources drifted since approval (not frozen — Gate E)');
|
|
68
68
|
if (failed.length) reasons.push(`${failed.length} golden variant(s) FAILED`);
|
|
69
69
|
if (pending.length) reasons.push(`${pending.length} golden variant(s) have no run result`);
|
|
@@ -9,6 +9,8 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { mergeResultsReports, findGhostTitles } from '../../exporters/results-merge';
|
|
12
|
+
import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
|
|
13
|
+
import { registerResultsSanitizeCommand } from './results-sanitize';
|
|
12
14
|
import { resolveResultsPath } from '../../exporters/playwright-report-parser';
|
|
13
15
|
import { parseSpecFile } from '../../exporters/spec-parser';
|
|
14
16
|
|
|
@@ -95,8 +97,12 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
|
|
|
95
97
|
throw new Error('results merge: expected <base.json> <new.json>, or --screen <name> <new.json>.');
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
// Each report relativizes against ITS OWN root before the merge: `config` is copied from the base
|
|
101
|
+
// wholesale, so a base produced on another machine would otherwise carry that machine's paths
|
|
102
|
+
// into the merged file for good.
|
|
103
|
+
const sanitize = (doc: any): any => sanitizeReportPaths(doc, { root: detectReportRoot(doc) ?? cwd }).doc;
|
|
104
|
+
const base = sanitize(readReport(baseFile, 'base'));
|
|
105
|
+
const fresh = sanitize(readReport(newFile, 'new'));
|
|
100
106
|
const { merged, replaced, added, kept } = mergeResultsReports(base, fresh);
|
|
101
107
|
|
|
102
108
|
if (specTitles) {
|
|
@@ -136,6 +142,7 @@ export function registerResultsCommand(program: Command): void {
|
|
|
136
142
|
const results = program
|
|
137
143
|
.command('results')
|
|
138
144
|
.description('Operate on per-target Playwright result files');
|
|
145
|
+
registerResultsSanitizeCommand(results);
|
|
139
146
|
|
|
140
147
|
results
|
|
141
148
|
.command('merge [base] [new]')
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen results sanitize [files…]` — rewrite the operator's machine paths out of result/report JSON.
|
|
3
|
+
*
|
|
4
|
+
* Playwright's JSON reporter and sungen's own reports record absolute paths, and teams commit those
|
|
5
|
+
* files next to the spec (delivery reads them). The reporter registered in `playwright.config.ts`
|
|
6
|
+
* sanitizes a fresh run's report on exit; this command is the safety net that reaches every file a
|
|
7
|
+
* project already has — and every project whose config predates the reporter. Idempotent: a clean
|
|
8
|
+
* file is left untouched (no rewrite, no timestamp churn).
|
|
9
|
+
*
|
|
10
|
+
* Each report relativizes against ITS OWN root (`dirname(config.configFile)` when it carries one), so
|
|
11
|
+
* a file produced on another machine comes out right too; a report that does not say falls back to
|
|
12
|
+
* the working directory.
|
|
13
|
+
*/
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import fg from 'fast-glob';
|
|
18
|
+
import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
|
|
19
|
+
|
|
20
|
+
/** Where a project keeps the JSON files that carry paths. */
|
|
21
|
+
export const DEFAULT_REPORT_GLOBS = [
|
|
22
|
+
'specs/generated/**/*-test-result*.json',
|
|
23
|
+
'test-result.json',
|
|
24
|
+
'*-test-result*.json',
|
|
25
|
+
'.sungen/reports/*-script-check.json', // the one sungen report that records a path (`specPath`)
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export interface SanitizedFile {
|
|
29
|
+
file: string; // relative to cwd
|
|
30
|
+
rewritten: number; // string values changed (0 = already clean)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Sanitize the given files (or the project's default report locations). Returns one row per file. */
|
|
34
|
+
export function runResultsSanitize(files: string[] | undefined, cwd: string = process.cwd()): SanitizedFile[] {
|
|
35
|
+
const targets = files && files.length
|
|
36
|
+
? files.map((f) => path.resolve(cwd, f))
|
|
37
|
+
: fg.sync(DEFAULT_REPORT_GLOBS, { cwd, absolute: true, onlyFiles: true, dot: true });
|
|
38
|
+
const out: SanitizedFile[] = [];
|
|
39
|
+
for (const file of targets.sort()) {
|
|
40
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
41
|
+
let doc: unknown;
|
|
42
|
+
try {
|
|
43
|
+
doc = JSON.parse(raw);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw new Error(`results sanitize: ${path.relative(cwd, file)} is not JSON (${err instanceof Error ? err.message : String(err)})`);
|
|
46
|
+
}
|
|
47
|
+
// A report that does not describe its root belongs to the project it sits in — the nearest
|
|
48
|
+
// ancestor holding a playwright.config.ts or .sungen/ — not to wherever the command was run from
|
|
49
|
+
// (a monorepo root would otherwise turn `specs/generated/x/x.spec.ts` into a bare basename).
|
|
50
|
+
const root = detectReportRoot(doc) ?? nearestProjectRoot(path.dirname(file)) ?? cwd;
|
|
51
|
+
const { doc: clean, rewritten } = sanitizeReportPaths(doc, { root });
|
|
52
|
+
if (rewritten > 0) {
|
|
53
|
+
// Atomic write, same as `results merge`: a crash mid-write must never leave a truncated report.
|
|
54
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(tmp, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
|
|
57
|
+
fs.renameSync(tmp, file);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out.push({ file: path.relative(cwd, file), rewritten });
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerResultsSanitizeCommand(results: Command): void {
|
|
69
|
+
results
|
|
70
|
+
.command('sanitize [files...]')
|
|
71
|
+
.description('Rewrite absolute machine paths in result/report JSON to project-relative ones (default: every *-test-result*.json under specs/generated and .sungen/reports/*.json)')
|
|
72
|
+
.action((files: string[]) => {
|
|
73
|
+
try {
|
|
74
|
+
const rows = runResultsSanitize(files.length ? files : undefined);
|
|
75
|
+
if (rows.length === 0) {
|
|
76
|
+
console.log('results sanitize: no report files found.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const changed = rows.filter((r) => r.rewritten > 0);
|
|
80
|
+
for (const r of changed) console.log(`✓ ${r.file} — ${r.rewritten} path(s) made project-relative`);
|
|
81
|
+
console.log(`results sanitize: ${changed.length} of ${rows.length} file(s) rewritten${changed.length === 0 ? ' (all already clean)' : ''}.`);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Nearest ancestor directory that looks like a sungen/Playwright project root. */
|
|
90
|
+
function nearestProjectRoot(from: string): string | undefined {
|
|
91
|
+
let dir = from;
|
|
92
|
+
for (;;) {
|
|
93
|
+
if (fs.existsSync(path.join(dir, 'playwright.config.ts')) || fs.existsSync(path.join(dir, '.sungen'))) return dir;
|
|
94
|
+
const parent = path.dirname(dir);
|
|
95
|
+
if (parent === dir) return undefined;
|
|
96
|
+
dir = parent;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -3,6 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { runScriptCheck } from '../../harness/script-check';
|
|
5
5
|
import { reportSlug } from '../../harness/unit-paths';
|
|
6
|
+
import { sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
|
|
6
7
|
|
|
7
8
|
export function registerScriptCheckCommand(program: Command): void {
|
|
8
9
|
program
|
|
@@ -23,7 +24,8 @@ export function registerScriptCheckCommand(program: Command): void {
|
|
|
23
24
|
const dir = kind === 'screen' ? screen : kind === 'flow' ? flow : kind === 'api' ? api : null;
|
|
24
25
|
if (!dir || !kind) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// The report is committed next to the spec — keep the operator's machine paths out of it.
|
|
28
|
+
const r = sanitizeReportPaths(await runScriptCheck(dir, name, kind), { root: process.cwd() }).doc;
|
|
27
29
|
|
|
28
30
|
const outDir = path.join(process.cwd(), '.sungen', 'reports');
|
|
29
31
|
fs.mkdirSync(outDir, { recursive: true });
|
|
@@ -100,6 +100,9 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
100
100
|
const { AIRulesUpdater } = require('../../orchestrator/ai-rules-updater');
|
|
101
101
|
const updater = new AIRulesUpdater(process.cwd());
|
|
102
102
|
await updater.update(options.dryRun ?? false, { agents: resolveAssistants(options.assistant), force: options.force ?? false });
|
|
103
|
+
// playwright.config.ts is written once at init and never re-synced — patch the reporter in.
|
|
104
|
+
const { installReportSanitizeReporter } = require('../../orchestrator/report-sanitize-reporter-install');
|
|
105
|
+
installReportSanitizeReporter(process.cwd(), options.dryRun ?? false);
|
|
103
106
|
} catch (error) {
|
|
104
107
|
console.error('❌ Update failed:', error);
|
|
105
108
|
process.exit(1);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { ApiCatalogEntry } from './types';
|
|
19
|
+
import { SENSITIVE_NAME, isSensitiveKey } from './sensitive-key';
|
|
19
20
|
|
|
20
21
|
/** A single `@api:<name>(k=v,…)` invocation parsed from a scenario's tags. */
|
|
21
22
|
export interface ApiCall {
|
|
@@ -372,12 +373,6 @@ function formatBodyFile(bodyFile: unknown, args: Record<string, string>, localiz
|
|
|
372
373
|
return `${p} (${spec.mimeType || 'application/octet-stream'})`;
|
|
373
374
|
}
|
|
374
375
|
|
|
375
|
-
/**
|
|
376
|
-
* Credential name matcher (substring, not anchored) for both body-field keys and
|
|
377
|
-
* test-data keys — catches variants like confirmPassword / new_password /
|
|
378
|
-
* password_confirmation / access_token / client_secret / otp / passcode.
|
|
379
|
-
*/
|
|
380
|
-
export const SENSITIVE_NAME = /pass(word|code)?|passwd|pwd|secret|token|otp|api[-_]?key|apikey|credential|authorization|signature/i;
|
|
381
376
|
function isSensitiveField(key: string): boolean {
|
|
382
377
|
return SENSITIVE_NAME.test(key);
|
|
383
378
|
}
|
|
@@ -387,11 +382,14 @@ function isSensitiveField(key: string): boolean {
|
|
|
387
382
|
* over the finished Steps/Expected cells, closing the paths key-based masking misses
|
|
388
383
|
* (nested request bodies, query-string credentials, and the Expected assertion cell).
|
|
389
384
|
* Values shorter than 3 chars are skipped to avoid masking trivial/coincidental text.
|
|
385
|
+
* Sensitivity is judged by the key segment that NAMES the value (see isSensitiveKey): the
|
|
386
|
+
* test-data map also carries per-index array leaves (`tokens.0.label`), and matching the
|
|
387
|
+
* whole key would redact every field under a list whose name happens to be a credential word.
|
|
390
388
|
*/
|
|
391
389
|
export function collectSecretValues(testData: Record<string, string>): string[] {
|
|
392
390
|
const secrets: string[] = [];
|
|
393
391
|
for (const [k, v] of Object.entries(testData)) {
|
|
394
|
-
if (typeof v === 'string' && v.length >= 3 &&
|
|
392
|
+
if (typeof v === 'string' && v.length >= 3 && isSensitiveKey(k)) secrets.push(v);
|
|
395
393
|
}
|
|
396
394
|
// Longest-first so a longer secret is redacted before any shorter substring of it.
|
|
397
395
|
return [...new Set(secrets)].sort((a, b) => b.length - a.length);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '../feature-parser';
|
|
19
19
|
import { getCasesDatasetRows, resolveResultVariants } from '../result-variants';
|
|
20
20
|
import { renderDynamicTokens } from '../test-data-resolver';
|
|
21
|
+
import { maskCredentialValue } from '../sensitive-key';
|
|
21
22
|
import { classifyManualComments, renderAction, renderExpected, renderPrecondition, renderSetupInstruction, runtimeVarsOf } from './wording';
|
|
22
23
|
import { scenarioFingerprint, combinedFingerprint, mapContentFingerprint } from './fingerprint';
|
|
23
24
|
import {
|
|
@@ -100,12 +101,13 @@ function resolveDataPairs(
|
|
|
100
101
|
if (row) {
|
|
101
102
|
for (const [k, v] of Object.entries(row)) {
|
|
102
103
|
if (k.startsWith('__') || k === 'case' || k === 'name' || k === 'label') continue;
|
|
103
|
-
pairs.push(`${k}: ${displayValue(String(v))}`);
|
|
104
|
+
pairs.push(`${k}: ${displayValue(maskCredentialValue(k, String(v)))}`);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
for (const v of vars) {
|
|
107
108
|
const val = row && v in row ? undefined : testData?.[v]; // row columns already listed
|
|
108
|
-
|
|
109
|
+
// Credential-named refs (`accounts.0.password`) render `***` — this column is read outside the run.
|
|
110
|
+
if (val !== undefined) pairs.push(`${v}: ${displayValue(maskCredentialValue(v, val))}`);
|
|
109
111
|
}
|
|
110
112
|
return pairs;
|
|
111
113
|
}
|
|
@@ -13,6 +13,7 @@ import * as ExcelJS from 'exceljs';
|
|
|
13
13
|
import { renderReportHeaderBand } from '../xlsx-report-builder';
|
|
14
14
|
import { variantState } from './build';
|
|
15
15
|
import { MatrixModel, MapCategory } from './types';
|
|
16
|
+
import { formatTestOutcome, resultTotals } from './result-totals';
|
|
16
17
|
import { GAP_FILL, SOFT_BLUE, dataCell, headerCell, draftBanner } from './xlsx-cells';
|
|
17
18
|
|
|
18
19
|
const CATEGORY_ORDER: MapCategory[] = ['normal', 'abnormal', 'security', 'nfr'];
|
|
@@ -151,7 +152,8 @@ export function addCoverageSheet(wb: ExcelJS.Workbook, model: MatrixModel, sunge
|
|
|
151
152
|
const m = model.manifest;
|
|
152
153
|
const manifestRows: Array<[string, string]> = [
|
|
153
154
|
['Unit', m.unit],
|
|
154
|
-
['
|
|
155
|
+
['Review state (design approval)', m.approvalState],
|
|
156
|
+
['Test outcome', formatTestOutcome(resultTotals(model.items))],
|
|
155
157
|
['Delivery items', String(m.itemCount)],
|
|
156
158
|
['Coverage variants', String(m.variantCount)],
|
|
157
159
|
['Source fingerprint', m.sourceFingerprint],
|