create-appraisejs 0.1.6 → 0.1.8-alpha
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 +45 -45
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/copy-template.d.ts +2 -1
- package/dist/copy-template.d.ts.map +1 -1
- package/dist/copy-template.js +10 -7
- package/dist/copy-template.js.map +1 -1
- package/dist/copy-template.test.js +19 -0
- package/dist/copy-template.test.js.map +1 -1
- package/package.json +69 -67
- package/templates/default/.vscode/settings.json +5 -0
- package/templates/default/appraisejs.config.json +1 -1
- package/templates/default/components.json +24 -24
- package/templates/default/cucumber.mjs +16 -0
- package/templates/default/eslint.config.mjs +15 -15
- package/templates/default/next.config.ts +13 -7
- package/templates/default/package-lock.json +13732 -14321
- package/templates/default/package.json +11 -9
- package/templates/default/postcss.config.mjs +8 -8
- package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
- package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
- package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
- package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
- package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
- package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
- package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
- package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
- package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
- package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
- package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
- package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
- package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
- package/templates/default/prisma/schema.prisma +554 -554
- package/templates/default/scripts/regenerate-features.ts +94 -94
- package/templates/default/scripts/setup-env.ts +19 -19
- package/templates/default/scripts/sync-all.ts +341 -341
- package/templates/default/scripts/sync-appraise-base-template.ts +52 -2
- package/templates/default/scripts/sync-environments.ts +323 -323
- package/templates/default/scripts/sync-locator-groups.ts +413 -413
- package/templates/default/scripts/sync-locators.ts +402 -402
- package/templates/default/scripts/sync-modules.ts +349 -349
- package/templates/default/scripts/sync-tags.ts +292 -292
- package/templates/default/scripts/sync-template-step-groups.ts +399 -399
- package/templates/default/scripts/sync-template-steps.ts +806 -806
- package/templates/default/scripts/sync-test-cases.ts +905 -905
- package/templates/default/scripts/sync-test-suites.ts +411 -411
- package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
- package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
- package/templates/default/src/actions/environments/environment-actions.ts +205 -205
- package/templates/default/src/actions/locator/locator-actions.ts +547 -547
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -344
- package/templates/default/src/actions/modules/module-actions.ts +133 -133
- package/templates/default/src/actions/reports/report-actions.ts +613 -613
- package/templates/default/src/actions/review/review-actions.ts +147 -147
- package/templates/default/src/actions/tags/tag-actions.ts +104 -104
- package/templates/default/src/actions/template-step/template-step-actions.ts +332 -332
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -278
- package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
- package/templates/default/src/actions/test-case/test-case-actions.ts +419 -419
- package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -1185
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -253
- package/templates/default/src/actions/user/user-actions.ts +13 -13
- package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
- package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
- package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
- package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
- package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
- package/templates/default/src/app/(base)/environments/page.tsx +59 -59
- package/templates/default/src/app/(base)/layout.tsx +10 -10
- package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
- package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
- package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
- package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
- package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
- package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -61
- package/templates/default/src/app/(base)/locators/create/page.tsx +38 -38
- package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
- package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -90
- package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
- package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -45
- package/templates/default/src/app/(base)/locators/page.tsx +65 -65
- package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
- package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
- package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
- package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
- package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
- package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
- package/templates/default/src/app/(base)/modules/page.tsx +59 -59
- package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -517
- package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
- package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
- package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -46
- package/templates/default/src/app/(base)/reports/page.tsx +98 -98
- package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -16
- package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
- package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
- package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -131
- package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -82
- package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -42
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
- package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
- package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -60
- package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
- package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
- package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
- package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
- package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
- package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
- package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
- package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
- package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
- package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
- package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
- package/templates/default/src/app/(base)/tags/page.tsx +58 -58
- package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
- package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
- package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
- package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
- package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
- package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
- package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
- package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
- package/templates/default/src/app/(base)/template-steps/page.tsx +59 -59
- package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
- package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
- package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
- package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
- package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
- package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
- package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
- package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
- package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
- package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
- package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
- package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
- package/templates/default/src/app/(base)/test-cases/page.tsx +60 -60
- package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
- package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
- package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -90
- package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -35
- package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -56
- package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -47
- package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
- package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +508 -512
- package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
- package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
- package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -45
- package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -55
- package/templates/default/src/app/(base)/test-suites/page.tsx +82 -82
- package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -269
- package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -97
- package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
- package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
- package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
- package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
- package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
- package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
- package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
- package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -133
- package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -420
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -146
- package/templates/default/src/app/globals.css +147 -147
- package/templates/default/src/app/layout.tsx +171 -171
- package/templates/default/src/app/page.tsx +64 -64
- package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
- package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
- package/templates/default/src/components/base-node.tsx +21 -21
- package/templates/default/src/components/chart/pie-chart.tsx +73 -73
- package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
- package/templates/default/src/components/data-state/empty-state.tsx +40 -40
- package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
- package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
- package/templates/default/src/components/devtools/providers.tsx +19 -13
- package/templates/default/src/components/diagram/button-edge.tsx +54 -54
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -438
- package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
- package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
- package/templates/default/src/components/diagram/node-form.tsx +262 -262
- package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
- package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
- package/templates/default/src/components/form/error-message.tsx +7 -7
- package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
- package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
- package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
- package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
- package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
- package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
- package/templates/default/src/components/logo.tsx +15 -15
- package/templates/default/src/components/navigation/command-badge.tsx +34 -34
- package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
- package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
- package/templates/default/src/components/navigation/nav-card.tsx +31 -31
- package/templates/default/src/components/navigation/nav-command.tsx +508 -508
- package/templates/default/src/components/navigation/nav-link.tsx +60 -60
- package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
- package/templates/default/src/components/node-header.tsx +159 -159
- package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -253
- package/templates/default/src/components/table/table-actions.tsx +172 -172
- package/templates/default/src/components/test-run/download-logs-button.tsx +99 -99
- package/templates/default/src/components/test-run/log-viewer.tsx +445 -445
- package/templates/default/src/components/test-run/test-run-details.tsx +611 -611
- package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
- package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
- package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
- package/templates/default/src/components/theme/theme-provider.tsx +8 -8
- package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
- package/templates/default/src/components/typography/page-header.tsx +7 -7
- package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
- package/templates/default/src/components/ui/alert.tsx +43 -43
- package/templates/default/src/components/ui/avatar.tsx +40 -40
- package/templates/default/src/components/ui/badge.tsx +29 -29
- package/templates/default/src/components/ui/button.tsx +47 -47
- package/templates/default/src/components/ui/calendar.tsx +158 -158
- package/templates/default/src/components/ui/card.tsx +43 -43
- package/templates/default/src/components/ui/checkbox.tsx +28 -28
- package/templates/default/src/components/ui/command.tsx +135 -135
- package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
- package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
- package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
- package/templates/default/src/components/ui/data-table.tsx +267 -267
- package/templates/default/src/components/ui/dialog.tsx +97 -97
- package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
- package/templates/default/src/components/ui/input.tsx +22 -22
- package/templates/default/src/components/ui/kbd.tsx +28 -28
- package/templates/default/src/components/ui/label.tsx +19 -19
- package/templates/default/src/components/ui/loading.tsx +12 -12
- package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
- package/templates/default/src/components/ui/multi-select.tsx +142 -142
- package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
- package/templates/default/src/components/ui/popover.tsx +33 -33
- package/templates/default/src/components/ui/progress.tsx +25 -25
- package/templates/default/src/components/ui/radio-group.tsx +44 -44
- package/templates/default/src/components/ui/scroll-area.tsx +40 -40
- package/templates/default/src/components/ui/select.tsx +151 -144
- package/templates/default/src/components/ui/separator.tsx +22 -22
- package/templates/default/src/components/ui/skeleton.tsx +7 -7
- package/templates/default/src/components/ui/table.tsx +76 -76
- package/templates/default/src/components/ui/tabs.tsx +55 -55
- package/templates/default/src/components/ui/textarea.tsx +21 -21
- package/templates/default/src/components/ui/toast.tsx +113 -113
- package/templates/default/src/components/ui/toaster.tsx +26 -26
- package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
- package/templates/default/src/config/db-config.ts +10 -10
- package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
- package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
- package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
- package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
- package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
- package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
- package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
- package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
- package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
- package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
- package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
- package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
- package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -31
- package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
- package/templates/default/src/hooks/use-toast.ts +187 -187
- package/templates/default/src/lib/bidirectional-sync.ts +432 -432
- package/templates/default/src/lib/database-sync.ts +531 -531
- package/templates/default/src/lib/environment-file-utils.ts +221 -221
- package/templates/default/src/lib/feature-file-generator.ts +411 -411
- package/templates/default/src/lib/gherkin-parser.ts +259 -259
- package/templates/default/src/lib/locator-group-file-utils.ts +370 -370
- package/templates/default/src/lib/metrics/metric-calculator.ts +613 -613
- package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
- package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
- package/templates/default/src/lib/test-case-utils.ts +6 -6
- package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
- package/templates/default/src/lib/test-run/process-manager.ts +191 -191
- package/templates/default/src/lib/test-run/report-parser.ts +316 -316
- package/templates/default/src/lib/test-run/test-run-executor.ts +144 -144
- package/templates/default/src/lib/test-run/winston-logger.ts +95 -95
- package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
- package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
- package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
- package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
- package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -167
- package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -723
- package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
- package/templates/default/src/lib/utils.ts +31 -31
- package/templates/default/src/tests/config/executor/world.ts +41 -41
- package/templates/default/src/tests/executor.ts +80 -80
- package/templates/default/src/tests/hooks/hooks.ts +99 -99
- package/templates/default/src/tests/mapping/locator-map.json +1 -1
- package/templates/default/src/tests/steps/actions/click.step.ts +62 -62
- package/templates/default/src/tests/steps/actions/navigation.step.ts +73 -72
- package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -34
- package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +24 -23
- package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -111
- package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -30
- package/templates/default/src/tests/support/parameter-types.ts +12 -12
- package/templates/default/src/tests/utils/cache.util.ts +260 -260
- package/templates/default/src/tests/utils/cli.util.ts +177 -177
- package/templates/default/src/tests/utils/environment.util.ts +65 -65
- package/templates/default/src/tests/utils/locator.util.ts +248 -248
- package/templates/default/src/tests/utils/random-data.util.ts +44 -44
- package/templates/default/src/tests/utils/spawner.util.ts +617 -617
- package/templates/default/src/types/diagram/diagram.ts +34 -34
- package/templates/default/src/types/diagram/template-step.ts +11 -11
- package/templates/default/src/types/executor/browser.type.ts +1 -1
- package/templates/default/src/types/form/actionHandler.ts +6 -6
- package/templates/default/src/types/locator/locator.type.ts +11 -11
- package/templates/default/src/types/step/step.type.ts +1 -1
- package/templates/default/src/types/table/data-table.ts +6 -6
- package/templates/default/tailwind.config.ts +62 -62
- package/templates/default/.env +0 -2
- package/templates/default/next-env.d.ts +0 -6
- package/templates/default/prisma/prisma/dev.db +0 -0
- package/templates/default/src/tests/config/environments/environments.json +0 -14
|
@@ -1,460 +1,460 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
4
|
-
import { Button } from '@/components/ui/button'
|
|
5
|
-
import { Input } from '@/components/ui/input'
|
|
6
|
-
import { Label } from '@/components/ui/label'
|
|
7
|
-
import { Separator } from '@/components/ui/separator'
|
|
8
|
-
import { Badge } from '@/components/ui/badge'
|
|
9
|
-
import { Textarea } from '@/components/ui/textarea'
|
|
10
|
-
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
11
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
12
|
-
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
13
|
-
import { Copy, Target, MousePointer, Info } from 'lucide-react'
|
|
14
|
-
import { useState, useEffect, useRef } from 'react'
|
|
15
|
-
|
|
16
|
-
interface SelectedElementDetails {
|
|
17
|
-
tag: string
|
|
18
|
-
id?: string
|
|
19
|
-
className?: string
|
|
20
|
-
name?: string
|
|
21
|
-
text?: string
|
|
22
|
-
cssPath: string
|
|
23
|
-
xpath: string
|
|
24
|
-
html: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface LocatorInspectorProps {
|
|
28
|
-
iframeUrl?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default function LocatorInspector({ iframeUrl = '/sample-page' }: LocatorInspectorProps) {
|
|
32
|
-
const [isSelectionMode, setIsSelectionMode] = useState(false)
|
|
33
|
-
const [selectedElementDetails, setSelectedElementDetails] = useState<SelectedElementDetails | undefined>()
|
|
34
|
-
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
35
|
-
|
|
36
|
-
// Injection script - optimized and cleaned up
|
|
37
|
-
const injectionScript = `
|
|
38
|
-
(function() {
|
|
39
|
-
if (window.locatorInspectorInjected) return;
|
|
40
|
-
window.locatorInspectorInjected = true;
|
|
41
|
-
|
|
42
|
-
let isSelectionMode = false;
|
|
43
|
-
let hoveredElement = null;
|
|
44
|
-
|
|
45
|
-
// Inject CSS for hover effects
|
|
46
|
-
const style = document.createElement('style');
|
|
47
|
-
style.textContent = \`
|
|
48
|
-
.locator-inspector-hover {
|
|
49
|
-
outline: 2px solid #3b82f6 !important;
|
|
50
|
-
background-color: rgba(59, 130, 246, 0.1) !important;
|
|
51
|
-
cursor: crosshair !important;
|
|
52
|
-
}
|
|
53
|
-
.locator-inspector-selection-mode * {
|
|
54
|
-
cursor: crosshair !important;
|
|
55
|
-
}
|
|
56
|
-
\`;
|
|
57
|
-
document.head.appendChild(style);
|
|
58
|
-
|
|
59
|
-
function handleElementClick(event) {
|
|
60
|
-
if (!isSelectionMode) return;
|
|
61
|
-
|
|
62
|
-
event.preventDefault();
|
|
63
|
-
event.stopPropagation();
|
|
64
|
-
|
|
65
|
-
const target = event.target;
|
|
66
|
-
|
|
67
|
-
// Send element data to parent
|
|
68
|
-
window.parent.postMessage({
|
|
69
|
-
type: 'ELEMENT_SELECTED',
|
|
70
|
-
elementData: {
|
|
71
|
-
tagName: target.tagName,
|
|
72
|
-
id: target.id,
|
|
73
|
-
className: target.className,
|
|
74
|
-
name: target.name,
|
|
75
|
-
textContent: target.textContent?.trim(),
|
|
76
|
-
outerHTML: target.outerHTML,
|
|
77
|
-
}
|
|
78
|
-
}, '*');
|
|
79
|
-
|
|
80
|
-
exitSelectionMode();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function handleElementHover(event) {
|
|
84
|
-
if (!isSelectionMode) return;
|
|
85
|
-
|
|
86
|
-
if (hoveredElement) {
|
|
87
|
-
hoveredElement.classList.remove('locator-inspector-hover');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
hoveredElement = event.target;
|
|
91
|
-
hoveredElement.classList.add('locator-inspector-hover');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function handleElementLeave(event) {
|
|
95
|
-
if (!isSelectionMode) return;
|
|
96
|
-
event.target.classList.remove('locator-inspector-hover');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function enterSelectionMode() {
|
|
100
|
-
isSelectionMode = true;
|
|
101
|
-
document.body.classList.add('locator-inspector-selection-mode');
|
|
102
|
-
document.addEventListener('click', handleElementClick, true);
|
|
103
|
-
document.addEventListener('mouseover', handleElementHover, true);
|
|
104
|
-
document.addEventListener('mouseout', handleElementLeave, true);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function exitSelectionMode() {
|
|
108
|
-
isSelectionMode = false;
|
|
109
|
-
document.body.classList.remove('locator-inspector-selection-mode');
|
|
110
|
-
document.removeEventListener('click', handleElementClick, true);
|
|
111
|
-
document.removeEventListener('mouseover', handleElementHover, true);
|
|
112
|
-
document.removeEventListener('mouseout', handleElementLeave, true);
|
|
113
|
-
|
|
114
|
-
// Clean up hover effects
|
|
115
|
-
document.querySelectorAll('.locator-inspector-hover').forEach(el => {
|
|
116
|
-
el.classList.remove('locator-inspector-hover');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
hoveredElement = null;
|
|
120
|
-
|
|
121
|
-
window.parent.postMessage({ type: 'SELECTION_MODE_OFF' }, '*');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Listen for toggle messages
|
|
125
|
-
window.addEventListener('message', function(event) {
|
|
126
|
-
if (event.data.type === 'TOGGLE_SELECTION_MODE') {
|
|
127
|
-
if (event.data.isSelectionMode) {
|
|
128
|
-
enterSelectionMode();
|
|
129
|
-
} else {
|
|
130
|
-
exitSelectionMode();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
})();
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
// Inject script when iframe loads
|
|
138
|
-
const handleIframeLoad = () => {
|
|
139
|
-
const iframe = iframeRef.current
|
|
140
|
-
if (!iframe?.contentWindow) return
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const script = iframe.contentDocument?.createElement('script')
|
|
144
|
-
if (script) {
|
|
145
|
-
script.textContent = injectionScript
|
|
146
|
-
iframe.contentDocument?.head.appendChild(script)
|
|
147
|
-
}
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.warn('Could not inject script into iframe:', error)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Generate CSS selector
|
|
154
|
-
const generateCSSPath = (element: { tagName: string; className?: string; id?: string }) => {
|
|
155
|
-
if (element.id) return `#${element.id}`
|
|
156
|
-
|
|
157
|
-
let path = element.tagName.toLowerCase()
|
|
158
|
-
if (element.className) {
|
|
159
|
-
const classes = element.className.split(' ').filter(Boolean)
|
|
160
|
-
if (classes.length > 0) {
|
|
161
|
-
path += '.' + classes.join('.')
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return path
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Generate XPath
|
|
168
|
-
const generateXPath = (element: { tagName: string; className?: string; id?: string }) => {
|
|
169
|
-
if (element.id) return `//*[@id="${element.id}"]`
|
|
170
|
-
|
|
171
|
-
let path = `//${element.tagName.toLowerCase()}`
|
|
172
|
-
if (element.className) {
|
|
173
|
-
path += `[@class="${element.className}"]`
|
|
174
|
-
}
|
|
175
|
-
return path
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Listen for messages from iframe
|
|
179
|
-
useEffect(() => {
|
|
180
|
-
const handleMessage = (event: MessageEvent) => {
|
|
181
|
-
if (event.data.type === 'SELECTION_MODE_OFF') {
|
|
182
|
-
setIsSelectionMode(false)
|
|
183
|
-
} else if (event.data.type === 'ELEMENT_SELECTED') {
|
|
184
|
-
const { elementData } = event.data
|
|
185
|
-
setSelectedElementDetails({
|
|
186
|
-
tag: elementData.tagName.toLowerCase(),
|
|
187
|
-
id: elementData.id || undefined,
|
|
188
|
-
className: elementData.className || undefined,
|
|
189
|
-
name: elementData.name || undefined,
|
|
190
|
-
text: elementData.textContent || undefined,
|
|
191
|
-
cssPath: generateCSSPath(elementData),
|
|
192
|
-
xpath: generateXPath(elementData),
|
|
193
|
-
html: elementData.outerHTML,
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
window.addEventListener('message', handleMessage)
|
|
199
|
-
return () => window.removeEventListener('message', handleMessage)
|
|
200
|
-
}, [])
|
|
201
|
-
|
|
202
|
-
const copyToClipboard = (text: string) => {
|
|
203
|
-
navigator.clipboard.writeText(text)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const toggleSelectionMode = () => {
|
|
207
|
-
const newSelectionMode = !isSelectionMode
|
|
208
|
-
setIsSelectionMode(newSelectionMode)
|
|
209
|
-
|
|
210
|
-
const iframe = iframeRef.current
|
|
211
|
-
if (iframe?.contentWindow) {
|
|
212
|
-
iframe.contentWindow.postMessage(
|
|
213
|
-
{
|
|
214
|
-
type: 'TOGGLE_SELECTION_MODE',
|
|
215
|
-
isSelectionMode: newSelectionMode,
|
|
216
|
-
},
|
|
217
|
-
'*',
|
|
218
|
-
)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Render property field with copy button
|
|
223
|
-
const renderPropertyField = (id: string, label: string, value: string, bgColor?: string, borderColor?: string) => (
|
|
224
|
-
<div className="space-y-2">
|
|
225
|
-
<Label htmlFor={id} className="text-sm font-medium">
|
|
226
|
-
{label}
|
|
227
|
-
</Label>
|
|
228
|
-
<div className="flex">
|
|
229
|
-
<Input
|
|
230
|
-
id={id}
|
|
231
|
-
value={value}
|
|
232
|
-
readOnly
|
|
233
|
-
className={`rounded-r-none border-r-0 font-mono text-sm ${bgColor || ''} ${borderColor || ''}`}
|
|
234
|
-
/>
|
|
235
|
-
<Tooltip>
|
|
236
|
-
<TooltipTrigger asChild>
|
|
237
|
-
<Button
|
|
238
|
-
size="sm"
|
|
239
|
-
variant="outline"
|
|
240
|
-
className="h-10 w-10 rounded-l-none border-l-0 p-0"
|
|
241
|
-
onClick={() => copyToClipboard(value)}
|
|
242
|
-
>
|
|
243
|
-
<Copy className="h-3 w-3" />
|
|
244
|
-
</Button>
|
|
245
|
-
</TooltipTrigger>
|
|
246
|
-
<TooltipContent>
|
|
247
|
-
<p>Copy {label.toLowerCase()}</p>
|
|
248
|
-
</TooltipContent>
|
|
249
|
-
</Tooltip>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
<TooltipProvider>
|
|
256
|
-
<div className="min-h-screen bg-gray-50">
|
|
257
|
-
{/* Header */}
|
|
258
|
-
<header className="border-b bg-background shadow-sm">
|
|
259
|
-
<div className="container mx-auto flex items-center justify-between px-6 py-4">
|
|
260
|
-
<div className="space-y-1">
|
|
261
|
-
<h1 className="text-2xl font-bold tracking-tight">Locator Explorer</h1>
|
|
262
|
-
<p className="text-sm text-muted-foreground">Inspect and generate locators for test automation</p>
|
|
263
|
-
</div>
|
|
264
|
-
<div className="flex items-center gap-3">
|
|
265
|
-
<Button
|
|
266
|
-
onClick={toggleSelectionMode}
|
|
267
|
-
variant={isSelectionMode ? 'default' : 'outline'}
|
|
268
|
-
size="sm"
|
|
269
|
-
className="flex items-center gap-2"
|
|
270
|
-
>
|
|
271
|
-
{isSelectionMode ? (
|
|
272
|
-
<>
|
|
273
|
-
<MousePointer className="h-4 w-4" />
|
|
274
|
-
Exit Selection
|
|
275
|
-
</>
|
|
276
|
-
) : (
|
|
277
|
-
<>
|
|
278
|
-
<Target className="h-4 w-4" />
|
|
279
|
-
Select Element
|
|
280
|
-
</>
|
|
281
|
-
)}
|
|
282
|
-
</Button>
|
|
283
|
-
{isSelectionMode && (
|
|
284
|
-
<Badge variant="secondary" className="flex items-center gap-2">
|
|
285
|
-
<div className="h-2 w-2 animate-pulse rounded-full bg-blue-500"></div>
|
|
286
|
-
Click an element to inspect
|
|
287
|
-
</Badge>
|
|
288
|
-
)}
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
</header>
|
|
292
|
-
|
|
293
|
-
{/* Main Content */}
|
|
294
|
-
<div className="flex h-[calc(100vh-80px)]">
|
|
295
|
-
{/* Left Column - Application Under Test */}
|
|
296
|
-
<div className="flex-1 p-6">
|
|
297
|
-
<Card className="h-full shadow-lg">
|
|
298
|
-
<CardHeader className="pb-3">
|
|
299
|
-
<CardTitle className="text-lg font-semibold text-gray-800">Application Under Test</CardTitle>
|
|
300
|
-
</CardHeader>
|
|
301
|
-
<CardContent className="p-0">
|
|
302
|
-
<div className="h-[80vh] overflow-hidden rounded-b-lg bg-gray-100">
|
|
303
|
-
<iframe
|
|
304
|
-
ref={iframeRef}
|
|
305
|
-
src={iframeUrl}
|
|
306
|
-
className="h-full w-full border-0"
|
|
307
|
-
title="Application Under Test"
|
|
308
|
-
sandbox="allow-same-origin allow-scripts allow-forms"
|
|
309
|
-
onLoad={handleIframeLoad}
|
|
310
|
-
/>
|
|
311
|
-
</div>
|
|
312
|
-
</CardContent>
|
|
313
|
-
</Card>
|
|
314
|
-
</div>
|
|
315
|
-
|
|
316
|
-
{/* Right Column - Inspector Sidebar */}
|
|
317
|
-
<div className="w-96 p-6 pl-0">
|
|
318
|
-
<Card className="h-full shadow-lg">
|
|
319
|
-
<CardHeader className="pb-3">
|
|
320
|
-
<CardTitle className="flex items-center gap-2 text-lg font-semibold">
|
|
321
|
-
<span className="truncate">Selected Element</span>
|
|
322
|
-
{selectedElementDetails && (
|
|
323
|
-
<Badge variant="outline" className="shrink-0 font-mono text-xs">
|
|
324
|
-
{selectedElementDetails.tag.toUpperCase()}
|
|
325
|
-
</Badge>
|
|
326
|
-
)}
|
|
327
|
-
</CardTitle>
|
|
328
|
-
</CardHeader>
|
|
329
|
-
<CardContent className="p-0">
|
|
330
|
-
<ScrollArea className="h-[calc(80vh-60px)] p-6">
|
|
331
|
-
{selectedElementDetails ? (
|
|
332
|
-
<div className="space-y-6">
|
|
333
|
-
{/* Element Properties */}
|
|
334
|
-
<div className="space-y-4">
|
|
335
|
-
<h3 className="text-sm font-medium uppercase tracking-wide text-muted-foreground">
|
|
336
|
-
Properties
|
|
337
|
-
</h3>
|
|
338
|
-
<div className="space-y-3">
|
|
339
|
-
{renderPropertyField(
|
|
340
|
-
'tag',
|
|
341
|
-
'Tag Name',
|
|
342
|
-
selectedElementDetails.tag,
|
|
343
|
-
'bg-blue-50',
|
|
344
|
-
'border-blue-200',
|
|
345
|
-
)}
|
|
346
|
-
{selectedElementDetails.id && renderPropertyField('id', 'ID', selectedElementDetails.id)}
|
|
347
|
-
{selectedElementDetails.className &&
|
|
348
|
-
renderPropertyField('class', 'Class', selectedElementDetails.className)}
|
|
349
|
-
{selectedElementDetails.name &&
|
|
350
|
-
renderPropertyField('name', 'Name', selectedElementDetails.name)}
|
|
351
|
-
{selectedElementDetails.text &&
|
|
352
|
-
renderPropertyField('text', 'Text Content', selectedElementDetails.text)}
|
|
353
|
-
</div>
|
|
354
|
-
</div>
|
|
355
|
-
|
|
356
|
-
<Separator />
|
|
357
|
-
|
|
358
|
-
{/* Selectors */}
|
|
359
|
-
<div className="space-y-4">
|
|
360
|
-
<h3 className="text-sm font-medium uppercase tracking-wide text-muted-foreground">Locators</h3>
|
|
361
|
-
<div className="space-y-3">
|
|
362
|
-
<div className="space-y-2">
|
|
363
|
-
<Label htmlFor="css" className="flex flex-wrap items-center gap-2 text-sm font-medium">
|
|
364
|
-
<span>CSS Selector</span>
|
|
365
|
-
<Badge variant="secondary" className="shrink-0 text-xs">
|
|
366
|
-
Recommended
|
|
367
|
-
</Badge>
|
|
368
|
-
</Label>
|
|
369
|
-
<div className="flex">
|
|
370
|
-
<Input
|
|
371
|
-
id="css"
|
|
372
|
-
value={selectedElementDetails.cssPath}
|
|
373
|
-
readOnly
|
|
374
|
-
className="rounded-r-none border-r-0 border-green-200 bg-green-50 font-mono text-sm"
|
|
375
|
-
/>
|
|
376
|
-
<Tooltip>
|
|
377
|
-
<TooltipTrigger asChild>
|
|
378
|
-
<Button
|
|
379
|
-
size="sm"
|
|
380
|
-
variant="outline"
|
|
381
|
-
className="h-10 w-10 rounded-l-none border-l-0 p-0"
|
|
382
|
-
onClick={() => copyToClipboard(selectedElementDetails.cssPath)}
|
|
383
|
-
>
|
|
384
|
-
<Copy className="h-3 w-3" />
|
|
385
|
-
</Button>
|
|
386
|
-
</TooltipTrigger>
|
|
387
|
-
<TooltipContent>
|
|
388
|
-
<p>Copy CSS selector</p>
|
|
389
|
-
</TooltipContent>
|
|
390
|
-
</Tooltip>
|
|
391
|
-
</div>
|
|
392
|
-
</div>
|
|
393
|
-
|
|
394
|
-
{renderPropertyField(
|
|
395
|
-
'xpath',
|
|
396
|
-
'XPath',
|
|
397
|
-
selectedElementDetails.xpath,
|
|
398
|
-
'bg-yellow-50',
|
|
399
|
-
'border-yellow-200',
|
|
400
|
-
)}
|
|
401
|
-
</div>
|
|
402
|
-
</div>
|
|
403
|
-
|
|
404
|
-
<Separator />
|
|
405
|
-
|
|
406
|
-
{/* HTML Snippet */}
|
|
407
|
-
<div className="space-y-3">
|
|
408
|
-
<Label className="text-sm font-medium">HTML Snippet</Label>
|
|
409
|
-
<div className="flex">
|
|
410
|
-
<Textarea
|
|
411
|
-
value={selectedElementDetails.html}
|
|
412
|
-
readOnly
|
|
413
|
-
className="min-h-[120px] flex-1 resize-none rounded-r-none border-r-0 font-mono text-xs"
|
|
414
|
-
/>
|
|
415
|
-
<Tooltip>
|
|
416
|
-
<TooltipTrigger asChild>
|
|
417
|
-
<Button
|
|
418
|
-
size="sm"
|
|
419
|
-
variant="outline"
|
|
420
|
-
className="w-10 self-start rounded-l-none border-l-0"
|
|
421
|
-
style={{ height: '120px' }}
|
|
422
|
-
onClick={() => copyToClipboard(selectedElementDetails.html)}
|
|
423
|
-
>
|
|
424
|
-
<Copy className="h-3 w-3" />
|
|
425
|
-
</Button>
|
|
426
|
-
</TooltipTrigger>
|
|
427
|
-
<TooltipContent>
|
|
428
|
-
<p>Copy HTML</p>
|
|
429
|
-
</TooltipContent>
|
|
430
|
-
</Tooltip>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
</div>
|
|
434
|
-
) : (
|
|
435
|
-
<div className="flex h-full flex-col items-center justify-center p-8 text-center">
|
|
436
|
-
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
|
437
|
-
<Target className="h-8 w-8 text-muted-foreground" />
|
|
438
|
-
</div>
|
|
439
|
-
<h3 className="mb-2 text-lg font-semibold">No Element Selected</h3>
|
|
440
|
-
<p className="mb-4 max-w-sm break-words text-sm text-muted-foreground">
|
|
441
|
-
Click the "Select Element" button above, then click on any element in the application
|
|
442
|
-
to inspect its properties.
|
|
443
|
-
</p>
|
|
444
|
-
<Alert className="max-w-sm">
|
|
445
|
-
<Info className="h-4 w-4" />
|
|
446
|
-
<AlertDescription className="break-words">
|
|
447
|
-
Use the element selector to generate reliable locators for your test automation scripts.
|
|
448
|
-
</AlertDescription>
|
|
449
|
-
</Alert>
|
|
450
|
-
</div>
|
|
451
|
-
)}
|
|
452
|
-
</ScrollArea>
|
|
453
|
-
</CardContent>
|
|
454
|
-
</Card>
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
</TooltipProvider>
|
|
459
|
-
)
|
|
460
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import { Label } from '@/components/ui/label'
|
|
7
|
+
import { Separator } from '@/components/ui/separator'
|
|
8
|
+
import { Badge } from '@/components/ui/badge'
|
|
9
|
+
import { Textarea } from '@/components/ui/textarea'
|
|
10
|
+
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
12
|
+
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
13
|
+
import { Copy, Target, MousePointer, Info } from 'lucide-react'
|
|
14
|
+
import { useState, useEffect, useRef } from 'react'
|
|
15
|
+
|
|
16
|
+
interface SelectedElementDetails {
|
|
17
|
+
tag: string
|
|
18
|
+
id?: string
|
|
19
|
+
className?: string
|
|
20
|
+
name?: string
|
|
21
|
+
text?: string
|
|
22
|
+
cssPath: string
|
|
23
|
+
xpath: string
|
|
24
|
+
html: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface LocatorInspectorProps {
|
|
28
|
+
iframeUrl?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function LocatorInspector({ iframeUrl = '/sample-page' }: LocatorInspectorProps) {
|
|
32
|
+
const [isSelectionMode, setIsSelectionMode] = useState(false)
|
|
33
|
+
const [selectedElementDetails, setSelectedElementDetails] = useState<SelectedElementDetails | undefined>()
|
|
34
|
+
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
35
|
+
|
|
36
|
+
// Injection script - optimized and cleaned up
|
|
37
|
+
const injectionScript = `
|
|
38
|
+
(function() {
|
|
39
|
+
if (window.locatorInspectorInjected) return;
|
|
40
|
+
window.locatorInspectorInjected = true;
|
|
41
|
+
|
|
42
|
+
let isSelectionMode = false;
|
|
43
|
+
let hoveredElement = null;
|
|
44
|
+
|
|
45
|
+
// Inject CSS for hover effects
|
|
46
|
+
const style = document.createElement('style');
|
|
47
|
+
style.textContent = \`
|
|
48
|
+
.locator-inspector-hover {
|
|
49
|
+
outline: 2px solid #3b82f6 !important;
|
|
50
|
+
background-color: rgba(59, 130, 246, 0.1) !important;
|
|
51
|
+
cursor: crosshair !important;
|
|
52
|
+
}
|
|
53
|
+
.locator-inspector-selection-mode * {
|
|
54
|
+
cursor: crosshair !important;
|
|
55
|
+
}
|
|
56
|
+
\`;
|
|
57
|
+
document.head.appendChild(style);
|
|
58
|
+
|
|
59
|
+
function handleElementClick(event) {
|
|
60
|
+
if (!isSelectionMode) return;
|
|
61
|
+
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
|
|
65
|
+
const target = event.target;
|
|
66
|
+
|
|
67
|
+
// Send element data to parent
|
|
68
|
+
window.parent.postMessage({
|
|
69
|
+
type: 'ELEMENT_SELECTED',
|
|
70
|
+
elementData: {
|
|
71
|
+
tagName: target.tagName,
|
|
72
|
+
id: target.id,
|
|
73
|
+
className: target.className,
|
|
74
|
+
name: target.name,
|
|
75
|
+
textContent: target.textContent?.trim(),
|
|
76
|
+
outerHTML: target.outerHTML,
|
|
77
|
+
}
|
|
78
|
+
}, '*');
|
|
79
|
+
|
|
80
|
+
exitSelectionMode();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleElementHover(event) {
|
|
84
|
+
if (!isSelectionMode) return;
|
|
85
|
+
|
|
86
|
+
if (hoveredElement) {
|
|
87
|
+
hoveredElement.classList.remove('locator-inspector-hover');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
hoveredElement = event.target;
|
|
91
|
+
hoveredElement.classList.add('locator-inspector-hover');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleElementLeave(event) {
|
|
95
|
+
if (!isSelectionMode) return;
|
|
96
|
+
event.target.classList.remove('locator-inspector-hover');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function enterSelectionMode() {
|
|
100
|
+
isSelectionMode = true;
|
|
101
|
+
document.body.classList.add('locator-inspector-selection-mode');
|
|
102
|
+
document.addEventListener('click', handleElementClick, true);
|
|
103
|
+
document.addEventListener('mouseover', handleElementHover, true);
|
|
104
|
+
document.addEventListener('mouseout', handleElementLeave, true);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function exitSelectionMode() {
|
|
108
|
+
isSelectionMode = false;
|
|
109
|
+
document.body.classList.remove('locator-inspector-selection-mode');
|
|
110
|
+
document.removeEventListener('click', handleElementClick, true);
|
|
111
|
+
document.removeEventListener('mouseover', handleElementHover, true);
|
|
112
|
+
document.removeEventListener('mouseout', handleElementLeave, true);
|
|
113
|
+
|
|
114
|
+
// Clean up hover effects
|
|
115
|
+
document.querySelectorAll('.locator-inspector-hover').forEach(el => {
|
|
116
|
+
el.classList.remove('locator-inspector-hover');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
hoveredElement = null;
|
|
120
|
+
|
|
121
|
+
window.parent.postMessage({ type: 'SELECTION_MODE_OFF' }, '*');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Listen for toggle messages
|
|
125
|
+
window.addEventListener('message', function(event) {
|
|
126
|
+
if (event.data.type === 'TOGGLE_SELECTION_MODE') {
|
|
127
|
+
if (event.data.isSelectionMode) {
|
|
128
|
+
enterSelectionMode();
|
|
129
|
+
} else {
|
|
130
|
+
exitSelectionMode();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
})();
|
|
135
|
+
`
|
|
136
|
+
|
|
137
|
+
// Inject script when iframe loads
|
|
138
|
+
const handleIframeLoad = () => {
|
|
139
|
+
const iframe = iframeRef.current
|
|
140
|
+
if (!iframe?.contentWindow) return
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const script = iframe.contentDocument?.createElement('script')
|
|
144
|
+
if (script) {
|
|
145
|
+
script.textContent = injectionScript
|
|
146
|
+
iframe.contentDocument?.head.appendChild(script)
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn('Could not inject script into iframe:', error)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Generate CSS selector
|
|
154
|
+
const generateCSSPath = (element: { tagName: string; className?: string; id?: string }) => {
|
|
155
|
+
if (element.id) return `#${element.id}`
|
|
156
|
+
|
|
157
|
+
let path = element.tagName.toLowerCase()
|
|
158
|
+
if (element.className) {
|
|
159
|
+
const classes = element.className.split(' ').filter(Boolean)
|
|
160
|
+
if (classes.length > 0) {
|
|
161
|
+
path += '.' + classes.join('.')
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return path
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Generate XPath
|
|
168
|
+
const generateXPath = (element: { tagName: string; className?: string; id?: string }) => {
|
|
169
|
+
if (element.id) return `//*[@id="${element.id}"]`
|
|
170
|
+
|
|
171
|
+
let path = `//${element.tagName.toLowerCase()}`
|
|
172
|
+
if (element.className) {
|
|
173
|
+
path += `[@class="${element.className}"]`
|
|
174
|
+
}
|
|
175
|
+
return path
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Listen for messages from iframe
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const handleMessage = (event: MessageEvent) => {
|
|
181
|
+
if (event.data.type === 'SELECTION_MODE_OFF') {
|
|
182
|
+
setIsSelectionMode(false)
|
|
183
|
+
} else if (event.data.type === 'ELEMENT_SELECTED') {
|
|
184
|
+
const { elementData } = event.data
|
|
185
|
+
setSelectedElementDetails({
|
|
186
|
+
tag: elementData.tagName.toLowerCase(),
|
|
187
|
+
id: elementData.id || undefined,
|
|
188
|
+
className: elementData.className || undefined,
|
|
189
|
+
name: elementData.name || undefined,
|
|
190
|
+
text: elementData.textContent || undefined,
|
|
191
|
+
cssPath: generateCSSPath(elementData),
|
|
192
|
+
xpath: generateXPath(elementData),
|
|
193
|
+
html: elementData.outerHTML,
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
window.addEventListener('message', handleMessage)
|
|
199
|
+
return () => window.removeEventListener('message', handleMessage)
|
|
200
|
+
}, [])
|
|
201
|
+
|
|
202
|
+
const copyToClipboard = (text: string) => {
|
|
203
|
+
navigator.clipboard.writeText(text)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const toggleSelectionMode = () => {
|
|
207
|
+
const newSelectionMode = !isSelectionMode
|
|
208
|
+
setIsSelectionMode(newSelectionMode)
|
|
209
|
+
|
|
210
|
+
const iframe = iframeRef.current
|
|
211
|
+
if (iframe?.contentWindow) {
|
|
212
|
+
iframe.contentWindow.postMessage(
|
|
213
|
+
{
|
|
214
|
+
type: 'TOGGLE_SELECTION_MODE',
|
|
215
|
+
isSelectionMode: newSelectionMode,
|
|
216
|
+
},
|
|
217
|
+
'*',
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Render property field with copy button
|
|
223
|
+
const renderPropertyField = (id: string, label: string, value: string, bgColor?: string, borderColor?: string) => (
|
|
224
|
+
<div className="space-y-2">
|
|
225
|
+
<Label htmlFor={id} className="text-sm font-medium">
|
|
226
|
+
{label}
|
|
227
|
+
</Label>
|
|
228
|
+
<div className="flex">
|
|
229
|
+
<Input
|
|
230
|
+
id={id}
|
|
231
|
+
value={value}
|
|
232
|
+
readOnly
|
|
233
|
+
className={`rounded-r-none border-r-0 font-mono text-sm ${bgColor || ''} ${borderColor || ''}`}
|
|
234
|
+
/>
|
|
235
|
+
<Tooltip>
|
|
236
|
+
<TooltipTrigger asChild>
|
|
237
|
+
<Button
|
|
238
|
+
size="sm"
|
|
239
|
+
variant="outline"
|
|
240
|
+
className="h-10 w-10 rounded-l-none border-l-0 p-0"
|
|
241
|
+
onClick={() => copyToClipboard(value)}
|
|
242
|
+
>
|
|
243
|
+
<Copy className="h-3 w-3" />
|
|
244
|
+
</Button>
|
|
245
|
+
</TooltipTrigger>
|
|
246
|
+
<TooltipContent>
|
|
247
|
+
<p>Copy {label.toLowerCase()}</p>
|
|
248
|
+
</TooltipContent>
|
|
249
|
+
</Tooltip>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<TooltipProvider>
|
|
256
|
+
<div className="min-h-screen bg-gray-50">
|
|
257
|
+
{/* Header */}
|
|
258
|
+
<header className="border-b bg-background shadow-sm">
|
|
259
|
+
<div className="container mx-auto flex items-center justify-between px-6 py-4">
|
|
260
|
+
<div className="space-y-1">
|
|
261
|
+
<h1 className="text-2xl font-bold tracking-tight">Locator Explorer</h1>
|
|
262
|
+
<p className="text-sm text-muted-foreground">Inspect and generate locators for test automation</p>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex items-center gap-3">
|
|
265
|
+
<Button
|
|
266
|
+
onClick={toggleSelectionMode}
|
|
267
|
+
variant={isSelectionMode ? 'default' : 'outline'}
|
|
268
|
+
size="sm"
|
|
269
|
+
className="flex items-center gap-2"
|
|
270
|
+
>
|
|
271
|
+
{isSelectionMode ? (
|
|
272
|
+
<>
|
|
273
|
+
<MousePointer className="h-4 w-4" />
|
|
274
|
+
Exit Selection
|
|
275
|
+
</>
|
|
276
|
+
) : (
|
|
277
|
+
<>
|
|
278
|
+
<Target className="h-4 w-4" />
|
|
279
|
+
Select Element
|
|
280
|
+
</>
|
|
281
|
+
)}
|
|
282
|
+
</Button>
|
|
283
|
+
{isSelectionMode && (
|
|
284
|
+
<Badge variant="secondary" className="flex items-center gap-2">
|
|
285
|
+
<div className="h-2 w-2 animate-pulse rounded-full bg-blue-500"></div>
|
|
286
|
+
Click an element to inspect
|
|
287
|
+
</Badge>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
</header>
|
|
292
|
+
|
|
293
|
+
{/* Main Content */}
|
|
294
|
+
<div className="flex h-[calc(100vh-80px)]">
|
|
295
|
+
{/* Left Column - Application Under Test */}
|
|
296
|
+
<div className="flex-1 p-6">
|
|
297
|
+
<Card className="h-full shadow-lg">
|
|
298
|
+
<CardHeader className="pb-3">
|
|
299
|
+
<CardTitle className="text-lg font-semibold text-gray-800">Application Under Test</CardTitle>
|
|
300
|
+
</CardHeader>
|
|
301
|
+
<CardContent className="p-0">
|
|
302
|
+
<div className="h-[80vh] overflow-hidden rounded-b-lg bg-gray-100">
|
|
303
|
+
<iframe
|
|
304
|
+
ref={iframeRef}
|
|
305
|
+
src={iframeUrl}
|
|
306
|
+
className="h-full w-full border-0"
|
|
307
|
+
title="Application Under Test"
|
|
308
|
+
sandbox="allow-same-origin allow-scripts allow-forms"
|
|
309
|
+
onLoad={handleIframeLoad}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
</CardContent>
|
|
313
|
+
</Card>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
{/* Right Column - Inspector Sidebar */}
|
|
317
|
+
<div className="w-96 p-6 pl-0">
|
|
318
|
+
<Card className="h-full shadow-lg">
|
|
319
|
+
<CardHeader className="pb-3">
|
|
320
|
+
<CardTitle className="flex items-center gap-2 text-lg font-semibold">
|
|
321
|
+
<span className="truncate">Selected Element</span>
|
|
322
|
+
{selectedElementDetails && (
|
|
323
|
+
<Badge variant="outline" className="shrink-0 font-mono text-xs">
|
|
324
|
+
{selectedElementDetails.tag.toUpperCase()}
|
|
325
|
+
</Badge>
|
|
326
|
+
)}
|
|
327
|
+
</CardTitle>
|
|
328
|
+
</CardHeader>
|
|
329
|
+
<CardContent className="p-0">
|
|
330
|
+
<ScrollArea className="h-[calc(80vh-60px)] p-6">
|
|
331
|
+
{selectedElementDetails ? (
|
|
332
|
+
<div className="space-y-6">
|
|
333
|
+
{/* Element Properties */}
|
|
334
|
+
<div className="space-y-4">
|
|
335
|
+
<h3 className="text-sm font-medium uppercase tracking-wide text-muted-foreground">
|
|
336
|
+
Properties
|
|
337
|
+
</h3>
|
|
338
|
+
<div className="space-y-3">
|
|
339
|
+
{renderPropertyField(
|
|
340
|
+
'tag',
|
|
341
|
+
'Tag Name',
|
|
342
|
+
selectedElementDetails.tag,
|
|
343
|
+
'bg-blue-50',
|
|
344
|
+
'border-blue-200',
|
|
345
|
+
)}
|
|
346
|
+
{selectedElementDetails.id && renderPropertyField('id', 'ID', selectedElementDetails.id)}
|
|
347
|
+
{selectedElementDetails.className &&
|
|
348
|
+
renderPropertyField('class', 'Class', selectedElementDetails.className)}
|
|
349
|
+
{selectedElementDetails.name &&
|
|
350
|
+
renderPropertyField('name', 'Name', selectedElementDetails.name)}
|
|
351
|
+
{selectedElementDetails.text &&
|
|
352
|
+
renderPropertyField('text', 'Text Content', selectedElementDetails.text)}
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<Separator />
|
|
357
|
+
|
|
358
|
+
{/* Selectors */}
|
|
359
|
+
<div className="space-y-4">
|
|
360
|
+
<h3 className="text-sm font-medium uppercase tracking-wide text-muted-foreground">Locators</h3>
|
|
361
|
+
<div className="space-y-3">
|
|
362
|
+
<div className="space-y-2">
|
|
363
|
+
<Label htmlFor="css" className="flex flex-wrap items-center gap-2 text-sm font-medium">
|
|
364
|
+
<span>CSS Selector</span>
|
|
365
|
+
<Badge variant="secondary" className="shrink-0 text-xs">
|
|
366
|
+
Recommended
|
|
367
|
+
</Badge>
|
|
368
|
+
</Label>
|
|
369
|
+
<div className="flex">
|
|
370
|
+
<Input
|
|
371
|
+
id="css"
|
|
372
|
+
value={selectedElementDetails.cssPath}
|
|
373
|
+
readOnly
|
|
374
|
+
className="rounded-r-none border-r-0 border-green-200 bg-green-50 font-mono text-sm"
|
|
375
|
+
/>
|
|
376
|
+
<Tooltip>
|
|
377
|
+
<TooltipTrigger asChild>
|
|
378
|
+
<Button
|
|
379
|
+
size="sm"
|
|
380
|
+
variant="outline"
|
|
381
|
+
className="h-10 w-10 rounded-l-none border-l-0 p-0"
|
|
382
|
+
onClick={() => copyToClipboard(selectedElementDetails.cssPath)}
|
|
383
|
+
>
|
|
384
|
+
<Copy className="h-3 w-3" />
|
|
385
|
+
</Button>
|
|
386
|
+
</TooltipTrigger>
|
|
387
|
+
<TooltipContent>
|
|
388
|
+
<p>Copy CSS selector</p>
|
|
389
|
+
</TooltipContent>
|
|
390
|
+
</Tooltip>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
{renderPropertyField(
|
|
395
|
+
'xpath',
|
|
396
|
+
'XPath',
|
|
397
|
+
selectedElementDetails.xpath,
|
|
398
|
+
'bg-yellow-50',
|
|
399
|
+
'border-yellow-200',
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<Separator />
|
|
405
|
+
|
|
406
|
+
{/* HTML Snippet */}
|
|
407
|
+
<div className="space-y-3">
|
|
408
|
+
<Label className="text-sm font-medium">HTML Snippet</Label>
|
|
409
|
+
<div className="flex">
|
|
410
|
+
<Textarea
|
|
411
|
+
value={selectedElementDetails.html}
|
|
412
|
+
readOnly
|
|
413
|
+
className="min-h-[120px] flex-1 resize-none rounded-r-none border-r-0 font-mono text-xs"
|
|
414
|
+
/>
|
|
415
|
+
<Tooltip>
|
|
416
|
+
<TooltipTrigger asChild>
|
|
417
|
+
<Button
|
|
418
|
+
size="sm"
|
|
419
|
+
variant="outline"
|
|
420
|
+
className="w-10 self-start rounded-l-none border-l-0"
|
|
421
|
+
style={{ height: '120px' }}
|
|
422
|
+
onClick={() => copyToClipboard(selectedElementDetails.html)}
|
|
423
|
+
>
|
|
424
|
+
<Copy className="h-3 w-3" />
|
|
425
|
+
</Button>
|
|
426
|
+
</TooltipTrigger>
|
|
427
|
+
<TooltipContent>
|
|
428
|
+
<p>Copy HTML</p>
|
|
429
|
+
</TooltipContent>
|
|
430
|
+
</Tooltip>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
) : (
|
|
435
|
+
<div className="flex h-full flex-col items-center justify-center p-8 text-center">
|
|
436
|
+
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
|
437
|
+
<Target className="h-8 w-8 text-muted-foreground" />
|
|
438
|
+
</div>
|
|
439
|
+
<h3 className="mb-2 text-lg font-semibold">No Element Selected</h3>
|
|
440
|
+
<p className="mb-4 max-w-sm break-words text-sm text-muted-foreground">
|
|
441
|
+
Click the "Select Element" button above, then click on any element in the application
|
|
442
|
+
to inspect its properties.
|
|
443
|
+
</p>
|
|
444
|
+
<Alert className="max-w-sm">
|
|
445
|
+
<Info className="h-4 w-4" />
|
|
446
|
+
<AlertDescription className="break-words">
|
|
447
|
+
Use the element selector to generate reliable locators for your test automation scripts.
|
|
448
|
+
</AlertDescription>
|
|
449
|
+
</Alert>
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
452
|
+
</ScrollArea>
|
|
453
|
+
</CardContent>
|
|
454
|
+
</Card>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</TooltipProvider>
|
|
459
|
+
)
|
|
460
|
+
}
|