@sun-asterisk/sungen 1.0.24 → 2.0.1
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/README.md +198 -74
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +5 -3
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +110 -35
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +5 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/frame-exit-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/scroll-action.hbs +1 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +3 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -0
- 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/partials/locator-base.hbs +11 -2
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +11 -2
- package/dist/generators/test-generator/code-generator.d.ts +0 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +10 -47
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/assertion-patterns.js +2 -0
- package/dist/generators/test-generator/patterns/assertion-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +4 -1
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +17 -5
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js +1 -1
- package/dist/generators/test-generator/patterns/interaction-patterns.js.map +1 -1
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/keyboard-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/keyboard-patterns.js +47 -0
- package/dist/generators/test-generator/patterns/keyboard-patterns.js.map +1 -0
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/scope-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/scope-patterns.js +36 -0
- package/dist/generators/test-generator/patterns/scope-patterns.js.map +1 -0
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/scroll-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/scroll-patterns.js +25 -0
- package/dist/generators/test-generator/patterns/scroll-patterns.js.map +1 -0
- package/dist/generators/test-generator/patterns/table-patterns.d.ts +7 -0
- package/dist/generators/test-generator/patterns/table-patterns.d.ts.map +1 -0
- package/dist/generators/test-generator/patterns/table-patterns.js +192 -0
- package/dist/generators/test-generator/patterns/table-patterns.js.map +1 -0
- package/dist/generators/test-generator/step-mapper.d.ts +5 -3
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +38 -27
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +4 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/types.d.ts +7 -24
- package/dist/generators/test-generator/types.d.ts.map +1 -1
- package/dist/generators/test-generator/types.js +2 -101
- package/dist/generators/test-generator/types.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +14 -0
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +37 -11
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +12 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +72 -160
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts +1 -32
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +55 -216
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-rules.md +189 -0
- package/dist/orchestrator/templates/gitignore +16 -0
- package/dist/orchestrator/templates/playwright.config.d.ts +10 -0
- package/dist/orchestrator/templates/playwright.config.d.ts.map +1 -0
- package/dist/orchestrator/templates/playwright.config.js +77 -0
- package/dist/orchestrator/templates/playwright.config.js.map +1 -0
- package/dist/orchestrator/templates/playwright.config.ts +80 -0
- package/dist/orchestrator/templates/readme.md +197 -0
- package/dist/utils/selector-types.d.ts +1 -1
- package/dist/utils/selector-types.d.ts.map +1 -1
- package/dist/utils/selector-types.js +3 -0
- package/dist/utils/selector-types.js.map +1 -1
- package/docs/gherkin standards/gherkin-core-standard.md +377 -0
- package/docs/gherkin standards/gherkin-core-standard.vi.md +303 -0
- package/docs/gherkin-dictionary.md +1071 -0
- package/docs/makeauth.md +225 -0
- package/package.json +4 -3
- package/src/cli/commands/add.ts +5 -3
- package/src/cli/commands/generate.ts +90 -38
- package/src/cli/index.ts +5 -16
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/frame-enter-action.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/frame-exit-action.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/keyboard-global-action.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/scroll-action.hbs +1 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-filter.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-cell-by-index.hbs +3 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-column-exists.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-empty.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-count.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-exists.hbs +2 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/table-row-not-exists.hbs +2 -0
- 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/partials/locator-base.hbs +11 -2
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +11 -2
- package/src/generators/test-generator/code-generator.ts +11 -59
- package/src/generators/test-generator/patterns/assertion-patterns.ts +2 -0
- package/src/generators/test-generator/patterns/index.ts +12 -3
- package/src/generators/test-generator/patterns/interaction-patterns.ts +1 -1
- package/src/generators/test-generator/patterns/keyboard-patterns.ts +51 -0
- package/src/generators/test-generator/patterns/scope-patterns.ts +40 -0
- package/src/generators/test-generator/patterns/scroll-patterns.ts +27 -0
- package/src/generators/test-generator/patterns/table-patterns.ts +232 -0
- package/src/generators/test-generator/step-mapper.ts +42 -27
- package/src/generators/test-generator/template-engine.ts +3 -1
- package/src/generators/test-generator/types.ts +7 -112
- package/src/generators/test-generator/utils/selector-resolver.ts +70 -25
- package/src/orchestrator/project-initializer.ts +75 -160
- package/src/orchestrator/screen-manager.ts +61 -233
- package/src/orchestrator/templates/ai-rules.md +189 -0
- package/src/orchestrator/templates/gitignore +16 -0
- package/src/orchestrator/templates/playwright.config.ts +80 -0
- package/src/orchestrator/templates/readme.md +197 -0
- package/src/utils/selector-types.ts +3 -0
- package/dist/cli/commands/cache-clear.d.ts +0 -3
- package/dist/cli/commands/cache-clear.d.ts.map +0 -1
- package/dist/cli/commands/cache-clear.js +0 -24
- package/dist/cli/commands/cache-clear.js.map +0 -1
- package/dist/cli/commands/full.d.ts +0 -3
- package/dist/cli/commands/full.d.ts.map +0 -1
- package/dist/cli/commands/full.js +0 -37
- package/dist/cli/commands/full.js.map +0 -1
- package/dist/cli/commands/live-scan.d.ts +0 -3
- package/dist/cli/commands/live-scan.d.ts.map +0 -1
- package/dist/cli/commands/live-scan.js +0 -78
- package/dist/cli/commands/live-scan.js.map +0 -1
- package/dist/cli/commands/map.d.ts +0 -3
- package/dist/cli/commands/map.d.ts.map +0 -1
- package/dist/cli/commands/map.js +0 -93
- package/dist/cli/commands/map.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -3
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -43
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/utils.d.ts +0 -6
- package/dist/cli/utils.d.ts.map +0 -1
- package/dist/cli/utils.js +0 -101
- package/dist/cli/utils.js.map +0 -1
- package/dist/config/config-loader.d.ts +0 -51
- package/dist/config/config-loader.d.ts.map +0 -1
- package/dist/config/config-loader.js +0 -216
- package/dist/config/config-loader.js.map +0 -1
- package/dist/config/config-schema.d.ts +0 -121
- package/dist/config/config-schema.d.ts.map +0 -1
- package/dist/config/config-schema.js +0 -7
- package/dist/config/config-schema.js.map +0 -1
- package/dist/core/live-scanner/config-reader.d.ts +0 -10
- package/dist/core/live-scanner/config-reader.d.ts.map +0 -1
- package/dist/core/live-scanner/config-reader.js +0 -87
- package/dist/core/live-scanner/config-reader.js.map +0 -1
- package/dist/core/live-scanner/element-finder.d.ts +0 -20
- package/dist/core/live-scanner/element-finder.d.ts.map +0 -1
- package/dist/core/live-scanner/element-finder.js +0 -481
- package/dist/core/live-scanner/element-finder.js.map +0 -1
- package/dist/core/live-scanner/index.d.ts +0 -8
- package/dist/core/live-scanner/index.d.ts.map +0 -1
- package/dist/core/live-scanner/index.js +0 -33
- package/dist/core/live-scanner/index.js.map +0 -1
- package/dist/core/live-scanner/matrix-reader.d.ts +0 -17
- package/dist/core/live-scanner/matrix-reader.d.ts.map +0 -1
- package/dist/core/live-scanner/matrix-reader.js +0 -60
- package/dist/core/live-scanner/matrix-reader.js.map +0 -1
- package/dist/core/live-scanner/matrix-writer.d.ts +0 -7
- package/dist/core/live-scanner/matrix-writer.d.ts.map +0 -1
- package/dist/core/live-scanner/matrix-writer.js +0 -103
- package/dist/core/live-scanner/matrix-writer.js.map +0 -1
- package/dist/core/live-scanner/role-fallback.d.ts +0 -15
- package/dist/core/live-scanner/role-fallback.d.ts.map +0 -1
- package/dist/core/live-scanner/role-fallback.js +0 -46
- package/dist/core/live-scanner/role-fallback.js.map +0 -1
- package/dist/core/live-scanner/scanner.d.ts +0 -22
- package/dist/core/live-scanner/scanner.d.ts.map +0 -1
- package/dist/core/live-scanner/scanner.js +0 -303
- package/dist/core/live-scanner/scanner.js.map +0 -1
- package/dist/core/live-scanner/step-replayer.d.ts +0 -26
- package/dist/core/live-scanner/step-replayer.d.ts.map +0 -1
- package/dist/core/live-scanner/step-replayer.js +0 -473
- package/dist/core/live-scanner/step-replayer.js.map +0 -1
- package/dist/core/live-scanner/types.d.ts +0 -52
- package/dist/core/live-scanner/types.d.ts.map +0 -1
- package/dist/core/live-scanner/types.js +0 -14
- package/dist/core/live-scanner/types.js.map +0 -1
- package/dist/core/selector-base/annotation-handler.d.ts +0 -45
- package/dist/core/selector-base/annotation-handler.d.ts.map +0 -1
- package/dist/core/selector-base/annotation-handler.js +0 -102
- package/dist/core/selector-base/annotation-handler.js.map +0 -1
- package/dist/core/selector-base/base-generator.d.ts +0 -49
- package/dist/core/selector-base/base-generator.d.ts.map +0 -1
- package/dist/core/selector-base/base-generator.js +0 -214
- package/dist/core/selector-base/base-generator.js.map +0 -1
- package/dist/core/selector-base/gherkin-parser.d.ts +0 -24
- package/dist/core/selector-base/gherkin-parser.d.ts.map +0 -1
- package/dist/core/selector-base/gherkin-parser.js +0 -42
- package/dist/core/selector-base/gherkin-parser.js.map +0 -1
- package/dist/core/selector-mapper/priority-mapper.d.ts +0 -74
- package/dist/core/selector-mapper/priority-mapper.d.ts.map +0 -1
- package/dist/core/selector-mapper/priority-mapper.js +0 -477
- package/dist/core/selector-mapper/priority-mapper.js.map +0 -1
- package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts +0 -91
- package/dist/core/ui-scanner/heuristics/base-heuristic.d.ts.map +0 -1
- package/dist/core/ui-scanner/heuristics/base-heuristic.js +0 -175
- package/dist/core/ui-scanner/heuristics/base-heuristic.js.map +0 -1
- package/dist/core/ui-scanner/react-scanner.d.ts +0 -32
- package/dist/core/ui-scanner/react-scanner.d.ts.map +0 -1
- package/dist/core/ui-scanner/react-scanner.js +0 -163
- package/dist/core/ui-scanner/react-scanner.js.map +0 -1
- package/dist/core/ui-scanner/scanner-interface.d.ts +0 -94
- package/dist/core/ui-scanner/scanner-interface.d.ts.map +0 -1
- package/dist/core/ui-scanner/scanner-interface.js +0 -33
- package/dist/core/ui-scanner/scanner-interface.js.map +0 -1
- package/dist/core/ui-scanner/strict-scanner.d.ts +0 -81
- package/dist/core/ui-scanner/strict-scanner.d.ts.map +0 -1
- package/dist/core/ui-scanner/strict-scanner.js +0 -511
- package/dist/core/ui-scanner/strict-scanner.js.map +0 -1
- package/dist/core/validator/data-validator.d.ts +0 -38
- package/dist/core/validator/data-validator.d.ts.map +0 -1
- package/dist/core/validator/data-validator.js +0 -212
- package/dist/core/validator/data-validator.js.map +0 -1
- package/dist/core/validator/feature-validator.d.ts +0 -27
- package/dist/core/validator/feature-validator.d.ts.map +0 -1
- package/dist/core/validator/feature-validator.js +0 -182
- package/dist/core/validator/feature-validator.js.map +0 -1
- package/dist/core/validator/index.d.ts +0 -46
- package/dist/core/validator/index.d.ts.map +0 -1
- package/dist/core/validator/index.js +0 -17
- package/dist/core/validator/index.js.map +0 -1
- package/dist/core/validator/screen-validator.d.ts +0 -35
- package/dist/core/validator/screen-validator.d.ts.map +0 -1
- package/dist/core/validator/screen-validator.js +0 -195
- package/dist/core/validator/screen-validator.js.map +0 -1
- package/dist/core/validator/selector-validator.d.ts +0 -36
- package/dist/core/validator/selector-validator.d.ts.map +0 -1
- package/dist/core/validator/selector-validator.js +0 -210
- package/dist/core/validator/selector-validator.js.map +0 -1
- package/dist/external/ai-provider.d.ts +0 -60
- package/dist/external/ai-provider.d.ts.map +0 -1
- package/dist/external/ai-provider.js +0 -30
- package/dist/external/ai-provider.js.map +0 -1
- package/dist/external/anthropic-provider.d.ts +0 -29
- package/dist/external/anthropic-provider.d.ts.map +0 -1
- package/dist/external/anthropic-provider.js +0 -85
- package/dist/external/anthropic-provider.js.map +0 -1
- package/dist/generators/cache/cache-manager.d.ts +0 -66
- package/dist/generators/cache/cache-manager.d.ts.map +0 -1
- package/dist/generators/cache/cache-manager.js +0 -286
- package/dist/generators/cache/cache-manager.js.map +0 -1
- package/dist/generators/dsl-writer/index.d.ts +0 -33
- package/dist/generators/dsl-writer/index.d.ts.map +0 -1
- package/dist/generators/dsl-writer/index.js +0 -226
- package/dist/generators/dsl-writer/index.js.map +0 -1
- package/dist/generators/scaffold-generator/index.d.ts +0 -162
- package/dist/generators/scaffold-generator/index.d.ts.map +0 -1
- package/dist/generators/scaffold-generator/index.js +0 -877
- package/dist/generators/scaffold-generator/index.js.map +0 -1
- package/dist/generators/selector-mapper/ai-mapper.d.ts +0 -56
- package/dist/generators/selector-mapper/ai-mapper.d.ts.map +0 -1
- package/dist/generators/selector-mapper/ai-mapper.js +0 -457
- package/dist/generators/selector-mapper/ai-mapper.js.map +0 -1
- package/dist/generators/selector-mapper/hybrid-mapper.d.ts +0 -67
- package/dist/generators/selector-mapper/hybrid-mapper.d.ts.map +0 -1
- package/dist/generators/selector-mapper/hybrid-mapper.js +0 -349
- package/dist/generators/selector-mapper/hybrid-mapper.js.map +0 -1
- package/dist/generators/selector-mapper/index.d.ts +0 -8
- package/dist/generators/selector-mapper/index.d.ts.map +0 -1
- package/dist/generators/selector-mapper/index.js +0 -12
- package/dist/generators/selector-mapper/index.js.map +0 -1
- package/dist/generators/selector-mapper/intelligent-mapper.d.ts +0 -125
- package/dist/generators/selector-mapper/intelligent-mapper.d.ts.map +0 -1
- package/dist/generators/selector-mapper/intelligent-mapper.js +0 -391
- package/dist/generators/selector-mapper/intelligent-mapper.js.map +0 -1
- package/dist/generators/test-generator/ai-step-mapper.d.ts +0 -27
- package/dist/generators/test-generator/ai-step-mapper.d.ts.map +0 -1
- package/dist/generators/test-generator/ai-step-mapper.js +0 -204
- package/dist/generators/test-generator/ai-step-mapper.js.map +0 -1
- package/dist/generators/test-generator/auth-setup-generator.d.ts +0 -18
- package/dist/generators/test-generator/auth-setup-generator.d.ts.map +0 -1
- package/dist/generators/test-generator/auth-setup-generator.js +0 -82
- package/dist/generators/test-generator/auth-setup-generator.js.map +0 -1
- package/dist/generators/test-generator/patterns/legacy-patterns.d.ts +0 -7
- package/dist/generators/test-generator/patterns/legacy-patterns.d.ts.map +0 -1
- package/dist/generators/test-generator/patterns/legacy-patterns.js +0 -98
- package/dist/generators/test-generator/patterns/legacy-patterns.js.map +0 -1
- package/dist/generators/test-generator/templates/auth-setup.ts.hbs +0 -36
- package/dist/generators/ui-model-builder/deep-scanner.d.ts +0 -121
- package/dist/generators/ui-model-builder/deep-scanner.d.ts.map +0 -1
- package/dist/generators/ui-model-builder/deep-scanner.js +0 -1113
- package/dist/generators/ui-model-builder/deep-scanner.js.map +0 -1
- package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts +0 -110
- package/dist/generators/ui-model-builder/enhanced-deep-scanner.d.ts.map +0 -1
- package/dist/generators/ui-model-builder/enhanced-deep-scanner.js +0 -608
- package/dist/generators/ui-model-builder/enhanced-deep-scanner.js.map +0 -1
- package/dist/generators/ui-model-builder/react-scanner.d.ts +0 -107
- package/dist/generators/ui-model-builder/react-scanner.d.ts.map +0 -1
- package/dist/generators/ui-model-builder/react-scanner.js +0 -797
- package/dist/generators/ui-model-builder/react-scanner.js.map +0 -1
- package/dist/orchestrator/cache-manager.d.ts +0 -15
- package/dist/orchestrator/cache-manager.d.ts.map +0 -1
- package/dist/orchestrator/cache-manager.js +0 -62
- package/dist/orchestrator/cache-manager.js.map +0 -1
- package/dist/orchestrator/pipeline.d.ts +0 -56
- package/dist/orchestrator/pipeline.d.ts.map +0 -1
- package/dist/orchestrator/pipeline.js +0 -298
- package/dist/orchestrator/pipeline.js.map +0 -1
- package/dist/orchestrator/reporter.d.ts +0 -15
- package/dist/orchestrator/reporter.d.ts.map +0 -1
- package/dist/orchestrator/reporter.js +0 -30
- package/dist/orchestrator/reporter.js.map +0 -1
- package/src/cli/commands/cache-clear.ts +0 -22
- package/src/cli/commands/full.ts +0 -35
- package/src/cli/commands/live-scan.ts +0 -82
- package/src/cli/commands/map.ts +0 -97
- package/src/cli/commands/validate.ts +0 -43
- package/src/cli/utils.ts +0 -106
- package/src/config/ai-providers.yaml +0 -56
- package/src/config/config-loader.ts +0 -248
- package/src/config/config-schema.ts +0 -148
- package/src/config/default.config.yaml +0 -107
- package/src/config/framework.config.yaml +0 -52
- package/src/config/routes.yaml +0 -31
- package/src/core/live-scanner/config-reader.ts +0 -57
- package/src/core/live-scanner/element-finder.ts +0 -534
- package/src/core/live-scanner/index.ts +0 -7
- package/src/core/live-scanner/matrix-reader.ts +0 -65
- package/src/core/live-scanner/matrix-writer.ts +0 -77
- package/src/core/live-scanner/role-fallback.ts +0 -44
- package/src/core/live-scanner/scanner.ts +0 -321
- package/src/core/live-scanner/step-replayer.ts +0 -503
- package/src/core/live-scanner/types.ts +0 -58
- package/src/core/selector-base/annotation-handler.ts +0 -127
- package/src/core/selector-base/base-generator.ts +0 -234
- package/src/core/selector-base/gherkin-parser.ts +0 -57
- package/src/core/selector-mapper/priority-mapper.ts +0 -607
- package/src/core/ui-scanner/heuristics/base-heuristic.ts +0 -216
- package/src/core/ui-scanner/react-scanner.ts +0 -156
- package/src/core/ui-scanner/scanner-interface.ts +0 -133
- package/src/core/ui-scanner/strict-scanner.ts +0 -629
- package/src/core/validator/data-validator.ts +0 -202
- package/src/core/validator/feature-validator.ts +0 -176
- package/src/core/validator/index.ts +0 -57
- package/src/core/validator/screen-validator.ts +0 -209
- package/src/core/validator/selector-validator.ts +0 -209
- package/src/external/ai-provider.ts +0 -90
- package/src/external/anthropic-provider.ts +0 -114
- package/src/generators/README.md +0 -410
- package/src/generators/cache/cache-manager.ts +0 -322
- package/src/generators/dsl-writer/index.ts +0 -253
- package/src/generators/scaffold-generator/index.ts +0 -1029
- package/src/generators/selector-mapper/ai-mapper.ts +0 -528
- package/src/generators/selector-mapper/hybrid-mapper.ts +0 -427
- package/src/generators/selector-mapper/index.ts +0 -10
- package/src/generators/selector-mapper/intelligent-mapper.ts +0 -530
- package/src/generators/test-generator/ai-step-mapper.ts +0 -224
- package/src/generators/test-generator/auth-setup-generator.ts +0 -59
- package/src/generators/test-generator/patterns/legacy-patterns.ts +0 -104
- package/src/generators/test-generator/templates/auth-setup.ts.hbs +0 -36
- package/src/generators/ui-model-builder/deep-scanner.ts +0 -1244
- package/src/generators/ui-model-builder/enhanced-deep-scanner.ts +0 -731
- package/src/generators/ui-model-builder/react-scanner.ts +0 -959
- package/src/orchestrator/cache-manager.ts +0 -32
- package/src/orchestrator/pipeline.ts +0 -354
- package/src/orchestrator/reporter.ts +0 -36
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Element Finder
|
|
3
|
-
*
|
|
4
|
-
* Searches for elements using Playwright's own locator strategies in priority order:
|
|
5
|
-
* ① getByTestId — data-testid attribute
|
|
6
|
-
* ② getByRole — ARIA role + accessible name (exact match, then fallback roles)
|
|
7
|
-
* ③ getByLabel — associated <label> text
|
|
8
|
-
* ④ getByPlaceholder — placeholder attribute
|
|
9
|
-
* ⑤ getByText — visible text content
|
|
10
|
-
*
|
|
11
|
-
* The result records WHICH Playwright strategy found the element,
|
|
12
|
-
* so `map` can generate the correct locator code.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Page, Locator } from 'playwright';
|
|
16
|
-
import { getRoleFallbacks, isTextOnlyType } from './role-fallback';
|
|
17
|
-
import { LiveElement, MatchMethod, SelectorType, ElementContext } from './types';
|
|
18
|
-
|
|
19
|
-
interface FindResult {
|
|
20
|
-
locator: Locator;
|
|
21
|
-
selectorType: SelectorType;
|
|
22
|
-
selectorValue: string;
|
|
23
|
-
role: string;
|
|
24
|
-
matchMethod: MatchMethod;
|
|
25
|
-
warning: string | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Find an element on the page using cascading Playwright locator strategies.
|
|
30
|
-
*/
|
|
31
|
-
export async function findElement(
|
|
32
|
-
page: Page,
|
|
33
|
-
gherkinRef: string,
|
|
34
|
-
gherkinType: string,
|
|
35
|
-
nth: number = 0,
|
|
36
|
-
context: ElementContext = 'page'
|
|
37
|
-
): Promise<LiveElement> {
|
|
38
|
-
const namePattern = new RegExp(escapeRegex(gherkinRef), 'i');
|
|
39
|
-
|
|
40
|
-
const result = await cascadingSearch(page, gherkinRef, gherkinType, namePattern, nth, context);
|
|
41
|
-
|
|
42
|
-
if (!result) {
|
|
43
|
-
return buildUnresolvedElement(gherkinRef, gherkinType);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const attrs = await extractElementAttributes(result.locator);
|
|
47
|
-
|
|
48
|
-
// Use Playwright's computed accessible name (includes alt text, aria-label, etc.)
|
|
49
|
-
// This matches what getByRole/getByText actually see, unlike textContent
|
|
50
|
-
let playwrightName = '';
|
|
51
|
-
try {
|
|
52
|
-
playwrightName = await result.locator.evaluate((el) => {
|
|
53
|
-
// computedRole/computedLabel not available in all browsers, fallback to aria
|
|
54
|
-
const raw = (el as any).computedName
|
|
55
|
-
|| el.getAttribute('aria-label')
|
|
56
|
-
|| el.textContent?.trim()?.substring(0, 100)
|
|
57
|
-
|| '';
|
|
58
|
-
return raw.replace(/[\n\r\t]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
59
|
-
});
|
|
60
|
-
// Better approach: use Playwright's accessibility snapshot
|
|
61
|
-
const ariaSnapshot = await result.locator.ariaSnapshot();
|
|
62
|
-
// Parse accessible name from snapshot (format: "- role \"name\"" or "- role \"name\": ...")
|
|
63
|
-
// Regex handles escaped quotes (\") inside the name string
|
|
64
|
-
const nameMatch = ariaSnapshot?.match(/"((?:[^"\\]|\\.)*)"/);
|
|
65
|
-
if (nameMatch) {
|
|
66
|
-
playwrightName = unescapeAriaSnapshot(nameMatch[1]);
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
playwrightName = attrs.accessibleName || gherkinRef;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const actualName = playwrightName || attrs.accessibleName || gherkinRef;
|
|
73
|
-
const isExactMatch = actualName.toLowerCase().trim() === gherkinRef.toLowerCase().trim();
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
gherkinRef,
|
|
77
|
-
gherkinType,
|
|
78
|
-
selectorType: result.selectorType,
|
|
79
|
-
selectorValue: result.selectorValue,
|
|
80
|
-
role: result.role,
|
|
81
|
-
name: actualName,
|
|
82
|
-
testid: attrs.testid,
|
|
83
|
-
tag: attrs.tag,
|
|
84
|
-
contenteditable: attrs.contenteditable,
|
|
85
|
-
placeholder: attrs.placeholder,
|
|
86
|
-
label: attrs.label,
|
|
87
|
-
context: 'page',
|
|
88
|
-
matchMethod: result.matchMethod,
|
|
89
|
-
exact: isExactMatch,
|
|
90
|
-
warning: result.warning,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function cascadingSearch(
|
|
95
|
-
page: Page,
|
|
96
|
-
gherkinRef: string,
|
|
97
|
-
gherkinType: string,
|
|
98
|
-
namePattern: RegExp,
|
|
99
|
-
nth: number,
|
|
100
|
-
context: ElementContext = 'page'
|
|
101
|
-
): Promise<FindResult | null> {
|
|
102
|
-
// ① getByTestId
|
|
103
|
-
const testidResult = await tryTestId(page, gherkinRef, nth);
|
|
104
|
-
if (testidResult) return testidResult;
|
|
105
|
-
|
|
106
|
-
// For text-only types (text, message, error), skip role and go to text
|
|
107
|
-
if (!isTextOnlyType(gherkinType)) {
|
|
108
|
-
// ② getByRole — exact role match
|
|
109
|
-
const roles = getRoleFallbacks(gherkinType);
|
|
110
|
-
if (roles.length > 0) {
|
|
111
|
-
const exactResult = await tryRole(page, roles[0], gherkinRef, namePattern, nth);
|
|
112
|
-
if (exactResult) {
|
|
113
|
-
return {
|
|
114
|
-
...exactResult,
|
|
115
|
-
matchMethod: 'exact_role',
|
|
116
|
-
warning: null,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ③ getByRole — fallback roles
|
|
121
|
-
for (let i = 1; i < roles.length; i++) {
|
|
122
|
-
const fallbackResult = await tryRole(page, roles[i], gherkinRef, namePattern, nth);
|
|
123
|
-
if (fallbackResult) {
|
|
124
|
-
return {
|
|
125
|
-
...fallbackResult,
|
|
126
|
-
matchMethod: 'role_fallback',
|
|
127
|
-
warning: `Gherkin says '${gherkinType}' but found role '${roles[i]}'`,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ④ getByLabel — for form elements
|
|
134
|
-
const labelResult = await tryLabel(page, gherkinRef, namePattern, nth);
|
|
135
|
-
if (labelResult) return labelResult;
|
|
136
|
-
|
|
137
|
-
// ⑤ getByPlaceholder — for input fields
|
|
138
|
-
const placeholderResult = await tryPlaceholder(page, gherkinRef, namePattern, nth);
|
|
139
|
-
if (placeholderResult) return placeholderResult;
|
|
140
|
-
|
|
141
|
-
// ⑤.5 contenteditable — for rich text editors (textarea/textbox/field types)
|
|
142
|
-
// When role match fails (editor has role="textbox" but no accessible name),
|
|
143
|
-
// try to find a [contenteditable="true"] element scoped to the current context
|
|
144
|
-
if (isEditableType(gherkinType)) {
|
|
145
|
-
const ceResult = await tryContentEditable(page, nth, context);
|
|
146
|
-
if (ceResult) return ceResult;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ⑥ getByText — final fallback
|
|
151
|
-
const textResult = await tryText(page, gherkinRef, namePattern, nth);
|
|
152
|
-
if (textResult) return textResult;
|
|
153
|
-
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ① getByTestId
|
|
158
|
-
async function tryTestId(page: Page, ref: string, nth: number): Promise<FindResult | null> {
|
|
159
|
-
const normalized = ref.toLowerCase().replace(/[\s_-]+/g, '-');
|
|
160
|
-
const patterns = [normalized, ref.toLowerCase().replace(/\s+/g, '')];
|
|
161
|
-
|
|
162
|
-
for (const pattern of patterns) {
|
|
163
|
-
try {
|
|
164
|
-
const locator = page.locator(`[data-testid*="${pattern}" i]`);
|
|
165
|
-
const count = await locator.count();
|
|
166
|
-
if (count > 0) {
|
|
167
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
168
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
169
|
-
const testid = await target.getAttribute('data-testid');
|
|
170
|
-
return {
|
|
171
|
-
locator: target,
|
|
172
|
-
selectorType: 'testid',
|
|
173
|
-
selectorValue: testid || pattern,
|
|
174
|
-
role: '',
|
|
175
|
-
matchMethod: 'testid',
|
|
176
|
-
warning: null,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
} catch {
|
|
181
|
-
// Continue
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ② ③ getByRole — regex first, exact disambiguation when ambiguous
|
|
188
|
-
async function tryRole(
|
|
189
|
-
page: Page,
|
|
190
|
-
role: string,
|
|
191
|
-
gherkinRef: string,
|
|
192
|
-
namePattern: RegExp,
|
|
193
|
-
nth: number
|
|
194
|
-
): Promise<FindResult | null> {
|
|
195
|
-
try {
|
|
196
|
-
const locator = page.getByRole(role as any, { name: namePattern });
|
|
197
|
-
const count = await locator.count();
|
|
198
|
-
if (count === 0) return null;
|
|
199
|
-
|
|
200
|
-
if (count === 1) {
|
|
201
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
202
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
203
|
-
return {
|
|
204
|
-
locator: target,
|
|
205
|
-
selectorType: 'role',
|
|
206
|
-
selectorValue: role,
|
|
207
|
-
role: role,
|
|
208
|
-
matchMethod: 'exact_role',
|
|
209
|
-
warning: null,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
// Multiple matches — try exact to disambiguate
|
|
214
|
-
try {
|
|
215
|
-
const exactLocator = page.getByRole(role as any, { name: gherkinRef, exact: true });
|
|
216
|
-
const exactCount = await exactLocator.count();
|
|
217
|
-
if (exactCount > 0) {
|
|
218
|
-
const target = nth > 0 ? exactLocator.nth(nth) : exactLocator.first();
|
|
219
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
220
|
-
return {
|
|
221
|
-
locator: target,
|
|
222
|
-
selectorType: 'role',
|
|
223
|
-
selectorValue: role,
|
|
224
|
-
role: role,
|
|
225
|
-
matchMethod: 'exact_role',
|
|
226
|
-
warning: null,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
} catch {
|
|
231
|
-
// Exact disambiguation failed
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Fall back to first regex match
|
|
235
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
236
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
237
|
-
return {
|
|
238
|
-
locator: target,
|
|
239
|
-
selectorType: 'role',
|
|
240
|
-
selectorValue: role,
|
|
241
|
-
role: role,
|
|
242
|
-
matchMethod: 'exact_role',
|
|
243
|
-
warning: null,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} catch {
|
|
248
|
-
// Role not found
|
|
249
|
-
}
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ④ getByLabel — regex first, exact disambiguation when ambiguous
|
|
254
|
-
async function tryLabel(page: Page, gherkinRef: string, namePattern: RegExp, nth: number): Promise<FindResult | null> {
|
|
255
|
-
try {
|
|
256
|
-
const locator = page.getByLabel(namePattern);
|
|
257
|
-
const count = await locator.count();
|
|
258
|
-
if (count === 0) return null;
|
|
259
|
-
|
|
260
|
-
if (count > 1) {
|
|
261
|
-
// Multiple matches — try exact to disambiguate
|
|
262
|
-
try {
|
|
263
|
-
const exactLocator = page.getByLabel(gherkinRef, { exact: true });
|
|
264
|
-
const exactCount = await exactLocator.count();
|
|
265
|
-
if (exactCount > 0) {
|
|
266
|
-
const target = nth > 0 ? exactLocator.nth(nth) : exactLocator.first();
|
|
267
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
268
|
-
return {
|
|
269
|
-
locator: target,
|
|
270
|
-
selectorType: 'label',
|
|
271
|
-
selectorValue: '',
|
|
272
|
-
role: '',
|
|
273
|
-
matchMethod: 'label',
|
|
274
|
-
warning: null,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
} catch {
|
|
279
|
-
// Exact disambiguation failed
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Single match or exact failed — use first regex match
|
|
284
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
285
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
286
|
-
return {
|
|
287
|
-
locator: target,
|
|
288
|
-
selectorType: 'label',
|
|
289
|
-
selectorValue: '',
|
|
290
|
-
role: '',
|
|
291
|
-
matchMethod: 'label',
|
|
292
|
-
warning: null,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
} catch {
|
|
296
|
-
// Label not found
|
|
297
|
-
}
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// ⑤ getByPlaceholder — regex first, exact disambiguation when ambiguous
|
|
302
|
-
async function tryPlaceholder(page: Page, gherkinRef: string, namePattern: RegExp, nth: number): Promise<FindResult | null> {
|
|
303
|
-
try {
|
|
304
|
-
const locator = page.getByPlaceholder(namePattern);
|
|
305
|
-
const count = await locator.count();
|
|
306
|
-
if (count === 0) return null;
|
|
307
|
-
|
|
308
|
-
if (count > 1) {
|
|
309
|
-
// Multiple matches — try exact to disambiguate
|
|
310
|
-
try {
|
|
311
|
-
const exactLocator = page.getByPlaceholder(gherkinRef, { exact: true });
|
|
312
|
-
const exactCount = await exactLocator.count();
|
|
313
|
-
if (exactCount > 0) {
|
|
314
|
-
const target = nth > 0 ? exactLocator.nth(nth) : exactLocator.first();
|
|
315
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
316
|
-
const rawPlaceholder = await target.getAttribute('placeholder');
|
|
317
|
-
return {
|
|
318
|
-
locator: target,
|
|
319
|
-
selectorType: 'placeholder',
|
|
320
|
-
selectorValue: sanitizeText(rawPlaceholder || ''),
|
|
321
|
-
role: '',
|
|
322
|
-
matchMethod: 'placeholder',
|
|
323
|
-
warning: null,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
} catch {
|
|
328
|
-
// Exact disambiguation failed
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Single match or exact failed — use first regex match
|
|
333
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
334
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
335
|
-
const rawPlaceholder = await target.getAttribute('placeholder');
|
|
336
|
-
return {
|
|
337
|
-
locator: target,
|
|
338
|
-
selectorType: 'placeholder',
|
|
339
|
-
selectorValue: sanitizeText(rawPlaceholder || ''),
|
|
340
|
-
role: '',
|
|
341
|
-
matchMethod: 'placeholder',
|
|
342
|
-
warning: null,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
} catch {
|
|
346
|
-
// Placeholder not found
|
|
347
|
-
}
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Check if gherkinType is an editable/input type that could be a contenteditable div
|
|
353
|
-
*/
|
|
354
|
-
function isEditableType(gherkinType: string): boolean {
|
|
355
|
-
const normalized = gherkinType.toLowerCase().trim();
|
|
356
|
-
return normalized === 'textarea' || normalized === 'textbox' || normalized === 'field' || normalized === 'editor';
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// ⑤.5 contenteditable — for rich text editors
|
|
362
|
-
// Scoped to current context (dialog/page) to avoid matching wrong editor.
|
|
363
|
-
// The nth from Gherkin refers to text matches, not contenteditable count,
|
|
364
|
-
// so we try the requested nth first, then fall back to first match.
|
|
365
|
-
async function tryContentEditable(page: Page, nth: number, context: ElementContext): Promise<FindResult | null> {
|
|
366
|
-
try {
|
|
367
|
-
// Scope the search to the current context
|
|
368
|
-
const root = context === 'dialog' ? page.getByRole('dialog') : page;
|
|
369
|
-
const locator = root.locator('[contenteditable="true"]');
|
|
370
|
-
const count = await locator.count();
|
|
371
|
-
if (count === 0) return null;
|
|
372
|
-
|
|
373
|
-
// Try requested nth first, then fall back to first visible
|
|
374
|
-
const candidates = nth > 0 && count > nth ? [nth, 0] : [0];
|
|
375
|
-
for (const idx of candidates) {
|
|
376
|
-
const target = idx > 0 ? locator.nth(idx) : locator.first();
|
|
377
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
378
|
-
return {
|
|
379
|
-
locator: target,
|
|
380
|
-
selectorType: 'locator',
|
|
381
|
-
selectorValue: '[contenteditable="true"]',
|
|
382
|
-
role: 'textbox',
|
|
383
|
-
matchMethod: 'contenteditable',
|
|
384
|
-
warning: count > 1
|
|
385
|
-
? `${count} contenteditable elements in ${context}, using index ${idx}. Use nth in Gherkin to disambiguate.`
|
|
386
|
-
: null,
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
// Not found
|
|
392
|
-
}
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ⑥ getByText — regex first, exact disambiguation when ambiguous
|
|
397
|
-
async function tryText(page: Page, gherkinRef: string, namePattern: RegExp, nth: number): Promise<FindResult | null> {
|
|
398
|
-
try {
|
|
399
|
-
const locator = page.getByText(namePattern);
|
|
400
|
-
const count = await locator.count();
|
|
401
|
-
if (count === 0) return null;
|
|
402
|
-
|
|
403
|
-
if (count > 1) {
|
|
404
|
-
// Multiple matches — try exact to disambiguate
|
|
405
|
-
try {
|
|
406
|
-
const exactLocator = page.getByText(gherkinRef, { exact: true });
|
|
407
|
-
const exactCount = await exactLocator.count();
|
|
408
|
-
if (exactCount > 0) {
|
|
409
|
-
const target = nth > 0 ? exactLocator.nth(nth) : exactLocator.first();
|
|
410
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
411
|
-
return {
|
|
412
|
-
locator: target,
|
|
413
|
-
selectorType: 'text',
|
|
414
|
-
selectorValue: '',
|
|
415
|
-
role: '',
|
|
416
|
-
matchMethod: 'text_only',
|
|
417
|
-
warning: null,
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
} catch {
|
|
422
|
-
// Exact disambiguation failed
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Single match or exact failed — use first regex match
|
|
427
|
-
const target = nth > 0 ? locator.nth(nth) : locator.first();
|
|
428
|
-
if (await target.isVisible({ timeout: 3000 })) {
|
|
429
|
-
return {
|
|
430
|
-
locator: target,
|
|
431
|
-
selectorType: 'text',
|
|
432
|
-
selectorValue: '',
|
|
433
|
-
role: '',
|
|
434
|
-
matchMethod: 'text_only',
|
|
435
|
-
warning: null,
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
} catch {
|
|
439
|
-
// Text not found
|
|
440
|
-
}
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function extractElementAttributes(locator: Locator): Promise<{
|
|
445
|
-
testid: string | null;
|
|
446
|
-
tag: string;
|
|
447
|
-
contenteditable: boolean;
|
|
448
|
-
accessibleName: string;
|
|
449
|
-
placeholder: string | null;
|
|
450
|
-
label: string | null;
|
|
451
|
-
}> {
|
|
452
|
-
try {
|
|
453
|
-
return await locator.evaluate((el) => {
|
|
454
|
-
// Inline sanitizer — browser context can't access Node functions
|
|
455
|
-
const clean = (s: string | null | undefined): string =>
|
|
456
|
-
(s || '').replace(/[\n\r\t]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
457
|
-
|
|
458
|
-
// Find associated label
|
|
459
|
-
let label: string | null = null;
|
|
460
|
-
if (el.id) {
|
|
461
|
-
const labelEl = el.ownerDocument.querySelector(`label[for="${el.id}"]`);
|
|
462
|
-
if (labelEl) label = clean(labelEl.textContent) || null;
|
|
463
|
-
}
|
|
464
|
-
if (!label && el.closest('label')) {
|
|
465
|
-
label = clean(el.closest('label')?.textContent) || null;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Detect contenteditable: check element itself and ancestors (rich text editors
|
|
469
|
-
// often have contenteditable on a parent div wrapping the editable area)
|
|
470
|
-
const isContentEditable = el.getAttribute('contenteditable') === 'true'
|
|
471
|
-
|| el.isContentEditable;
|
|
472
|
-
|
|
473
|
-
return {
|
|
474
|
-
testid: el.getAttribute('data-testid'),
|
|
475
|
-
tag: el.tagName.toLowerCase(),
|
|
476
|
-
contenteditable: isContentEditable,
|
|
477
|
-
accessibleName: clean(el.getAttribute('aria-label') || el.textContent?.trim()?.substring(0, 100)),
|
|
478
|
-
placeholder: clean(el.getAttribute('placeholder')) || null,
|
|
479
|
-
label,
|
|
480
|
-
};
|
|
481
|
-
});
|
|
482
|
-
} catch {
|
|
483
|
-
return { testid: null, tag: 'unknown', contenteditable: false, accessibleName: '', placeholder: null, label: null };
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function buildUnresolvedElement(gherkinRef: string, gherkinType: string): LiveElement {
|
|
488
|
-
return {
|
|
489
|
-
gherkinRef,
|
|
490
|
-
gherkinType,
|
|
491
|
-
selectorType: '',
|
|
492
|
-
selectorValue: '',
|
|
493
|
-
role: '',
|
|
494
|
-
name: gherkinRef,
|
|
495
|
-
testid: null,
|
|
496
|
-
tag: '',
|
|
497
|
-
contenteditable: false,
|
|
498
|
-
placeholder: null,
|
|
499
|
-
label: null,
|
|
500
|
-
context: 'page',
|
|
501
|
-
matchMethod: 'unresolved',
|
|
502
|
-
exact: false,
|
|
503
|
-
warning: 'Element not found on page',
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Normalize a DOM text value for YAML output:
|
|
509
|
-
* collapse real newlines/tabs/carriage-returns into spaces, then deduplicate whitespace.
|
|
510
|
-
* Runs in both browser evaluate() and Node context.
|
|
511
|
-
*/
|
|
512
|
-
function sanitizeText(str: string): string {
|
|
513
|
-
return str.replace(/[\n\r\t]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Unescape a string extracted from Playwright's ariaSnapshot() output.
|
|
518
|
-
* ariaSnapshot encodes special chars as literal escape sequences:
|
|
519
|
-
* \n → newline, \t → tab, \\ → backslash, \" → quote
|
|
520
|
-
* We unescape them then normalize whitespace.
|
|
521
|
-
*/
|
|
522
|
-
function unescapeAriaSnapshot(str: string): string {
|
|
523
|
-
return str
|
|
524
|
-
.replace(/\\n/g, ' ')
|
|
525
|
-
.replace(/\\t/g, ' ')
|
|
526
|
-
.replace(/\\"/g, '"')
|
|
527
|
-
.replace(/\\\\/g, '\\')
|
|
528
|
-
.replace(/\s+/g, ' ')
|
|
529
|
-
.trim();
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
function escapeRegex(str: string): string {
|
|
533
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
534
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { LiveScanner } from './scanner';
|
|
2
|
-
export { findElement } from './element-finder';
|
|
3
|
-
export { getRoleFallbacks, isTextOnlyType } from './role-fallback';
|
|
4
|
-
export { writeMatrix } from './matrix-writer';
|
|
5
|
-
export { readMatrix, mergeMatrixElements } from './matrix-reader';
|
|
6
|
-
export { readBaseUrlFromPlaywrightConfig } from './config-reader';
|
|
7
|
-
export * from './types';
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Matrix Reader
|
|
3
|
-
* Loads and parses a .live-scan.yaml file for consumption by the map command.
|
|
4
|
-
* Merges elements across scenarios (first match wins).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readYamlIfExists } from '../../utils/yaml-io';
|
|
8
|
-
import { LiveElement, LiveScanResult } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Read a .live-scan.yaml file and return the parsed result.
|
|
12
|
-
* Returns null if file doesn't exist.
|
|
13
|
-
*/
|
|
14
|
-
export function readMatrix(filePath: string): LiveScanResult | null {
|
|
15
|
-
const parsed = readYamlIfExists<any>(filePath);
|
|
16
|
-
|
|
17
|
-
if (!parsed || !parsed.scenarios) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return parsed as LiveScanResult;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Merge all elements across scenarios into a flat map.
|
|
26
|
-
* First successful (non-unresolved) match wins for duplicate keys.
|
|
27
|
-
*/
|
|
28
|
-
export function mergeMatrixElements(result: LiveScanResult): Record<string, LiveElement> {
|
|
29
|
-
const merged: Record<string, LiveElement> = {};
|
|
30
|
-
|
|
31
|
-
for (const scenario of Object.values(result.scenarios)) {
|
|
32
|
-
const elements = (scenario as any).elements;
|
|
33
|
-
if (!elements) continue;
|
|
34
|
-
|
|
35
|
-
for (const [key, raw] of Object.entries(elements)) {
|
|
36
|
-
const el = raw as any;
|
|
37
|
-
|
|
38
|
-
// Convert from YAML snake_case to interface camelCase
|
|
39
|
-
const element: LiveElement = {
|
|
40
|
-
gherkinRef: el.name || key,
|
|
41
|
-
gherkinType: el.gherkin_type || '',
|
|
42
|
-
selectorType: el.selector_type || '',
|
|
43
|
-
selectorValue: el.selector_value || '',
|
|
44
|
-
role: el.role || '',
|
|
45
|
-
name: el.name || '',
|
|
46
|
-
testid: el.testid || null,
|
|
47
|
-
tag: el.tag || '',
|
|
48
|
-
contenteditable: el.contenteditable === true,
|
|
49
|
-
placeholder: el.placeholder || null,
|
|
50
|
-
label: el.label || null,
|
|
51
|
-
context: (el.context as any) || 'page',
|
|
52
|
-
matchMethod: (el.match_method as any) || 'unresolved',
|
|
53
|
-
exact: el.exact === true,
|
|
54
|
-
warning: el.warning || null,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// First non-unresolved match wins
|
|
58
|
-
if (!merged[key] || (merged[key].matchMethod === 'unresolved' && element.matchMethod !== 'unresolved')) {
|
|
59
|
-
merged[key] = element;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return merged;
|
|
65
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Matrix Writer
|
|
3
|
-
* Writes LiveScanResult to a .live-scan.yaml file.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as yaml from 'yaml';
|
|
8
|
-
import { LiveScanResult } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Sanitize a string value before YAML serialization:
|
|
12
|
-
* collapse newlines/tabs/carriage-returns into spaces to prevent
|
|
13
|
-
* broken escape sequences (e.g. \\n) in QUOTE_DOUBLE output.
|
|
14
|
-
*/
|
|
15
|
-
function sanitizeForYaml(value: string | null): string | null {
|
|
16
|
-
if (!value) return value;
|
|
17
|
-
return value.replace(/[\n\r\t]/g, ' ').replace(/\s+/g, ' ').trim() || null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function writeMatrix(result: LiveScanResult, outputPath: string): void {
|
|
21
|
-
const output: any = {
|
|
22
|
-
screen: result.screen,
|
|
23
|
-
baseUrl: result.baseUrl,
|
|
24
|
-
scannedAt: result.scannedAt,
|
|
25
|
-
scenarios: {},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
for (const [scenarioKey, scenario] of Object.entries(result.scenarios)) {
|
|
29
|
-
const scenarioData: any = {
|
|
30
|
-
auth: scenario.auth,
|
|
31
|
-
path: scenario.path,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
if (scenario.extends) {
|
|
35
|
-
scenarioData.extends = scenario.extends;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
scenarioData.elements = {};
|
|
39
|
-
|
|
40
|
-
for (const [elementKey, element] of Object.entries(scenario.elements)) {
|
|
41
|
-
scenarioData.elements[elementKey] = {
|
|
42
|
-
gherkin_type: element.gherkinType,
|
|
43
|
-
selector_type: element.selectorType,
|
|
44
|
-
selector_value: sanitizeForYaml(element.selectorValue),
|
|
45
|
-
role: element.role,
|
|
46
|
-
name: sanitizeForYaml(element.name),
|
|
47
|
-
testid: element.testid,
|
|
48
|
-
tag: element.tag,
|
|
49
|
-
contenteditable: element.contenteditable,
|
|
50
|
-
placeholder: sanitizeForYaml(element.placeholder),
|
|
51
|
-
label: sanitizeForYaml(element.label),
|
|
52
|
-
context: element.context,
|
|
53
|
-
match_method: element.matchMethod,
|
|
54
|
-
exact: element.exact,
|
|
55
|
-
warning: element.warning,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
output.scenarios[scenarioKey] = scenarioData;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const header = [
|
|
63
|
-
`# Auto-generated by sungen live-scan`,
|
|
64
|
-
`# Screen: ${result.screen} | Base URL: ${result.baseUrl}`,
|
|
65
|
-
`# Scanned: ${result.scannedAt}`,
|
|
66
|
-
`# Do not edit manually — re-run 'sungen live-scan' to regenerate`,
|
|
67
|
-
'',
|
|
68
|
-
].join('\n');
|
|
69
|
-
|
|
70
|
-
const yamlStr = yaml.stringify(output, {
|
|
71
|
-
lineWidth: 120,
|
|
72
|
-
defaultStringType: 'QUOTE_DOUBLE',
|
|
73
|
-
nullStr: 'null',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
fs.writeFileSync(outputPath, header + yamlStr, 'utf-8');
|
|
77
|
-
}
|