@sun-asterisk/sungen 3.2.29 → 3.2.31
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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.probeControl = probeControl;
|
|
4
|
+
exports.setCheckedState = setCheckedState;
|
|
5
|
+
exports.expectCheckedState = expectCheckedState;
|
|
6
|
+
/**
|
|
7
|
+
* Budget for EACH PHASE of a check/uncheck step — classification, the action, and the post-click
|
|
8
|
+
* verification each get their own (deliberately: see setCheckedState). So the worst case is a small
|
|
9
|
+
* multiple of this, not this. Raise it for a slow CI box with `SUNGEN_TOGGLE_TIMEOUT_MS`; without an
|
|
10
|
+
* escape hatch this would silently override a project that raised Playwright's own `actionTimeout`.
|
|
11
|
+
*/
|
|
12
|
+
const ENV_TIMEOUT = Number(process.env.SUNGEN_TOGGLE_TIMEOUT_MS);
|
|
13
|
+
// Guard the parse: `=0` means "no timeout" to Playwright (hang forever) and a negative is nonsense —
|
|
14
|
+
// neither should be inherited from a typo'd env var.
|
|
15
|
+
const TOGGLE_TIMEOUT_MS = Number.isFinite(ENV_TIMEOUT) && ENV_TIMEOUT > 0 ? ENV_TIMEOUT : 8000;
|
|
16
|
+
const POLL_INTERVAL_MS = 100;
|
|
17
|
+
/**
|
|
18
|
+
* Runs IN THE PAGE, so it must stay self-contained: Playwright serialises it and the browser has
|
|
19
|
+
* none of this module's scope.
|
|
20
|
+
*
|
|
21
|
+
* It also must contain NO inner function of its own — not even an arrow helper. A bundler with
|
|
22
|
+
* keepNames (esbuild, which is what Playwright itself transpiles specs with) rewrites every named
|
|
23
|
+
* inner function to `__name(fn, "…")`, that wrapper travels inside the serialised source, and the
|
|
24
|
+
* page throws `ReferenceError: __name is not defined` on EVERY check step. Straight-line code with
|
|
25
|
+
* the normalise repeated inline is the price of being serialisable; `tests/runtime/
|
|
26
|
+
* checked-state.run.ts` pins it so the next edit cannot quietly reintroduce a helper.
|
|
27
|
+
*
|
|
28
|
+
* Structurally typed rather than via the DOM lib names because this file is also typechecked
|
|
29
|
+
* inside the compiler build, whose tsconfig carries no DOM lib.
|
|
30
|
+
*/
|
|
31
|
+
function probeControl(node, declared) {
|
|
32
|
+
const el = node;
|
|
33
|
+
const tag = String(el.tagName ?? '').trim().toLowerCase(); // lowercase: XHTML serves it so
|
|
34
|
+
const type = String(el.type ?? '').trim().toLowerCase();
|
|
35
|
+
const role = String(el.getAttribute('role') ?? '').trim().toLowerCase();
|
|
36
|
+
const ariaCheckableRole = role === 'checkbox' || role === 'radio' || role === 'switch';
|
|
37
|
+
const buttonLike = tag === 'button' || role === 'button'
|
|
38
|
+
|| (tag === 'input' && (type === 'button' || type === 'submit' || type === 'reset'));
|
|
39
|
+
// A DECLARED signal wins over every ARIA reading below (issue #730). The author reached for this
|
|
40
|
+
// escape hatch precisely because the component publishes no usable ARIA, so second-guessing them
|
|
41
|
+
// from a stray role would put the step back on the path that could not work. Self OR descendant:
|
|
42
|
+
// a card picker swaps the class on the control itself, while a custom checkbox often mounts a
|
|
43
|
+
// checkmark <svg class="is-checked"> inside instead — both are the same declaration to the author.
|
|
44
|
+
// `classList.contains` is exact-token by definition; `[class~="…"]` is its selector equivalent,
|
|
45
|
+
// so neither needs the class escaped and neither can half-match ("is-checked-disabled").
|
|
46
|
+
if (declared && declared.class) {
|
|
47
|
+
const onSelf = el.classList != null && el.classList.contains(declared.class);
|
|
48
|
+
const onChild = !onSelf && el.querySelector('[class~="' + declared.class + '"]') != null;
|
|
49
|
+
return { source: 'class', on: onSelf || onChild, buttonLike };
|
|
50
|
+
}
|
|
51
|
+
// The same declaration published as a boolean attribute instead of a class. Self ONLY, unlike the
|
|
52
|
+
// class above: a class swap can legitimately live on a checkmark child, but `data-checked` on a
|
|
53
|
+
// descendant is a different control's state, and reading it would assert the wrong element.
|
|
54
|
+
if (declared && declared.attribute) {
|
|
55
|
+
const raw = el.getAttribute(declared.attribute);
|
|
56
|
+
return { source: 'attribute', on: String(raw ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
57
|
+
}
|
|
58
|
+
// A native input needs no ARIA attribute — Playwright reads `.checked` directly.
|
|
59
|
+
if (tag === 'input' && (type === 'checkbox' || type === 'radio'))
|
|
60
|
+
return { source: 'native', on: false, buttonLike };
|
|
61
|
+
// An ARIA checkable publishes state through `aria-checked`, which is what Playwright reads. Only
|
|
62
|
+
// hand it over when that attribute is actually there: `<button role="switch" aria-pressed="…">`
|
|
63
|
+
// is a common authoring slip, and routing it to .check() makes Playwright click, re-read the
|
|
64
|
+
// aria-checked that does not exist, and die on "did not change its state".
|
|
65
|
+
if (ariaCheckableRole && el.hasAttribute('aria-checked'))
|
|
66
|
+
return { source: 'native', on: false, buttonLike };
|
|
67
|
+
if (el.hasAttribute('aria-pressed')) {
|
|
68
|
+
return { source: 'aria-pressed', on: String(el.getAttribute('aria-pressed') ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
69
|
+
}
|
|
70
|
+
if (el.hasAttribute('aria-checked')) {
|
|
71
|
+
return { source: 'aria-checked', on: String(el.getAttribute('aria-checked') ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
72
|
+
}
|
|
73
|
+
// Role claims checkable but nothing is published — let Playwright raise its own, precise error.
|
|
74
|
+
if (ariaCheckableRole)
|
|
75
|
+
return { source: 'native', on: false, buttonLike };
|
|
76
|
+
return { source: 'none', on: false, buttonLike };
|
|
77
|
+
}
|
|
78
|
+
/** The declaration both helpers forward into the page. Undefined when nothing was declared, so a
|
|
79
|
+
* control with real ARIA keeps being read exactly as it was before any of this existed. */
|
|
80
|
+
const declaredSignal = (options) => (options?.checkedClass ? { class: options.checkedClass }
|
|
81
|
+
: options?.checkedAttribute ? { attribute: options.checkedAttribute }
|
|
82
|
+
: undefined);
|
|
83
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
84
|
+
/**
|
|
85
|
+
* Time left on a budget, floored at 250ms. Never 0 — to Playwright that means "no timeout", i.e.
|
|
86
|
+
* hang forever. And never a sliver either: a 1ms floor makes the last poll fail with
|
|
87
|
+
* `Timeout 1ms exceeded`, which masks the real, explanatory error this helper is about to throw.
|
|
88
|
+
* The floor lets a budget overrun by at most 250ms; the loops below terminate on the deadline.
|
|
89
|
+
*/
|
|
90
|
+
const left = (deadline) => Math.max(250, deadline - Date.now());
|
|
91
|
+
/** Read the live state. A REMOVED attribute means off: `aria-pressed={on || undefined}` is a common
|
|
92
|
+
* React/Vue idiom, so requiring a literal "false" would fail a control that is correctly off. */
|
|
93
|
+
async function readOn(target, attr, deadline) {
|
|
94
|
+
const raw = await target.getAttribute(attr, { timeout: left(deadline) });
|
|
95
|
+
return String(raw ?? '').trim().toLowerCase() === 'true';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Drive `target` to the requested on/off state, idempotently.
|
|
99
|
+
*
|
|
100
|
+
* @param target the control
|
|
101
|
+
* @param want `true` = checked/pressed, `false` = unchecked/unpressed
|
|
102
|
+
* @param label the `[Selector Ref]` from the feature file, used only in error messages
|
|
103
|
+
* @param options.checkedClass `checked:`/`selected:` from selectors.yaml — the class marking ON,
|
|
104
|
+
* for a control that publishes no ARIA at all (issue #730). Overrides every ARIA reading.
|
|
105
|
+
* @param options.checkedAttribute the same declaration published as a boolean attribute
|
|
106
|
+
* ('true'/'false') instead of a class. A selector declares one or the other, never both.
|
|
107
|
+
*/
|
|
108
|
+
async function setCheckedState(target, want, label, options) {
|
|
109
|
+
const timeout = options?.timeout ?? TOGGLE_TIMEOUT_MS;
|
|
110
|
+
const declared = declaredSignal(options);
|
|
111
|
+
// TWO budgets, not one shared deadline: a control that hydrates late would otherwise spend the
|
|
112
|
+
// whole budget being classified and leave the post-click check a single sample — which then
|
|
113
|
+
// reports "the click landed on the label" when the real problem was the clock.
|
|
114
|
+
const classifyDeadline = Date.now() + timeout;
|
|
115
|
+
// `locator.evaluate` waits only for ATTACHED. Playwright's own .check() re-resolved the locator on
|
|
116
|
+
// every actionability retry, so a control that gains its role/aria-* on hydration still worked —
|
|
117
|
+
// classifying once at first attach would turn a slow mount into a hard failure.
|
|
118
|
+
let probe = await target.evaluate(probeControl, declared, { timeout: left(classifyDeadline) });
|
|
119
|
+
while (probe.source === 'none' && classifyDeadline - Date.now() > POLL_INTERVAL_MS) {
|
|
120
|
+
await sleep(POLL_INTERVAL_MS);
|
|
121
|
+
probe = await target.evaluate(probeControl, declared, { timeout: left(classifyDeadline) });
|
|
122
|
+
}
|
|
123
|
+
if (probe.source === 'native') {
|
|
124
|
+
if (want)
|
|
125
|
+
await target.check({ timeout });
|
|
126
|
+
else
|
|
127
|
+
await target.uncheck({ timeout });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// A declared class: drive it the way the ARIA path below drives a toggle — click ONLY when the
|
|
131
|
+
// current state differs, then require the class to reach the requested state. Same idempotence,
|
|
132
|
+
// and the same refusal to report success on a blind click.
|
|
133
|
+
if (probe.source === 'class' || probe.source === 'attribute') {
|
|
134
|
+
if (probe.on === want)
|
|
135
|
+
return;
|
|
136
|
+
await target.click({ timeout });
|
|
137
|
+
const classStart = Date.now();
|
|
138
|
+
const classDeadline = classStart + timeout;
|
|
139
|
+
for (;;) {
|
|
140
|
+
const after = await target.evaluate(probeControl, declared, { timeout: left(classDeadline) });
|
|
141
|
+
if (after.on === want)
|
|
142
|
+
return;
|
|
143
|
+
if (classDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
144
|
+
throw new Error(`[${label}] did not become ${want ? 'checked' : 'unchecked'} within ${Date.now() - classStart}ms of `
|
|
145
|
+
+ `being clicked — the declared ${probe.source} "${declared?.class ?? declared?.attribute}" still reads `
|
|
146
|
+
+ `${want ? 'off' : 'on'}${probe.source === 'class' ? ' on the element and on its descendants' : ''}. `
|
|
147
|
+
+ `Either the click landed on the label rather than the control, or that is not the `
|
|
148
|
+
+ `${probe.source} this component swaps for its on/off state.`);
|
|
149
|
+
}
|
|
150
|
+
await sleep(POLL_INTERVAL_MS);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (probe.source === 'none') {
|
|
154
|
+
// Absence on a PRESSABLE control means off — a toggle written as `aria-pressed={on || undefined}`
|
|
155
|
+
// (React/Vue, and several component libraries a QA team does not own) drops the attribute
|
|
156
|
+
// entirely when unpressed. Treating that as an error made `uncheck` pass on the first run and
|
|
157
|
+
// fail on the second, which is precisely the rerun-dependent flake this helper exists to stop.
|
|
158
|
+
if (probe.buttonLike) {
|
|
159
|
+
if (!want) {
|
|
160
|
+
// Nothing to do — but say so, because the alternative is a green step that did nothing at
|
|
161
|
+
// all (a mis-resolved selector pointing at a plain button looks exactly like this).
|
|
162
|
+
console.warn(`[sungen] uncheck [${label}]: the control publishes no aria-pressed/aria-checked, so it was `
|
|
163
|
+
+ `read as already unpressed and NOT clicked. If it should have been a toggle, expose `
|
|
164
|
+
+ `aria-pressed in both states; if this is not a toggle at all, the step is a no-op.`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Not a blind click: press once, then REQUIRE the control to publish the requested state.
|
|
168
|
+
// A plain button publishes nothing afterwards either, so it still fails — just one click later.
|
|
169
|
+
await target.click({ timeout });
|
|
170
|
+
const pressedStart = Date.now();
|
|
171
|
+
const pressedDeadline = pressedStart + timeout;
|
|
172
|
+
for (;;) {
|
|
173
|
+
const after = await target.evaluate(probeControl, undefined, { timeout: left(pressedDeadline) });
|
|
174
|
+
if (after.source !== 'none' && after.on === want)
|
|
175
|
+
return;
|
|
176
|
+
if (pressedDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
177
|
+
throw new Error(`Cannot check [${label}] — it published no aria-pressed/aria-checked before the click and `
|
|
178
|
+
+ `still none ${Date.now() - pressedStart}ms after it, so it is a plain button rather than a `
|
|
179
|
+
+ `toggle. Give the control a real ARIA toggle state, or use "toggles [${label}]" if an `
|
|
180
|
+
+ `unconditional flip is what you meant.`);
|
|
181
|
+
}
|
|
182
|
+
await sleep(POLL_INTERVAL_MS);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Not pressable and publishes nothing — there is no reading of this that makes the step meaningful.
|
|
186
|
+
throw new Error(`Cannot ${want ? 'check' : 'uncheck'} [${label}] — it is not a checkbox/radio/switch, is not a `
|
|
187
|
+
+ `button, and publishes no aria-pressed/aria-checked, so it has no on/off state to set. Point the `
|
|
188
|
+
+ `selector at the control itself, or give it a real ARIA toggle role.`);
|
|
189
|
+
}
|
|
190
|
+
const attr = probe.source;
|
|
191
|
+
// Already in the requested state → nothing to do. This is the idempotence `check` promises.
|
|
192
|
+
if (probe.on === want)
|
|
193
|
+
return;
|
|
194
|
+
await target.click({ timeout });
|
|
195
|
+
// Fresh budget: this measures the CONTROL's reaction, not time already spent finding it.
|
|
196
|
+
const verifyStart = Date.now();
|
|
197
|
+
const verifyDeadline = verifyStart + timeout;
|
|
198
|
+
for (;;) {
|
|
199
|
+
if (await readOn(target, attr, verifyDeadline) === want)
|
|
200
|
+
return;
|
|
201
|
+
if (verifyDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
202
|
+
throw new Error(`[${label}] did not become ${want ? 'pressed' : 'unpressed'} within ${Date.now() - verifyStart}ms of being `
|
|
203
|
+
+ `clicked — ${attr} still reads ${want ? 'off' : 'on'}. Either the click landed on the label rather than `
|
|
204
|
+
+ `the control, or the handler did not change the state.`);
|
|
205
|
+
}
|
|
206
|
+
await sleep(POLL_INTERVAL_MS);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Assert the on/off state of the same control family — the read side of #675.
|
|
211
|
+
*
|
|
212
|
+
* `expect(locator).toBeChecked()` throws the identical "Not a checkbox or radio button" on a
|
|
213
|
+
* `<button aria-pressed>`, so driving a toggle worked while asserting it did not. Polls like
|
|
214
|
+
* Playwright's own web-first assertions rather than sampling once, and reads state exactly the way
|
|
215
|
+
* setCheckedState does, so an action and the assertion after it can never disagree about what the
|
|
216
|
+
* control says. Uses `locator.isChecked()` (not `expect`) for the native family, which keeps this
|
|
217
|
+
* file free of any test-runner dependency.
|
|
218
|
+
*/
|
|
219
|
+
async function expectCheckedState(target, expected, label, options) {
|
|
220
|
+
const timeout = options?.timeout ?? TOGGLE_TIMEOUT_MS;
|
|
221
|
+
// Same signal the action side drives (issue #730) — passing it here is what keeps the invariant
|
|
222
|
+
// above true for a class-only control too: `source: 'class'` falls into the generic `probe.on`
|
|
223
|
+
// branch below, so check-then-assert cannot disagree.
|
|
224
|
+
const declared = declaredSignal(options);
|
|
225
|
+
const start = Date.now();
|
|
226
|
+
const deadline = start + timeout;
|
|
227
|
+
for (;;) {
|
|
228
|
+
const probe = await target.evaluate(probeControl, declared, { timeout: left(deadline) });
|
|
229
|
+
let actual = null;
|
|
230
|
+
if (probe.source === 'native')
|
|
231
|
+
actual = await target.isChecked({ timeout: left(deadline) });
|
|
232
|
+
else if (probe.source !== 'none')
|
|
233
|
+
actual = probe.on;
|
|
234
|
+
else if (probe.buttonLike) {
|
|
235
|
+
// Absence on a pressable control reads as off, mirroring setCheckedState. But a plain button
|
|
236
|
+
// and a correctly-off toggle are indistinguishable HERE, and this is an ASSERTION: returning
|
|
237
|
+
// green having verified nothing is worse than the loud "Not a checkbox or radio button" it
|
|
238
|
+
// replaced. So say so, exactly as the action side does for the same reading.
|
|
239
|
+
if (!expected) {
|
|
240
|
+
console.warn(`[sungen] see [${label}] is unchecked: the control publishes no aria-pressed/aria-checked, `
|
|
241
|
+
+ `so it was read as unpressed WITHOUT verifying anything. If it should be a toggle, expose `
|
|
242
|
+
+ `aria-pressed in both states; if it is not a toggle, this assertion proves nothing.`);
|
|
243
|
+
}
|
|
244
|
+
actual = false;
|
|
245
|
+
}
|
|
246
|
+
if (actual === expected)
|
|
247
|
+
return;
|
|
248
|
+
if (deadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
249
|
+
if (actual === null) {
|
|
250
|
+
throw new Error(`Cannot assert [${label}] is ${expected ? 'checked' : 'unchecked'} — it is not a `
|
|
251
|
+
+ `checkbox/radio/switch, is not a button, and publishes no aria-pressed/aria-checked, so it `
|
|
252
|
+
+ `reports no state to assert. Point the selector at the control itself, or give it a real `
|
|
253
|
+
+ `ARIA toggle role.`);
|
|
254
|
+
}
|
|
255
|
+
throw new Error(`Expected [${label}] to be ${expected ? 'checked' : 'unchecked'}, but it is `
|
|
256
|
+
+ `${actual ? 'checked' : 'unchecked'} after ${Date.now() - start}ms.`);
|
|
257
|
+
}
|
|
258
|
+
await sleep(POLL_INTERVAL_MS);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=specs-checked-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs-checked-state.js","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-checked-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Sungen checked-state helper — auto-generated into specs/checked-state.ts. DO NOT EDIT.
|
|
4
|
+
*
|
|
5
|
+
* Playwright's `.check()` / `.uncheck()` accept ONLY native checkbox/radio inputs and
|
|
6
|
+
* `role=checkbox|radio|switch`. Anything else throws "Not a checkbox or radio button" (#675).
|
|
7
|
+
* A toggle BUTTON — `<button aria-pressed>`, the correct ARIA pattern for a pressed/unpressed
|
|
8
|
+
* control — is none of those, so a bare `.check()` could never drive one.
|
|
9
|
+
*
|
|
10
|
+
* Rewriting the Gherkin to `click` is NOT an equivalent workaround, and that is the whole reason
|
|
11
|
+
* this helper exists: `click` flips UNCONDITIONALLY, so a scenario that re-runs, or starts from an
|
|
12
|
+
* unknown state, drives the toggle the WRONG way. Idempotence — end up in the requested state
|
|
13
|
+
* regardless of where you started — is the entire meaning of `check` / `uncheck`.
|
|
14
|
+
*
|
|
15
|
+
* The mechanism is chosen from the ELEMENT at run time, not from the selector's declared role,
|
|
16
|
+
* because the resolved selector is very often a testid/CSS locator carrying no role at all — so
|
|
17
|
+
* codegen cannot know what it will hit.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately depends on NOTHING but the `Locator` TYPE: no `expect`, no test-runner context. That
|
|
20
|
+
* keeps the branching testable against a fake locator without a browser (tests/runtime/
|
|
21
|
+
* checked-state.run.ts) — this file runs inside every user's suite, so its logic is worth pinning.
|
|
22
|
+
*/
|
|
23
|
+
import type { Locator } from '@playwright/test';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Budget for EACH PHASE of a check/uncheck step — classification, the action, and the post-click
|
|
27
|
+
* verification each get their own (deliberately: see setCheckedState). So the worst case is a small
|
|
28
|
+
* multiple of this, not this. Raise it for a slow CI box with `SUNGEN_TOGGLE_TIMEOUT_MS`; without an
|
|
29
|
+
* escape hatch this would silently override a project that raised Playwright's own `actionTimeout`.
|
|
30
|
+
*/
|
|
31
|
+
const ENV_TIMEOUT = Number(process.env.SUNGEN_TOGGLE_TIMEOUT_MS);
|
|
32
|
+
// Guard the parse: `=0` means "no timeout" to Playwright (hang forever) and a negative is nonsense —
|
|
33
|
+
// neither should be inherited from a typo'd env var.
|
|
34
|
+
const TOGGLE_TIMEOUT_MS = Number.isFinite(ENV_TIMEOUT) && ENV_TIMEOUT > 0 ? ENV_TIMEOUT : 8_000;
|
|
35
|
+
const POLL_INTERVAL_MS = 100;
|
|
36
|
+
|
|
37
|
+
/** A signal DECLARED in selectors.yaml: the class token, or the boolean attribute, marking ON. */
|
|
38
|
+
export interface DeclaredSignal { class?: string; attribute?: string }
|
|
39
|
+
|
|
40
|
+
/** Where the control publishes its on/off state — decided in the page, from the real element. */
|
|
41
|
+
export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'class' | 'attribute' | 'none';
|
|
42
|
+
export interface ControlProbe {
|
|
43
|
+
source: StateSource;
|
|
44
|
+
/** Current state, already normalised. Meaningless when `source` is 'native' (Playwright reads it). */
|
|
45
|
+
on: boolean;
|
|
46
|
+
/** Button-like: a control a human would press. Decides how a MISSING state is read — see below. */
|
|
47
|
+
buttonLike: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Runs IN THE PAGE, so it must stay self-contained: Playwright serialises it and the browser has
|
|
52
|
+
* none of this module's scope.
|
|
53
|
+
*
|
|
54
|
+
* It also must contain NO inner function of its own — not even an arrow helper. A bundler with
|
|
55
|
+
* keepNames (esbuild, which is what Playwright itself transpiles specs with) rewrites every named
|
|
56
|
+
* inner function to `__name(fn, "…")`, that wrapper travels inside the serialised source, and the
|
|
57
|
+
* page throws `ReferenceError: __name is not defined` on EVERY check step. Straight-line code with
|
|
58
|
+
* the normalise repeated inline is the price of being serialisable; `tests/runtime/
|
|
59
|
+
* checked-state.run.ts` pins it so the next edit cannot quietly reintroduce a helper.
|
|
60
|
+
*
|
|
61
|
+
* Structurally typed rather than via the DOM lib names because this file is also typechecked
|
|
62
|
+
* inside the compiler build, whose tsconfig carries no DOM lib.
|
|
63
|
+
*/
|
|
64
|
+
export function probeControl(node: unknown, declared?: DeclaredSignal): ControlProbe {
|
|
65
|
+
const el = node as {
|
|
66
|
+
tagName: string; type?: string;
|
|
67
|
+
getAttribute(n: string): string | null; hasAttribute(n: string): boolean;
|
|
68
|
+
classList: { contains(c: string): boolean };
|
|
69
|
+
querySelector(s: string): unknown;
|
|
70
|
+
};
|
|
71
|
+
const tag = String(el.tagName ?? '').trim().toLowerCase(); // lowercase: XHTML serves it so
|
|
72
|
+
const type = String(el.type ?? '').trim().toLowerCase();
|
|
73
|
+
const role = String(el.getAttribute('role') ?? '').trim().toLowerCase();
|
|
74
|
+
const ariaCheckableRole = role === 'checkbox' || role === 'radio' || role === 'switch';
|
|
75
|
+
const buttonLike = tag === 'button' || role === 'button'
|
|
76
|
+
|| (tag === 'input' && (type === 'button' || type === 'submit' || type === 'reset'));
|
|
77
|
+
|
|
78
|
+
// A DECLARED signal wins over every ARIA reading below (issue #730). The author reached for this
|
|
79
|
+
// escape hatch precisely because the component publishes no usable ARIA, so second-guessing them
|
|
80
|
+
// from a stray role would put the step back on the path that could not work. Self OR descendant:
|
|
81
|
+
// a card picker swaps the class on the control itself, while a custom checkbox often mounts a
|
|
82
|
+
// checkmark <svg class="is-checked"> inside instead — both are the same declaration to the author.
|
|
83
|
+
// `classList.contains` is exact-token by definition; `[class~="…"]` is its selector equivalent,
|
|
84
|
+
// so neither needs the class escaped and neither can half-match ("is-checked-disabled").
|
|
85
|
+
if (declared && declared.class) {
|
|
86
|
+
const onSelf = el.classList != null && el.classList.contains(declared.class);
|
|
87
|
+
const onChild = !onSelf && el.querySelector('[class~="' + declared.class + '"]') != null;
|
|
88
|
+
return { source: 'class', on: onSelf || onChild, buttonLike };
|
|
89
|
+
}
|
|
90
|
+
// The same declaration published as a boolean attribute instead of a class. Self ONLY, unlike the
|
|
91
|
+
// class above: a class swap can legitimately live on a checkmark child, but `data-checked` on a
|
|
92
|
+
// descendant is a different control's state, and reading it would assert the wrong element.
|
|
93
|
+
if (declared && declared.attribute) {
|
|
94
|
+
const raw = el.getAttribute(declared.attribute);
|
|
95
|
+
return { source: 'attribute', on: String(raw ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A native input needs no ARIA attribute — Playwright reads `.checked` directly.
|
|
99
|
+
if (tag === 'input' && (type === 'checkbox' || type === 'radio')) return { source: 'native', on: false, buttonLike };
|
|
100
|
+
// An ARIA checkable publishes state through `aria-checked`, which is what Playwright reads. Only
|
|
101
|
+
// hand it over when that attribute is actually there: `<button role="switch" aria-pressed="…">`
|
|
102
|
+
// is a common authoring slip, and routing it to .check() makes Playwright click, re-read the
|
|
103
|
+
// aria-checked that does not exist, and die on "did not change its state".
|
|
104
|
+
if (ariaCheckableRole && el.hasAttribute('aria-checked')) return { source: 'native', on: false, buttonLike };
|
|
105
|
+
if (el.hasAttribute('aria-pressed')) {
|
|
106
|
+
return { source: 'aria-pressed', on: String(el.getAttribute('aria-pressed') ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
107
|
+
}
|
|
108
|
+
if (el.hasAttribute('aria-checked')) {
|
|
109
|
+
return { source: 'aria-checked', on: String(el.getAttribute('aria-checked') ?? '').trim().toLowerCase() === 'true', buttonLike };
|
|
110
|
+
}
|
|
111
|
+
// Role claims checkable but nothing is published — let Playwright raise its own, precise error.
|
|
112
|
+
if (ariaCheckableRole) return { source: 'native', on: false, buttonLike };
|
|
113
|
+
return { source: 'none', on: false, buttonLike };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The declaration both helpers forward into the page. Undefined when nothing was declared, so a
|
|
117
|
+
* control with real ARIA keeps being read exactly as it was before any of this existed. */
|
|
118
|
+
const declaredSignal = (
|
|
119
|
+
options?: { checkedClass?: string; checkedAttribute?: string },
|
|
120
|
+
): DeclaredSignal | undefined => (
|
|
121
|
+
options?.checkedClass ? { class: options.checkedClass }
|
|
122
|
+
: options?.checkedAttribute ? { attribute: options.checkedAttribute }
|
|
123
|
+
: undefined
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
|
|
127
|
+
/**
|
|
128
|
+
* Time left on a budget, floored at 250ms. Never 0 — to Playwright that means "no timeout", i.e.
|
|
129
|
+
* hang forever. And never a sliver either: a 1ms floor makes the last poll fail with
|
|
130
|
+
* `Timeout 1ms exceeded`, which masks the real, explanatory error this helper is about to throw.
|
|
131
|
+
* The floor lets a budget overrun by at most 250ms; the loops below terminate on the deadline.
|
|
132
|
+
*/
|
|
133
|
+
const left = (deadline: number): number => Math.max(250, deadline - Date.now());
|
|
134
|
+
|
|
135
|
+
/** Read the live state. A REMOVED attribute means off: `aria-pressed={on || undefined}` is a common
|
|
136
|
+
* React/Vue idiom, so requiring a literal "false" would fail a control that is correctly off. */
|
|
137
|
+
async function readOn(target: Locator, attr: 'aria-pressed' | 'aria-checked', deadline: number): Promise<boolean> {
|
|
138
|
+
const raw = await target.getAttribute(attr, { timeout: left(deadline) });
|
|
139
|
+
return String(raw ?? '').trim().toLowerCase() === 'true';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Drive `target` to the requested on/off state, idempotently.
|
|
144
|
+
*
|
|
145
|
+
* @param target the control
|
|
146
|
+
* @param want `true` = checked/pressed, `false` = unchecked/unpressed
|
|
147
|
+
* @param label the `[Selector Ref]` from the feature file, used only in error messages
|
|
148
|
+
* @param options.checkedClass `checked:`/`selected:` from selectors.yaml — the class marking ON,
|
|
149
|
+
* for a control that publishes no ARIA at all (issue #730). Overrides every ARIA reading.
|
|
150
|
+
* @param options.checkedAttribute the same declaration published as a boolean attribute
|
|
151
|
+
* ('true'/'false') instead of a class. A selector declares one or the other, never both.
|
|
152
|
+
*/
|
|
153
|
+
export async function setCheckedState(
|
|
154
|
+
target: Locator, want: boolean, label: string,
|
|
155
|
+
options?: { timeout?: number; checkedClass?: string; checkedAttribute?: string },
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const timeout = options?.timeout ?? TOGGLE_TIMEOUT_MS;
|
|
158
|
+
const declared = declaredSignal(options);
|
|
159
|
+
// TWO budgets, not one shared deadline: a control that hydrates late would otherwise spend the
|
|
160
|
+
// whole budget being classified and leave the post-click check a single sample — which then
|
|
161
|
+
// reports "the click landed on the label" when the real problem was the clock.
|
|
162
|
+
const classifyDeadline = Date.now() + timeout;
|
|
163
|
+
|
|
164
|
+
// `locator.evaluate` waits only for ATTACHED. Playwright's own .check() re-resolved the locator on
|
|
165
|
+
// every actionability retry, so a control that gains its role/aria-* on hydration still worked —
|
|
166
|
+
// classifying once at first attach would turn a slow mount into a hard failure.
|
|
167
|
+
let probe = await target.evaluate(probeControl, declared, { timeout: left(classifyDeadline) });
|
|
168
|
+
while (probe.source === 'none' && classifyDeadline - Date.now() > POLL_INTERVAL_MS) {
|
|
169
|
+
await sleep(POLL_INTERVAL_MS);
|
|
170
|
+
probe = await target.evaluate(probeControl, declared, { timeout: left(classifyDeadline) });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (probe.source === 'native') {
|
|
174
|
+
if (want) await target.check({ timeout });
|
|
175
|
+
else await target.uncheck({ timeout });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// A declared class: drive it the way the ARIA path below drives a toggle — click ONLY when the
|
|
180
|
+
// current state differs, then require the class to reach the requested state. Same idempotence,
|
|
181
|
+
// and the same refusal to report success on a blind click.
|
|
182
|
+
if (probe.source === 'class' || probe.source === 'attribute') {
|
|
183
|
+
if (probe.on === want) return;
|
|
184
|
+
await target.click({ timeout });
|
|
185
|
+
const classStart = Date.now();
|
|
186
|
+
const classDeadline = classStart + timeout;
|
|
187
|
+
for (;;) {
|
|
188
|
+
const after = await target.evaluate(probeControl, declared, { timeout: left(classDeadline) });
|
|
189
|
+
if (after.on === want) return;
|
|
190
|
+
if (classDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`[${label}] did not become ${want ? 'checked' : 'unchecked'} within ${Date.now() - classStart}ms of `
|
|
193
|
+
+ `being clicked — the declared ${probe.source} "${declared?.class ?? declared?.attribute}" still reads `
|
|
194
|
+
+ `${want ? 'off' : 'on'}${probe.source === 'class' ? ' on the element and on its descendants' : ''}. `
|
|
195
|
+
+ `Either the click landed on the label rather than the control, or that is not the `
|
|
196
|
+
+ `${probe.source} this component swaps for its on/off state.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
await sleep(POLL_INTERVAL_MS);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (probe.source === 'none') {
|
|
204
|
+
// Absence on a PRESSABLE control means off — a toggle written as `aria-pressed={on || undefined}`
|
|
205
|
+
// (React/Vue, and several component libraries a QA team does not own) drops the attribute
|
|
206
|
+
// entirely when unpressed. Treating that as an error made `uncheck` pass on the first run and
|
|
207
|
+
// fail on the second, which is precisely the rerun-dependent flake this helper exists to stop.
|
|
208
|
+
if (probe.buttonLike) {
|
|
209
|
+
if (!want) {
|
|
210
|
+
// Nothing to do — but say so, because the alternative is a green step that did nothing at
|
|
211
|
+
// all (a mis-resolved selector pointing at a plain button looks exactly like this).
|
|
212
|
+
console.warn(
|
|
213
|
+
`[sungen] uncheck [${label}]: the control publishes no aria-pressed/aria-checked, so it was `
|
|
214
|
+
+ `read as already unpressed and NOT clicked. If it should have been a toggle, expose `
|
|
215
|
+
+ `aria-pressed in both states; if this is not a toggle at all, the step is a no-op.`,
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Not a blind click: press once, then REQUIRE the control to publish the requested state.
|
|
220
|
+
// A plain button publishes nothing afterwards either, so it still fails — just one click later.
|
|
221
|
+
await target.click({ timeout });
|
|
222
|
+
const pressedStart = Date.now();
|
|
223
|
+
const pressedDeadline = pressedStart + timeout;
|
|
224
|
+
for (;;) {
|
|
225
|
+
const after = await target.evaluate(probeControl, undefined, { timeout: left(pressedDeadline) });
|
|
226
|
+
if (after.source !== 'none' && after.on === want) return;
|
|
227
|
+
if (pressedDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Cannot check [${label}] — it published no aria-pressed/aria-checked before the click and `
|
|
230
|
+
+ `still none ${Date.now() - pressedStart}ms after it, so it is a plain button rather than a `
|
|
231
|
+
+ `toggle. Give the control a real ARIA toggle state, or use "toggles [${label}]" if an `
|
|
232
|
+
+ `unconditional flip is what you meant.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
await sleep(POLL_INTERVAL_MS);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Not pressable and publishes nothing — there is no reading of this that makes the step meaningful.
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Cannot ${want ? 'check' : 'uncheck'} [${label}] — it is not a checkbox/radio/switch, is not a `
|
|
241
|
+
+ `button, and publishes no aria-pressed/aria-checked, so it has no on/off state to set. Point the `
|
|
242
|
+
+ `selector at the control itself, or give it a real ARIA toggle role.`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const attr = probe.source;
|
|
247
|
+
// Already in the requested state → nothing to do. This is the idempotence `check` promises.
|
|
248
|
+
if (probe.on === want) return;
|
|
249
|
+
await target.click({ timeout });
|
|
250
|
+
|
|
251
|
+
// Fresh budget: this measures the CONTROL's reaction, not time already spent finding it.
|
|
252
|
+
const verifyStart = Date.now();
|
|
253
|
+
const verifyDeadline = verifyStart + timeout;
|
|
254
|
+
for (;;) {
|
|
255
|
+
if (await readOn(target, attr, verifyDeadline) === want) return;
|
|
256
|
+
if (verifyDeadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`[${label}] did not become ${want ? 'pressed' : 'unpressed'} within ${Date.now() - verifyStart}ms of being `
|
|
259
|
+
+ `clicked — ${attr} still reads ${want ? 'off' : 'on'}. Either the click landed on the label rather than `
|
|
260
|
+
+ `the control, or the handler did not change the state.`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
await sleep(POLL_INTERVAL_MS);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Assert the on/off state of the same control family — the read side of #675.
|
|
269
|
+
*
|
|
270
|
+
* `expect(locator).toBeChecked()` throws the identical "Not a checkbox or radio button" on a
|
|
271
|
+
* `<button aria-pressed>`, so driving a toggle worked while asserting it did not. Polls like
|
|
272
|
+
* Playwright's own web-first assertions rather than sampling once, and reads state exactly the way
|
|
273
|
+
* setCheckedState does, so an action and the assertion after it can never disagree about what the
|
|
274
|
+
* control says. Uses `locator.isChecked()` (not `expect`) for the native family, which keeps this
|
|
275
|
+
* file free of any test-runner dependency.
|
|
276
|
+
*/
|
|
277
|
+
export async function expectCheckedState(
|
|
278
|
+
target: Locator, expected: boolean, label: string,
|
|
279
|
+
options?: { timeout?: number; checkedClass?: string; checkedAttribute?: string },
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
const timeout = options?.timeout ?? TOGGLE_TIMEOUT_MS;
|
|
282
|
+
// Same signal the action side drives (issue #730) — passing it here is what keeps the invariant
|
|
283
|
+
// above true for a class-only control too: `source: 'class'` falls into the generic `probe.on`
|
|
284
|
+
// branch below, so check-then-assert cannot disagree.
|
|
285
|
+
const declared = declaredSignal(options);
|
|
286
|
+
const start = Date.now();
|
|
287
|
+
const deadline = start + timeout;
|
|
288
|
+
for (;;) {
|
|
289
|
+
const probe = await target.evaluate(probeControl, declared, { timeout: left(deadline) });
|
|
290
|
+
let actual: boolean | null = null;
|
|
291
|
+
if (probe.source === 'native') actual = await target.isChecked({ timeout: left(deadline) });
|
|
292
|
+
else if (probe.source !== 'none') actual = probe.on;
|
|
293
|
+
else if (probe.buttonLike) {
|
|
294
|
+
// Absence on a pressable control reads as off, mirroring setCheckedState. But a plain button
|
|
295
|
+
// and a correctly-off toggle are indistinguishable HERE, and this is an ASSERTION: returning
|
|
296
|
+
// green having verified nothing is worse than the loud "Not a checkbox or radio button" it
|
|
297
|
+
// replaced. So say so, exactly as the action side does for the same reading.
|
|
298
|
+
if (!expected) {
|
|
299
|
+
console.warn(
|
|
300
|
+
`[sungen] see [${label}] is unchecked: the control publishes no aria-pressed/aria-checked, `
|
|
301
|
+
+ `so it was read as unpressed WITHOUT verifying anything. If it should be a toggle, expose `
|
|
302
|
+
+ `aria-pressed in both states; if it is not a toggle, this assertion proves nothing.`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
actual = false;
|
|
306
|
+
}
|
|
307
|
+
if (actual === expected) return;
|
|
308
|
+
if (deadline - Date.now() <= POLL_INTERVAL_MS) {
|
|
309
|
+
if (actual === null) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Cannot assert [${label}] is ${expected ? 'checked' : 'unchecked'} — it is not a `
|
|
312
|
+
+ `checkbox/radio/switch, is not a button, and publishes no aria-pressed/aria-checked, so it `
|
|
313
|
+
+ `reports no state to assert. Point the selector at the control itself, or give it a real `
|
|
314
|
+
+ `ARIA toggle role.`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Expected [${label}] to be ${expected ? 'checked' : 'unchecked'}, but it is `
|
|
319
|
+
+ `${actual ? 'checked' : 'unchecked'} after ${Date.now() - start}ms.`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
await sleep(POLL_INTERVAL_MS);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright reporter that rewrites the operator's machine paths out of the JSON result file.
|
|
3
|
+
*
|
|
4
|
+
* The JSON reporter records absolute paths — `config.configFile`, `rootDir`, `projects[]`, every
|
|
5
|
+
* `error.location.file`, attachment paths, and the paths quoted inside stack traces and messages.
|
|
6
|
+
* Teams commit that file next to the spec (sungen delivery reads it), so a username and a machine
|
|
7
|
+
* layout travel into the repository and the deliverable. Nothing downstream needs them absolute.
|
|
8
|
+
*
|
|
9
|
+
* Runs in `onExit()` — after every reporter, the JSON one included, has finished writing — reads the
|
|
10
|
+
* JSON reporter's `outputFile`, rewrites it in place, and stays silent otherwise. Paths under the
|
|
11
|
+
* project root (the directory of `playwright.config.ts`) become project-relative; outside it a path that
|
|
12
|
+
* names a machine (a home directory, a drive) keeps only its basename while any other absolute string
|
|
13
|
+
* — a URL route — is left alone; home directories quoted inside text become `~/`. `sungen results
|
|
14
|
+
* sanitize` applies the same rules to files that already exist.
|
|
15
|
+
*
|
|
16
|
+
* Wired in playwright.config.ts: reporter: [['html'], ['json', {…}], ['./specs/generated/report-path-sanitize-reporter.ts']].
|
|
17
|
+
* Regenerated by `sungen generate` / `sungen update` — DO NOT EDIT.
|
|
18
|
+
*/
|
|
19
|
+
import type { FullConfig, Reporter } from '@playwright/test/reporter';
|
|
20
|
+
declare class ReportPathSanitizeReporter implements Reporter {
|
|
21
|
+
private config?;
|
|
22
|
+
onBegin(config: FullConfig): void;
|
|
23
|
+
printsToStdio(): boolean;
|
|
24
|
+
onExit(): Promise<void>;
|
|
25
|
+
private configDir;
|
|
26
|
+
/** The JSON reporter's output file: its `outputFile` option, else the env var the config reads. */
|
|
27
|
+
private jsonOutputFile;
|
|
28
|
+
}
|
|
29
|
+
export default ReportPathSanitizeReporter;
|
|
30
|
+
export declare function sanitizeReportPaths<T>(doc: T, opts: {
|
|
31
|
+
root: string;
|
|
32
|
+
}): {
|
|
33
|
+
doc: T;
|
|
34
|
+
rewritten: number;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=specs-report-path-sanitize-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specs-report-path-sanitize-reporter.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/templates/specs-report-path-sanitize-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAItE,cAAM,0BAA2B,YAAW,QAAQ;IAClD,OAAO,CAAC,MAAM,CAAC,CAAa;IAE5B,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIjC,aAAa,IAAI,OAAO;IAIlB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB7B,OAAO,CAAC,SAAS;IAIjB,mGAAmG;IACnG,OAAO,CAAC,cAAc;CAWvB;AAED,eAAe,0BAA0B,CAAC;AAW1C,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmBpG"}
|