create-appraisejs 0.2.0 → 0.3.0-alpha.0
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 +12 -1
- package/package.json +1 -1
- package/templates/default/.appraise-template-meta.json +2 -2
- package/templates/default/.env.example +2 -2
- package/templates/default/README.md +57 -53
- package/templates/default/automation/steps/actions/click.step.ts +58 -58
- package/templates/default/automation/steps/actions/hover.step.ts +27 -27
- package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
- package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
- package/templates/default/automation/steps/actions/store.step.ts +86 -86
- package/templates/default/automation/steps/actions/wait.step.ts +110 -90
- package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
- package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
- package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
- package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
- package/templates/default/components.json +24 -24
- package/templates/default/cucumber.mjs +16 -16
- package/templates/default/eslint.config.mjs +20 -16
- package/templates/default/next-env.d.ts +6 -6
- package/templates/default/next.config.ts +20 -11
- package/templates/default/package-lock.json +1775 -74
- package/templates/default/package.json +8 -1
- package/templates/default/packages/cucumber-runtime/package.json +13 -13
- package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
- package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
- package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
- package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
- package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
- package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
- package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
- package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
- package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
- package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
- package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
- package/templates/default/postcss.config.mjs +8 -8
- package/templates/default/prisma/dev.db +0 -0
- 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/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
- package/templates/default/scripts/build-step-registry.ts +33 -0
- package/templates/default/scripts/install-playwright.ts +17 -8
- package/templates/default/scripts/install-template-step.ts +128 -0
- package/templates/default/scripts/lib/filename-utils.test.ts +24 -0
- package/templates/default/scripts/lib/filename-utils.ts +24 -0
- package/templates/default/scripts/lib/jsdoc-parser.test.ts +63 -0
- package/templates/default/scripts/lib/jsdoc-parser.ts +88 -0
- package/templates/default/scripts/lib/step-file-parser.test.ts +71 -0
- package/templates/default/scripts/lib/step-file-parser.ts +315 -0
- package/templates/default/scripts/lib/step-matcher.test.ts +86 -0
- package/templates/default/scripts/lib/step-matcher.ts +120 -0
- package/templates/default/scripts/lib/sync-script-runner.ts +23 -0
- package/templates/default/scripts/lib/sync-summary.ts +29 -0
- package/templates/default/scripts/lib/tag-parsing.test.ts +20 -0
- package/templates/default/scripts/lib/tag-parsing.ts +10 -0
- package/templates/default/scripts/lib/template-step-installer.test.ts +225 -0
- package/templates/default/scripts/lib/template-step-installer.ts +404 -0
- package/templates/default/scripts/lib/template-step-registry.test.ts +118 -0
- package/templates/default/scripts/lib/template-step-registry.ts +137 -0
- package/templates/default/scripts/protect-seeded-files.ts +51 -38
- package/templates/default/scripts/regenerate-features.ts +98 -94
- package/templates/default/scripts/run-vitest.ts +59 -0
- package/templates/default/scripts/setup-env.ts +26 -19
- package/templates/default/scripts/sync-all.ts +44 -54
- package/templates/default/scripts/sync-appraise-base-template.ts +44 -16
- package/templates/default/scripts/sync-environments.ts +22 -66
- package/templates/default/scripts/sync-locator-groups.ts +358 -410
- package/templates/default/scripts/sync-locators.ts +348 -398
- package/templates/default/scripts/sync-modules.ts +302 -341
- package/templates/default/scripts/sync-tags.ts +29 -65
- package/templates/default/scripts/sync-template-step-groups.ts +24 -182
- package/templates/default/scripts/sync-template-steps.ts +36 -493
- package/templates/default/scripts/sync-test-cases.ts +296 -539
- package/templates/default/scripts/sync-test-suites.ts +32 -79
- package/templates/default/src/actions/dashboard/dashboard-actions.ts +70 -241
- package/templates/default/src/actions/environments/environment-actions.ts +102 -188
- package/templates/default/src/actions/locator/locator-actions.ts +77 -490
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +34 -212
- package/templates/default/src/actions/locator-picker/locator-picker-actions.test.ts +81 -0
- package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +20 -161
- package/templates/default/src/actions/modules/module-actions.ts +99 -135
- package/templates/default/src/actions/reports/report-actions.ts +28 -565
- package/templates/default/src/actions/settings/sync-actions.test.ts +58 -0
- package/templates/default/src/actions/tags/tag-actions.ts +99 -107
- package/templates/default/src/actions/template-step/template-step-actions.ts +33 -194
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +35 -92
- package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +98 -238
- package/templates/default/src/actions/test-case/test-case-actions.ts +108 -356
- package/templates/default/src/actions/test-run/test-run-actions.ts +74 -1081
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +35 -202
- package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
- package/templates/default/src/app/(base)/environments/environment-form.test.tsx +92 -0
- package/templates/default/src/app/(base)/environments/environment-form.tsx +228 -219
- package/templates/default/src/app/(base)/environments/environment-helpers.ts +58 -0
- package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
- package/templates/default/src/app/(base)/environments/environment-table.tsx +25 -24
- package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +49 -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)/locators/create/create-locator-workspace-helpers.test.ts +71 -0
- package/templates/default/src/app/(base)/locators/create/create-locator-workspace-helpers.ts +333 -0
- package/templates/default/src/app/(base)/locators/create/create-locator-workspace.test.tsx +125 -0
- package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +56 -274
- package/templates/default/src/app/(base)/locators/create/page.tsx +8 -4
- package/templates/default/src/app/(base)/locators/create/use-locator-workspace.ts +183 -0
- package/templates/default/src/app/(base)/locators/locator-helpers.test.ts +28 -0
- package/templates/default/src/app/(base)/locators/locator-helpers.ts +59 -0
- package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +74 -73
- package/templates/default/src/app/(base)/locators/locator-table.tsx +30 -28
- package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +20 -8
- package/templates/default/src/app/(base)/locators/page.tsx +3 -6
- package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +67 -66
- package/templates/default/src/app/(base)/modules/create/page.tsx +33 -34
- package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +43 -46
- package/templates/default/src/app/(base)/modules/module-form.test.tsx +84 -0
- package/templates/default/src/app/(base)/modules/module-form.tsx +159 -126
- package/templates/default/src/app/(base)/modules/module-helpers.ts +64 -0
- package/templates/default/src/app/(base)/modules/module-table-columns.tsx +81 -85
- package/templates/default/src/app/(base)/modules/module-table.tsx +25 -24
- package/templates/default/src/app/(base)/modules/page.tsx +59 -59
- package/templates/default/src/app/(base)/reports/[id]/page.tsx +20 -260
- package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
- package/templates/default/src/app/(base)/reports/feature-chart.tsx +79 -78
- package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -49
- package/templates/default/src/app/(base)/reports/page.tsx +98 -98
- package/templates/default/src/app/(base)/reports/report-detail-helpers.test.ts +109 -0
- package/templates/default/src/app/(base)/reports/report-detail-helpers.ts +247 -0
- package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -78
- 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/test-cases/page.tsx +40 -40
- 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 +58 -58
- package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.test.tsx +40 -0
- package/templates/default/src/app/(base)/settings/settings-sync-panel-helpers.tsx +110 -0
- package/templates/default/src/app/(base)/settings/settings-sync-panel.test.tsx +127 -0
- package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +19 -134
- package/templates/default/src/app/(base)/settings/use-settings-sync.ts +66 -0
- 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-helpers.ts +13 -0
- package/templates/default/src/app/(base)/tags/tag-form.test.tsx +83 -0
- package/templates/default/src/app/(base)/tags/tag-form.tsx +143 -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 +43 -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.test.tsx +82 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +181 -167
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-helpers.ts +54 -0
- 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 +34 -32
- package/templates/default/src/app/(base)/template-steps/create/page.tsx +40 -37
- package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +54 -49
- package/templates/default/src/app/(base)/template-steps/page.tsx +59 -58
- package/templates/default/src/app/(base)/template-steps/paramChip.tsx +233 -213
- package/templates/default/src/app/(base)/template-steps/template-step-form.test.tsx +132 -0
- package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +342 -384
- package/templates/default/src/app/(base)/template-steps/template-step-helpers.test.ts +99 -0
- package/templates/default/src/app/(base)/template-steps/template-step-helpers.ts +176 -0
- package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +153 -158
- package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +26 -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.test.tsx +109 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +45 -84
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.test.tsx +140 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +154 -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 +90 -76
- package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.test.ts +94 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/create-from-template-helpers.ts +171 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +105 -96
- package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +40 -38
- package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.test.tsx +87 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +83 -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 +3 -2
- package/templates/default/src/app/(base)/test-cases/test-case-flow.test.tsx +108 -0
- package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +43 -82
- package/templates/default/src/app/(base)/test-cases/test-case-form.test.tsx +202 -0
- package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +263 -395
- package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.test.ts +95 -0
- package/templates/default/src/app/(base)/test-cases/test-case-route-helpers.ts +147 -0
- package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +4 -2
- package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +11 -10
- package/templates/default/src/app/(base)/test-runs/create/page.tsx +4 -5
- package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
- package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.test.ts +50 -0
- package/templates/default/src/app/(base)/test-runs/test-run-form-helpers.ts +168 -0
- package/templates/default/src/app/(base)/test-runs/test-run-form.test.tsx +138 -0
- package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +111 -256
- 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-runs/use-test-run-name-validation.ts +74 -0
- package/templates/default/src/app/(base)/test-suites/create/page.tsx +17 -12
- package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +22 -19
- package/templates/default/src/app/(base)/test-suites/page.tsx +14 -56
- package/templates/default/src/app/(base)/test-suites/test-suite-form.test.tsx +127 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +45 -64
- package/templates/default/src/app/(base)/test-suites/test-suite-helpers.test.ts +67 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-helpers.ts +215 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +32 -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/reports/steps/[stepId]/screenshot/route.test.ts +83 -0
- package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -52
- package/templates/default/src/app/api/test-runs/[runId]/download/route.test.ts +169 -0
- package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +1 -1
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.test.ts +135 -0
- 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/page.tsx +1 -1
- 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-helpers.test.ts +32 -0
- package/templates/default/src/components/data-extraction/locator-inspector-helpers.ts +183 -0
- package/templates/default/src/components/data-extraction/locator-inspector.tsx +349 -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/diagram/button-edge.tsx +54 -54
- package/templates/default/src/components/diagram/dynamic-parameters-helpers.test.ts +83 -0
- package/templates/default/src/components/diagram/dynamic-parameters-helpers.ts +158 -0
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +350 -474
- package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
- package/templates/default/src/components/diagram/flow-diagram-helpers.test.ts +117 -0
- package/templates/default/src/components/diagram/flow-diagram-helpers.ts +251 -0
- package/templates/default/src/components/diagram/flow-diagram.tsx +247 -470
- package/templates/default/src/components/diagram/flow-host-helpers.test.ts +74 -0
- package/templates/default/src/components/diagram/flow-host-helpers.ts +51 -0
- package/templates/default/src/components/diagram/node-form-helpers.test.ts +92 -0
- package/templates/default/src/components/diagram/node-form-helpers.ts +100 -0
- package/templates/default/src/components/diagram/node-form.test.tsx +168 -0
- package/templates/default/src/components/diagram/node-form.tsx +199 -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/diagram/use-flow-node-order.ts +49 -0
- 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/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 +118 -116
- package/templates/default/src/components/navigation/nav-card.tsx +31 -31
- package/templates/default/src/components/navigation/nav-command-helpers.ts +122 -0
- package/templates/default/src/components/navigation/nav-command-search.tsx +125 -0
- package/templates/default/src/components/navigation/nav-command.test.tsx +106 -0
- package/templates/default/src/components/navigation/nav-command.tsx +49 -472
- 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/navigation/use-nav-command.ts +58 -0
- package/templates/default/src/components/node-header.tsx +159 -159
- package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -310
- package/templates/default/src/components/table/table-actions.tsx +174 -172
- package/templates/default/src/components/test-case/test-case-form-helpers.test.ts +100 -0
- package/templates/default/src/components/test-case/test-case-form-helpers.ts +140 -0
- package/templates/default/src/components/test-case/test-case-picker-helpers.test.ts +40 -0
- package/templates/default/src/components/test-case/test-case-picker-helpers.ts +41 -0
- package/templates/default/src/components/test-case/test-case-picker.test.tsx +44 -0
- package/templates/default/src/components/test-case/test-case-picker.tsx +16 -35
- package/templates/default/src/components/test-case/test-scenario-preview.tsx +34 -0
- package/templates/default/src/components/test-run/download-logs-button.tsx +92 -92
- package/templates/default/src/components/test-run/log-viewer-helpers.test.ts +37 -0
- package/templates/default/src/components/test-run/log-viewer-helpers.ts +80 -0
- package/templates/default/src/components/test-run/log-viewer.test.tsx +118 -0
- package/templates/default/src/components/test-run/log-viewer.tsx +51 -350
- package/templates/default/src/components/test-run/test-run-details-helpers.test.ts +31 -0
- package/templates/default/src/components/test-run/test-run-details-helpers.ts +208 -0
- package/templates/default/src/components/test-run/test-run-details.test.tsx +174 -0
- package/templates/default/src/components/test-run/test-run-details.tsx +155 -457
- package/templates/default/src/components/test-run/test-run-header-helpers.test.ts +31 -0
- package/templates/default/src/components/test-run/test-run-header-helpers.ts +23 -0
- package/templates/default/src/components/test-run/test-run-header.test.tsx +103 -0
- package/templates/default/src/components/test-run/test-run-header.tsx +27 -149
- package/templates/default/src/components/test-run/use-log-viewer.ts +213 -0
- package/templates/default/src/components/test-run/use-test-run-details.ts +184 -0
- package/templates/default/src/components/test-run/use-test-run-header.ts +89 -0
- package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
- package/templates/default/src/components/test-suite/test-suite-picker-helpers.test.ts +68 -0
- package/templates/default/src/components/test-suite/test-suite-picker-helpers.ts +76 -0
- package/templates/default/src/components/test-suite/test-suite-picker.test.tsx +65 -0
- package/templates/default/src/components/test-suite/test-suite-picker.tsx +4 -72
- 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.test.tsx +122 -0
- package/templates/default/src/components/ui/data-table.tsx +298 -261
- 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.test.tsx +45 -0
- package/templates/default/src/components/ui/multi-select.tsx +158 -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 -151
- 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.test.tsx +60 -0
- package/templates/default/src/components/user-prompt/delete-prompt.tsx +118 -87
- 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-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/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-suite-form-opts.ts +24 -24
- package/templates/default/src/hooks/use-toast.ts +187 -187
- package/templates/default/src/lib/automation/automation-path-roots.ts +95 -0
- package/templates/default/src/lib/automation/automation-workspace.ts +147 -0
- package/templates/default/src/lib/automation/paths.ts +6 -211
- package/templates/default/src/lib/bidirectional-sync.ts +432 -432
- package/templates/default/src/lib/environment-file-utils.ts +2 -1
- package/templates/default/src/lib/executor/local-executor-adapter.ts +2 -5
- package/templates/default/src/lib/feature-file-generator.ts +2 -1
- package/templates/default/src/lib/gherkin-parser.ts +0 -2
- package/templates/default/src/lib/locator-group-file-utils.ts +304 -307
- package/templates/default/src/lib/locator-picker/session-manager.ts +0 -21
- package/templates/default/src/lib/locator-picker/suggestions.ts +4 -2
- package/templates/default/src/lib/metrics/metric-calculator.ts +2 -6
- 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/sync/sync-executor.test.ts +76 -0
- package/templates/default/src/lib/sync/sync-pending-counts.test.ts +227 -226
- package/templates/default/src/lib/sync/sync-pending-counts.ts +2 -5
- package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
- package/templates/default/src/lib/template-sync-utils.js +46 -46
- package/templates/default/src/lib/template-sync-utils.ts +63 -63
- 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/report-parser.ts +352 -352
- package/templates/default/src/lib/test-run/test-run-executor.ts +13 -13
- package/templates/default/src/lib/test-run/winston-logger.ts +65 -64
- 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 +2 -2
- 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/services/dashboard/dashboard-service.test.ts +106 -0
- package/templates/default/src/services/dashboard/dashboard-service.ts +173 -0
- package/templates/default/src/services/environment/environment-service.test.ts +137 -0
- package/templates/default/src/services/environment/environment-service.ts +96 -0
- package/templates/default/src/services/locator/locator-path-utils.test.ts +14 -0
- package/templates/default/src/services/locator/locator-path-utils.ts +14 -0
- package/templates/default/src/services/locator/locator-service.test.ts +63 -0
- package/templates/default/src/services/locator/locator-service.ts +479 -0
- package/templates/default/src/services/locator/locator-sync-utils.test.ts +19 -0
- package/templates/default/src/services/locator/locator-sync-utils.ts +21 -0
- package/templates/default/src/services/locator-group/locator-group-service.test.ts +123 -0
- package/templates/default/src/services/locator-group/locator-group-service.ts +180 -0
- package/templates/default/src/services/module/module-service.test.ts +89 -0
- package/templates/default/src/services/module/module-service.ts +66 -0
- package/templates/default/src/services/report/report-service.test.ts +244 -0
- package/templates/default/src/services/report/report-service.ts +438 -0
- package/templates/default/src/services/shared/constants.ts +2 -0
- package/templates/default/src/services/shared/errors.test.ts +38 -0
- package/templates/default/src/services/shared/errors.ts +44 -0
- package/templates/default/src/services/shared/index.ts +7 -0
- package/templates/default/src/services/tag/tag-service.test.ts +22 -0
- package/templates/default/src/services/tag/tag-service.ts +41 -0
- package/templates/default/src/services/template-step/template-step-service.test.ts +22 -0
- package/templates/default/src/services/template-step/template-step-service.ts +171 -0
- package/templates/default/src/services/template-step-group/template-step-group-service.test.ts +22 -0
- package/templates/default/src/services/template-step-group/template-step-group-service.ts +81 -0
- package/templates/default/src/services/template-test-case/template-test-case-service.test.ts +22 -0
- package/templates/default/src/services/template-test-case/template-test-case-service.ts +128 -0
- package/templates/default/src/services/test-case/test-case-service.test.ts +175 -0
- package/templates/default/src/services/test-case/test-case-service.ts +298 -0
- package/templates/default/src/services/test-run/test-run-helpers.ts +61 -0
- package/templates/default/src/services/test-run/test-run-service.test.ts +647 -0
- package/templates/default/src/services/test-run/test-run-service.ts +917 -0
- package/templates/default/src/services/test-suite/test-suite-service.test.ts +127 -0
- package/templates/default/src/services/test-suite/test-suite-service.ts +197 -0
- 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 +19 -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/tsconfig.json +1 -1
- package/dist/cli.e2e.test.d.ts +0 -2
- package/dist/cli.e2e.test.d.ts.map +0 -1
- package/dist/cli.e2e.test.js +0 -73
- package/dist/cli.e2e.test.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -65
- package/dist/config.test.js.map +0 -1
- package/dist/copy-template.test.d.ts +0 -2
- package/dist/copy-template.test.d.ts.map +0 -1
- package/dist/copy-template.test.js +0 -71
- package/dist/copy-template.test.js.map +0 -1
- package/dist/download-repo.test.d.ts +0 -2
- package/dist/download-repo.test.d.ts.map +0 -1
- package/dist/download-repo.test.js +0 -14
- package/dist/download-repo.test.js.map +0 -1
- package/dist/install.test.d.ts +0 -2
- package/dist/install.test.d.ts.map +0 -1
- package/dist/install.test.js +0 -119
- package/dist/install.test.js.map +0 -1
- package/dist/prompts.test.d.ts +0 -2
- package/dist/prompts.test.d.ts.map +0 -1
- package/dist/prompts.test.js +0 -58
- package/dist/prompts.test.js.map +0 -1
- package/templates/default/src/actions/conflict/conflict.action.ts +0 -33
- package/templates/default/src/actions/review/review-actions.ts +0 -147
- package/templates/default/src/actions/user/user-actions.ts +0 -13
- package/templates/default/src/app/(base)/locators/locator-form.tsx +0 -163
- package/templates/default/src/app/(base)/reviews/create/page.tsx +0 -26
- package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +0 -15
- package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +0 -26
- package/templates/default/src/app/(base)/reviews/page.tsx +0 -26
- package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +0 -26
- package/templates/default/src/app/(base)/reviews/review-form.tsx +0 -11
- package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +0 -9
- package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +0 -9
- package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +0 -15
- package/templates/default/src/constants/form-opts/locator-form-opts.ts +0 -20
- package/templates/default/src/constants/form-opts/review-form-opts.ts +0 -23
|
@@ -1,345 +1,93 @@
|
|
|
1
1
|
'use server'
|
|
2
2
|
|
|
3
|
-
import prisma from '@/config/db-config'
|
|
4
3
|
import { testRunSchema } from '@/constants/form-opts/test-run-form-opts'
|
|
5
4
|
import { ActionResponse } from '@/types/form/actionHandler'
|
|
6
5
|
import { z } from 'zod'
|
|
7
|
-
import {
|
|
8
|
-
TestRunStatus,
|
|
9
|
-
TestRunResult,
|
|
10
|
-
TestRunTestCaseStatus,
|
|
11
|
-
TestRunTestCaseResult,
|
|
12
|
-
Tag,
|
|
13
|
-
} from '@prisma/client'
|
|
14
|
-
import { localExecutorAdapter } from '@/lib/executor/local-executor-adapter'
|
|
15
6
|
import { revalidatePath } from 'next/cache'
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
async function checkUniqueName(name: string, excludeId?: string): Promise<boolean> {
|
|
31
|
-
const existing = await prisma.testRun.findFirst({
|
|
32
|
-
where: {
|
|
33
|
-
name: name,
|
|
34
|
-
...(excludeId && { id: { not: excludeId } }),
|
|
35
|
-
},
|
|
36
|
-
})
|
|
37
|
-
return !!existing
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function buildOrExpression(expressions: string[]): string | null {
|
|
41
|
-
if (expressions.length === 0) {
|
|
42
|
-
return null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (expressions.length === 1) {
|
|
46
|
-
return expressions[0]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return expressions.join(' or ')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeSuiteSelection(
|
|
53
|
-
selection: z.infer<typeof testRunSchema>['testSuites'][number],
|
|
54
|
-
availableTestCaseIds: string[],
|
|
55
|
-
) {
|
|
56
|
-
if (selection.runAll) {
|
|
57
|
-
return {
|
|
58
|
-
testSuiteId: selection.testSuiteId,
|
|
59
|
-
runAll: true,
|
|
60
|
-
testCaseIds: [] as string[],
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const selectedTestCaseIds = selection.testCaseIds.filter(testCaseId => availableTestCaseIds.includes(testCaseId))
|
|
65
|
-
|
|
66
|
-
if (selectedTestCaseIds.length === availableTestCaseIds.length) {
|
|
67
|
-
return {
|
|
68
|
-
testSuiteId: selection.testSuiteId,
|
|
69
|
-
runAll: true,
|
|
70
|
-
testCaseIds: [] as string[],
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
testSuiteId: selection.testSuiteId,
|
|
76
|
-
runAll: false,
|
|
77
|
-
testCaseIds: selectedTestCaseIds,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
7
|
+
import {
|
|
8
|
+
cancelTestRunService,
|
|
9
|
+
checkTraceViewerStatusService,
|
|
10
|
+
createTestRunFromValidatedValue,
|
|
11
|
+
deleteTestRunsByIds,
|
|
12
|
+
getTestRunLogsService,
|
|
13
|
+
getTestRunByIdOrThrow,
|
|
14
|
+
isTestRunNameTaken,
|
|
15
|
+
listTestRuns,
|
|
16
|
+
listTestSuiteTestCases,
|
|
17
|
+
spawnTraceViewerService,
|
|
18
|
+
} from '@/services/test-run/test-run-service'
|
|
19
|
+
import { ServiceError, serviceErrorToActionResponse, unknownErrorToActionResponse } from '@/services/shared/errors'
|
|
80
20
|
|
|
81
21
|
export async function getAllTestRunsAction(filter?: string): Promise<ActionResponse> {
|
|
82
22
|
try {
|
|
83
|
-
|
|
84
|
-
const whereClause: Prisma.TestRunWhereInput = {}
|
|
85
|
-
|
|
86
|
-
if (filter === 'recentFailed') {
|
|
87
|
-
// Calculate the date 7 days ago
|
|
88
|
-
const sevenDaysAgo = new Date()
|
|
89
|
-
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
|
|
90
|
-
|
|
91
|
-
whereClause.result = TestRunResult.FAILED
|
|
92
|
-
whereClause.completedAt = {
|
|
93
|
-
not: null,
|
|
94
|
-
gte: sevenDaysAgo,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const testRuns = await prisma.testRun.findMany({
|
|
99
|
-
where: whereClause,
|
|
100
|
-
include: {
|
|
101
|
-
testCases: true,
|
|
102
|
-
tags: true,
|
|
103
|
-
environment: true,
|
|
104
|
-
},
|
|
105
|
-
})
|
|
23
|
+
const testRuns = await listTestRuns(filter)
|
|
106
24
|
return {
|
|
107
25
|
status: 200,
|
|
26
|
+
success: true,
|
|
108
27
|
data: testRuns,
|
|
109
28
|
}
|
|
110
29
|
} catch (error) {
|
|
111
|
-
return
|
|
112
|
-
status: 500,
|
|
113
|
-
error: `Server error occurred: ${error}`,
|
|
114
|
-
}
|
|
30
|
+
return unknownErrorToActionResponse(error)
|
|
115
31
|
}
|
|
116
32
|
}
|
|
117
33
|
|
|
118
34
|
export async function getTestRunByIdAction(id: string): Promise<ActionResponse> {
|
|
119
35
|
try {
|
|
120
|
-
const testRun = await
|
|
121
|
-
where: { id },
|
|
122
|
-
include: {
|
|
123
|
-
testCases: {
|
|
124
|
-
include: {
|
|
125
|
-
testCase: true,
|
|
126
|
-
testSuite: true,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
tags: true,
|
|
130
|
-
environment: true,
|
|
131
|
-
reports: true,
|
|
132
|
-
},
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
if (!testRun) {
|
|
136
|
-
return {
|
|
137
|
-
status: 404,
|
|
138
|
-
error: 'Test run not found',
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
36
|
+
const testRun = await getTestRunByIdOrThrow(id)
|
|
142
37
|
return {
|
|
143
38
|
status: 200,
|
|
39
|
+
success: true,
|
|
144
40
|
data: testRun,
|
|
145
41
|
}
|
|
146
42
|
} catch (error) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
error: `Server error occurred: ${error}`,
|
|
43
|
+
if (error instanceof ServiceError) {
|
|
44
|
+
return serviceErrorToActionResponse(error)
|
|
150
45
|
}
|
|
46
|
+
return unknownErrorToActionResponse(error)
|
|
151
47
|
}
|
|
152
48
|
}
|
|
153
49
|
|
|
154
50
|
export async function deleteTestRunAction(id: string[]): Promise<ActionResponse> {
|
|
155
51
|
try {
|
|
156
|
-
|
|
157
|
-
where: { id: { in: id } },
|
|
158
|
-
select: {
|
|
159
|
-
runId: true,
|
|
160
|
-
logPath: true,
|
|
161
|
-
reportPath: true,
|
|
162
|
-
testCases: {
|
|
163
|
-
select: {
|
|
164
|
-
tracePath: true,
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
for (const testRun of testRuns) {
|
|
171
|
-
await fs.rm(getAutomationReportRunDir(testRun.runId), { recursive: true, force: true })
|
|
172
|
-
|
|
173
|
-
const legacyArtifactPaths = [
|
|
174
|
-
testRun.logPath,
|
|
175
|
-
testRun.reportPath,
|
|
176
|
-
...testRun.testCases.map(testCase => testCase.tracePath),
|
|
177
|
-
].filter((artifactPath): artifactPath is string => Boolean(artifactPath))
|
|
178
|
-
|
|
179
|
-
for (const artifactPath of legacyArtifactPaths) {
|
|
180
|
-
await fs.rm(resolveStoredPath(artifactPath), { force: true }).catch(() => {})
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// delete the test runs
|
|
185
|
-
await prisma.testRun.deleteMany({
|
|
186
|
-
where: { id: { in: id } },
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Recalculate metrics for affected test cases and dashboard metrics
|
|
190
|
-
// Note: We recalculate all test case metrics, not just affected ones, because
|
|
191
|
-
// deleting a test run might affect consecutive failure counts for any test case
|
|
192
|
-
// that had recent runs (e.g., if a test case had 3 consecutive failures and we
|
|
193
|
-
// delete one of those failures, it might no longer be "repeatedly failing")
|
|
194
|
-
const { recalculateMetricsForTestCases, updateDashboardMetrics } = await import('@/lib/metrics/metric-calculator')
|
|
195
|
-
|
|
196
|
-
// Get all test case IDs that have recent test runs (last 7 days)
|
|
197
|
-
// These are the ones that might be affected by the deletion
|
|
198
|
-
// We recalculate all of them because deleting a test run might affect
|
|
199
|
-
// consecutive failure counts for any test case
|
|
200
|
-
const recentPeriodDate = new Date()
|
|
201
|
-
recentPeriodDate.setDate(recentPeriodDate.getDate() - 7)
|
|
202
|
-
|
|
203
|
-
const allRecentTestRunTestCases = await prisma.testRunTestCase.findMany({
|
|
204
|
-
where: {
|
|
205
|
-
status: TestRunTestCaseStatus.COMPLETED,
|
|
206
|
-
testRun: {
|
|
207
|
-
completedAt: {
|
|
208
|
-
gte: recentPeriodDate,
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
select: {
|
|
213
|
-
testCaseId: true,
|
|
214
|
-
},
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
// Get unique test case IDs
|
|
218
|
-
const allAffectedTestCaseIds = [...new Set(allRecentTestRunTestCases.map(trtc => trtc.testCaseId))]
|
|
219
|
-
|
|
220
|
-
// Recalculate metrics for all test cases with recent runs
|
|
221
|
-
if (allAffectedTestCaseIds.length > 0) {
|
|
222
|
-
await recalculateMetricsForTestCases(allAffectedTestCaseIds)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Always update dashboard metrics (e.g., failedRecentRunsCount might change)
|
|
226
|
-
await updateDashboardMetrics()
|
|
52
|
+
await deleteTestRunsByIds(id)
|
|
227
53
|
|
|
228
|
-
// Revalidate paths
|
|
229
54
|
revalidatePath('/test-runs')
|
|
230
55
|
revalidatePath('/')
|
|
231
56
|
return {
|
|
232
57
|
status: 200,
|
|
58
|
+
success: true,
|
|
233
59
|
message: 'Test run(s) deleted successfully',
|
|
234
60
|
}
|
|
235
61
|
} catch (error) {
|
|
236
|
-
return
|
|
237
|
-
status: 500,
|
|
238
|
-
error: `Server error occurred: ${error}`,
|
|
239
|
-
}
|
|
62
|
+
return unknownErrorToActionResponse(error)
|
|
240
63
|
}
|
|
241
64
|
}
|
|
242
65
|
|
|
243
66
|
export async function getAllTestSuiteTestCasesAction(): Promise<ActionResponse> {
|
|
244
67
|
try {
|
|
245
|
-
await
|
|
246
|
-
|
|
247
|
-
const testSuiteTestCases = await prisma.testSuite.findMany({
|
|
248
|
-
include: {
|
|
249
|
-
module: true,
|
|
250
|
-
tags: true,
|
|
251
|
-
testCases: {
|
|
252
|
-
include: {
|
|
253
|
-
steps: true,
|
|
254
|
-
tags: true,
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
})
|
|
68
|
+
const testSuiteTestCases = await listTestSuiteTestCases()
|
|
259
69
|
return {
|
|
260
70
|
status: 200,
|
|
71
|
+
success: true,
|
|
261
72
|
data: testSuiteTestCases,
|
|
262
73
|
}
|
|
263
74
|
} catch (error) {
|
|
264
|
-
return
|
|
265
|
-
status: 500,
|
|
266
|
-
error: `Server error occurred: ${error}`,
|
|
267
|
-
}
|
|
75
|
+
return unknownErrorToActionResponse(error)
|
|
268
76
|
}
|
|
269
77
|
}
|
|
270
78
|
|
|
271
|
-
/**
|
|
272
|
-
* Stores test run logs in the database
|
|
273
|
-
* @param testRunId - The test run ID (runId, not id)
|
|
274
|
-
* @param logs - Array of log entries to store
|
|
275
|
-
*/
|
|
276
|
-
export async function storeTestRunLogsAction(testRunId: string, logs: LogEntry[]): Promise<ActionResponse> {
|
|
277
|
-
try {
|
|
278
|
-
if (logs.length === 0) {
|
|
279
|
-
return {
|
|
280
|
-
status: 200,
|
|
281
|
-
message: 'No logs to store',
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Format logs for storage
|
|
286
|
-
const formattedLogs = formatLogsForStorage(logs)
|
|
287
|
-
|
|
288
|
-
// Upsert logs in TestRunLog table
|
|
289
|
-
await prisma.testRunLog.upsert({
|
|
290
|
-
where: { testRunId },
|
|
291
|
-
create: {
|
|
292
|
-
testRunId,
|
|
293
|
-
logs: formattedLogs,
|
|
294
|
-
},
|
|
295
|
-
update: {
|
|
296
|
-
logs: formattedLogs,
|
|
297
|
-
},
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
status: 200,
|
|
302
|
-
message: 'Logs stored successfully',
|
|
303
|
-
}
|
|
304
|
-
} catch (error) {
|
|
305
|
-
console.error(`[TestRunAction] Error storing logs for testRunId: ${testRunId}:`, error)
|
|
306
|
-
return {
|
|
307
|
-
status: 500,
|
|
308
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Retrieves test run logs from the database
|
|
315
|
-
* @param testRunId - The test run ID (runId, not id)
|
|
316
|
-
*/
|
|
317
79
|
export async function getTestRunLogsAction(testRunId: string): Promise<ActionResponse> {
|
|
318
80
|
try {
|
|
319
|
-
const
|
|
320
|
-
where: { testRunId },
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
if (!testRunLog) {
|
|
324
|
-
return {
|
|
325
|
-
status: 200,
|
|
326
|
-
data: [],
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Parse logs from storage
|
|
331
|
-
const logs = parseLogsFromStorage(testRunLog.logs)
|
|
81
|
+
const logs = await getTestRunLogsService(testRunId)
|
|
332
82
|
|
|
333
83
|
return {
|
|
334
84
|
status: 200,
|
|
85
|
+
success: true,
|
|
335
86
|
data: logs,
|
|
336
87
|
}
|
|
337
88
|
} catch (error) {
|
|
338
89
|
console.error(`[TestRunAction] Error retrieving logs for testRunId: ${testRunId}:`, error)
|
|
339
|
-
return
|
|
340
|
-
status: 500,
|
|
341
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
342
|
-
}
|
|
90
|
+
return unknownErrorToActionResponse(error)
|
|
343
91
|
}
|
|
344
92
|
}
|
|
345
93
|
|
|
@@ -348,669 +96,51 @@ export async function createTestRunAction(
|
|
|
348
96
|
value: z.infer<typeof testRunSchema>,
|
|
349
97
|
): Promise<ActionResponse> {
|
|
350
98
|
try {
|
|
351
|
-
// Validate input
|
|
352
99
|
testRunSchema.parse(value)
|
|
353
100
|
|
|
354
|
-
|
|
355
|
-
const nameExists = await checkUniqueName(value.name)
|
|
356
|
-
if (nameExists) {
|
|
357
|
-
return {
|
|
358
|
-
status: 400,
|
|
359
|
-
error: 'A test run with this name already exists. Please choose a different name.',
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Fetch environment and tags from database
|
|
364
|
-
const environment = await prisma.environment.findUnique({
|
|
365
|
-
where: { id: value.environmentId },
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
if (!environment) {
|
|
369
|
-
return {
|
|
370
|
-
status: 400,
|
|
371
|
-
error: 'Environment not found',
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Determine if we're filtering by tags or test suites
|
|
376
|
-
const isFilteringByTags = value.tags.length > 0
|
|
377
|
-
const isFilteringByTestSuites = value.testSuites.length > 0 && value.tags.length === 0
|
|
378
|
-
|
|
379
|
-
// Validate that at least one filtering option is provided
|
|
380
|
-
if (!isFilteringByTags && !isFilteringByTestSuites) {
|
|
381
|
-
return {
|
|
382
|
-
status: 400,
|
|
383
|
-
error: 'Either tags or test suites must be provided to filter the test run.',
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
let tags: Tag[] = []
|
|
388
|
-
let tagExpression: string | null = null
|
|
389
|
-
let testRunTestCases: Array<{ testCaseId: string; testSuiteId?: string | null }> = []
|
|
390
|
-
|
|
391
|
-
if (isFilteringByTags) {
|
|
392
|
-
tags = await prisma.tag.findMany({
|
|
393
|
-
where: { id: { in: value.tags } },
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
tagExpression = buildOrExpression(tags.map(tag => `(${tag.tagExpression})`))
|
|
397
|
-
|
|
398
|
-
// Find test cases that have tags directly OR belong to test suites with tags
|
|
399
|
-
const tagFilteredTestCases = await prisma.testCase.findMany({
|
|
400
|
-
where: {
|
|
401
|
-
OR: [
|
|
402
|
-
// Test cases with tags directly
|
|
403
|
-
{
|
|
404
|
-
tags: {
|
|
405
|
-
some: { id: { in: value.tags } },
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
// Test cases in test suites with tags
|
|
409
|
-
{
|
|
410
|
-
TestSuite: {
|
|
411
|
-
some: {
|
|
412
|
-
tags: {
|
|
413
|
-
some: { id: { in: value.tags } },
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
},
|
|
418
|
-
],
|
|
419
|
-
},
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
testRunTestCases = tagFilteredTestCases.map(tc => ({
|
|
423
|
-
testCaseId: tc.id,
|
|
424
|
-
testSuiteId: null,
|
|
425
|
-
}))
|
|
426
|
-
} else if (isFilteringByTestSuites) {
|
|
427
|
-
await ensureTestSuiteIdentifierTags(value.testSuites.map(testSuite => testSuite.testSuiteId))
|
|
428
|
-
|
|
429
|
-
const selectedSuites = await prisma.testSuite.findMany({
|
|
430
|
-
where: {
|
|
431
|
-
id: {
|
|
432
|
-
in: value.testSuites.map(testSuite => testSuite.testSuiteId),
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
include: {
|
|
436
|
-
tags: true,
|
|
437
|
-
testCases: {
|
|
438
|
-
include: {
|
|
439
|
-
tags: true,
|
|
440
|
-
},
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
if (selectedSuites.length !== value.testSuites.length) {
|
|
446
|
-
return {
|
|
447
|
-
status: 400,
|
|
448
|
-
error: 'One or more selected test suites could not be found.',
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const selectedSuiteById = new Map(selectedSuites.map(testSuite => [testSuite.id, testSuite]))
|
|
453
|
-
const suiteClauses: string[] = []
|
|
454
|
-
|
|
455
|
-
for (const suiteSelection of value.testSuites) {
|
|
456
|
-
const selectedSuite = selectedSuiteById.get(suiteSelection.testSuiteId)
|
|
457
|
-
if (!selectedSuite) {
|
|
458
|
-
continue
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (selectedSuite.testCases.length === 0) {
|
|
462
|
-
continue
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const normalizedSelection = normalizeSuiteSelection(
|
|
466
|
-
suiteSelection,
|
|
467
|
-
selectedSuite.testCases.map(testCase => testCase.id),
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
if (!normalizedSelection) {
|
|
471
|
-
continue
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const suiteIdentifierTag = getIdentifierTagByPrefix(selectedSuite.tags, 'ts_')
|
|
475
|
-
if (!suiteIdentifierTag) {
|
|
476
|
-
return {
|
|
477
|
-
status: 400,
|
|
478
|
-
error: `Test suite "${selectedSuite.name}" does not have an identifier tag.`,
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (normalizedSelection.runAll) {
|
|
483
|
-
suiteClauses.push(`(${suiteIdentifierTag.tagExpression})`)
|
|
484
|
-
testRunTestCases.push(
|
|
485
|
-
...selectedSuite.testCases.map(testCase => ({
|
|
486
|
-
testCaseId: testCase.id,
|
|
487
|
-
testSuiteId: selectedSuite.id,
|
|
488
|
-
})),
|
|
489
|
-
)
|
|
490
|
-
continue
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const selectedTestCases = selectedSuite.testCases.filter(testCase =>
|
|
494
|
-
normalizedSelection.testCaseIds.includes(testCase.id),
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
if (selectedTestCases.length === 0) {
|
|
498
|
-
return {
|
|
499
|
-
status: 400,
|
|
500
|
-
error: `Test suite "${selectedSuite.name}" requires at least one selected test case.`,
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const missingIdentifierTestCase = selectedTestCases.find(
|
|
505
|
-
testCase => !getIdentifierTagByPrefix(testCase.tags, 'tc_'),
|
|
506
|
-
)
|
|
507
|
-
if (missingIdentifierTestCase) {
|
|
508
|
-
return {
|
|
509
|
-
status: 400,
|
|
510
|
-
error: `Test case "${missingIdentifierTestCase.title}" does not have an identifier tag.`,
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const testCaseTagExpressions = selectedTestCases.map(testCase => {
|
|
515
|
-
const identifierTag = getIdentifierTagByPrefix(testCase.tags, 'tc_')
|
|
516
|
-
return identifierTag!.tagExpression
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
suiteClauses.push(
|
|
520
|
-
`(${suiteIdentifierTag.tagExpression}) and (${testCaseTagExpressions.map(tag => `(${tag})`).join(' or ')})`,
|
|
521
|
-
)
|
|
522
|
-
testRunTestCases.push(
|
|
523
|
-
...selectedTestCases.map(testCase => ({
|
|
524
|
-
testCaseId: testCase.id,
|
|
525
|
-
testSuiteId: selectedSuite.id,
|
|
526
|
-
})),
|
|
527
|
-
)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
tagExpression = buildOrExpression(suiteClauses.map(clause => `(${clause})`))
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (!tagExpression) {
|
|
534
|
-
return {
|
|
535
|
-
status: 400,
|
|
536
|
-
error: 'No executable tests were resolved from the selected filters.',
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Create TestRun record in database with RUNNING status
|
|
541
|
-
const testRun = await prisma.testRun.create({
|
|
542
|
-
data: {
|
|
543
|
-
name: value.name,
|
|
544
|
-
environmentId: value.environmentId,
|
|
545
|
-
testWorkersCount: value.testWorkersCount || 1,
|
|
546
|
-
browserEngine: value.browserEngine,
|
|
547
|
-
status: TestRunStatus.RUNNING,
|
|
548
|
-
result: TestRunResult.PENDING,
|
|
549
|
-
tags: {
|
|
550
|
-
connect: tags.map(tag => ({ id: tag.id })),
|
|
551
|
-
},
|
|
552
|
-
testCases: {
|
|
553
|
-
create: testRunTestCases.map(tc => ({
|
|
554
|
-
testCaseId: tc.testCaseId,
|
|
555
|
-
testSuiteId: tc.testSuiteId ?? null,
|
|
556
|
-
})),
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
// Initialize Winston logger for this test run
|
|
562
|
-
const logger = await createTestRunLogger(testRun.runId)
|
|
563
|
-
const logFilePath = getLogFilePath(testRun.runId)
|
|
564
|
-
|
|
565
|
-
// Store log file path in database
|
|
566
|
-
await prisma.testRun.update({
|
|
567
|
-
where: { id: testRun.id },
|
|
568
|
-
data: {
|
|
569
|
-
logPath: logFilePath,
|
|
570
|
-
},
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
// Execute test run asynchronously (don't await, let it run in background)
|
|
574
|
-
try {
|
|
575
|
-
const { process: spawnedProcess, reportPath } = await localExecutorAdapter.executeTestRun({
|
|
576
|
-
testRunId: testRun.runId,
|
|
577
|
-
environment,
|
|
578
|
-
tagExpression,
|
|
579
|
-
testWorkersCount: value.testWorkersCount || 1,
|
|
580
|
-
browserEngine: value.browserEngine,
|
|
581
|
-
headless: true, // Default to headless
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
// Store report path in TestRun record
|
|
585
|
-
await prisma.testRun.update({
|
|
586
|
-
where: { id: testRun.id },
|
|
587
|
-
data: {
|
|
588
|
-
reportPath,
|
|
589
|
-
},
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
const executePromise = Promise.resolve(spawnedProcess)
|
|
593
|
-
|
|
594
|
-
// Set up server-side listener for scenario::end events to update test case statuses
|
|
595
|
-
// This ensures status updates happen even if no client is connected
|
|
596
|
-
const onScenarioEnd = async (eventData: {
|
|
597
|
-
testRunId: string
|
|
598
|
-
scenarioName: string
|
|
599
|
-
status: string
|
|
600
|
-
tracePath?: string
|
|
601
|
-
featureName?: string
|
|
602
|
-
scenarioTags?: string[]
|
|
603
|
-
}) => {
|
|
604
|
-
// Only process events for this test run
|
|
605
|
-
if (eventData.testRunId === testRun.runId) {
|
|
606
|
-
console.log(
|
|
607
|
-
`[TestRunAction] Server-side scenario::end event for testRunId: ${testRun.runId}, scenario: ${eventData.scenarioName}, status: ${eventData.status}${eventData.tracePath ? `, tracePath: ${eventData.tracePath}` : ''}`,
|
|
608
|
-
)
|
|
609
|
-
// Map the status string to the expected format
|
|
610
|
-
const statusMap: Record<string, 'passed' | 'failed' | 'skipped' | 'unknown'> = {
|
|
611
|
-
passed: 'passed',
|
|
612
|
-
failed: 'failed',
|
|
613
|
-
skipped: 'skipped',
|
|
614
|
-
}
|
|
615
|
-
const mappedStatus = statusMap[eventData.status] || 'unknown'
|
|
616
|
-
// Update test case status in database
|
|
617
|
-
await updateTestRunTestCaseStatusAction(testRun.runId, {
|
|
618
|
-
scenarioName: eventData.scenarioName,
|
|
619
|
-
status: mappedStatus,
|
|
620
|
-
tracePath: eventData.tracePath,
|
|
621
|
-
featureName: eventData.featureName,
|
|
622
|
-
scenarioTags: eventData.scenarioTags,
|
|
623
|
-
})
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Register the server-side listener
|
|
628
|
-
processManager.on('scenario::end', onScenarioEnd)
|
|
629
|
-
console.log(`[TestRunAction] Registered server-side scenario::end listener for testRunId: ${testRun.runId}`)
|
|
630
|
-
|
|
631
|
-
// Cleanup function to remove the listener
|
|
632
|
-
const cleanupListener = () => {
|
|
633
|
-
processManager.removeListener('scenario::end', onScenarioEnd)
|
|
634
|
-
console.log(`[TestRunAction] Removed server-side scenario::end listener for testRunId: ${testRun.runId}`)
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
executePromise
|
|
638
|
-
.then(async spawnedProcess => {
|
|
639
|
-
// Wait for process to complete
|
|
640
|
-
const exitCode = await localExecutorAdapter.waitForProcess(spawnedProcess.name)
|
|
641
|
-
|
|
642
|
-
// Collect all logs from the process output
|
|
643
|
-
const logEntries: LogEntry[] = []
|
|
644
|
-
|
|
645
|
-
// Add stdout logs
|
|
646
|
-
if (spawnedProcess.output.stdout.length > 0) {
|
|
647
|
-
const stdoutText = spawnedProcess.output.stdout.join('')
|
|
648
|
-
const stdoutLines = stdoutText.split('\n').filter(line => line.trim() !== '')
|
|
649
|
-
stdoutLines.forEach((line, index) => {
|
|
650
|
-
const timestamp = new Date(spawnedProcess.startTime.getTime() + index * 10)
|
|
651
|
-
logEntries.push({
|
|
652
|
-
type: 'stdout',
|
|
653
|
-
message: line,
|
|
654
|
-
timestamp,
|
|
655
|
-
})
|
|
656
|
-
// Log to Winston logger
|
|
657
|
-
logger.info(line)
|
|
658
|
-
})
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// Add stderr logs
|
|
662
|
-
if (spawnedProcess.output.stderr.length > 0) {
|
|
663
|
-
const stderrText = spawnedProcess.output.stderr.join('')
|
|
664
|
-
const stderrLines = stderrText.split('\n').filter(line => line.trim() !== '')
|
|
665
|
-
const stdoutCount = logEntries.filter(e => e.type === 'stdout').length
|
|
666
|
-
stderrLines.forEach((line, index) => {
|
|
667
|
-
const timestamp = new Date(spawnedProcess.startTime.getTime() + stdoutCount * 10 + index * 10)
|
|
668
|
-
logEntries.push({
|
|
669
|
-
type: 'stderr',
|
|
670
|
-
message: line,
|
|
671
|
-
timestamp,
|
|
672
|
-
})
|
|
673
|
-
// Log to Winston logger
|
|
674
|
-
logger.error(line)
|
|
675
|
-
})
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Add exit status log
|
|
679
|
-
const exitMessage = `Process exited with code ${exitCode}`
|
|
680
|
-
logEntries.push({
|
|
681
|
-
type: 'status',
|
|
682
|
-
message: exitMessage,
|
|
683
|
-
timestamp: spawnedProcess.endTime || new Date(),
|
|
684
|
-
})
|
|
685
|
-
// Log exit status to Winston logger
|
|
686
|
-
logger.info(exitMessage)
|
|
687
|
-
|
|
688
|
-
// Store logs in database
|
|
689
|
-
await storeTestRunLogsAction(testRun.runId, logEntries)
|
|
690
|
-
|
|
691
|
-
// Close Winston logger
|
|
692
|
-
await closeLogger(logger)
|
|
693
|
-
|
|
694
|
-
// Check current status before updating - preserve CANCELLED status if already set
|
|
695
|
-
const currentTestRun = await prisma.testRun.findUnique({
|
|
696
|
-
where: { id: testRun.id },
|
|
697
|
-
select: { status: true, result: true },
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
// Only update to COMPLETED if not already CANCELLED or CANCELLING
|
|
701
|
-
if (
|
|
702
|
-
currentTestRun &&
|
|
703
|
-
currentTestRun.status !== TestRunStatus.CANCELLED &&
|
|
704
|
-
currentTestRun.status !== TestRunStatus.CANCELLING
|
|
705
|
-
) {
|
|
706
|
-
// Update TestRun status based on exit code
|
|
707
|
-
const status = exitCode === 0 ? TestRunStatus.COMPLETED : TestRunStatus.COMPLETED
|
|
708
|
-
const result = exitCode === 0 ? TestRunResult.PASSED : TestRunResult.FAILED
|
|
709
|
-
|
|
710
|
-
await prisma.testRun.update({
|
|
711
|
-
where: { id: testRun.id },
|
|
712
|
-
data: {
|
|
713
|
-
status,
|
|
714
|
-
result,
|
|
715
|
-
completedAt: new Date(),
|
|
716
|
-
},
|
|
717
|
-
})
|
|
718
|
-
|
|
719
|
-
// Update metrics for the completed test run
|
|
720
|
-
try {
|
|
721
|
-
await updateMetricsForTestRun(testRun.id)
|
|
722
|
-
} catch (error) {
|
|
723
|
-
console.error(`[TestRunAction] Error updating metrics for test run ${testRun.id}:`, error)
|
|
724
|
-
// Don't fail the test run if metrics update fails
|
|
725
|
-
}
|
|
726
|
-
} else {
|
|
727
|
-
// Status is already CANCELLED or CANCELLING, just update completedAt if not set
|
|
728
|
-
if (currentTestRun && !currentTestRun.result) {
|
|
729
|
-
await prisma.testRun.update({
|
|
730
|
-
where: { id: testRun.id },
|
|
731
|
-
data: {
|
|
732
|
-
completedAt: new Date(),
|
|
733
|
-
},
|
|
734
|
-
})
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// Clean up the server-side event listener
|
|
739
|
-
cleanupListener()
|
|
740
|
-
|
|
741
|
-
// Store report in database if report path exists and test run is not cancelled
|
|
742
|
-
// Check current status again to ensure we don't generate reports for cancelled runs
|
|
743
|
-
const finalTestRunStatus = await prisma.testRun.findUnique({
|
|
744
|
-
where: { id: testRun.id },
|
|
745
|
-
select: { status: true },
|
|
746
|
-
})
|
|
747
|
-
|
|
748
|
-
if (
|
|
749
|
-
finalTestRunStatus &&
|
|
750
|
-
(finalTestRunStatus.status === TestRunStatus.CANCELLED ||
|
|
751
|
-
finalTestRunStatus.status === TestRunStatus.CANCELLING)
|
|
752
|
-
) {
|
|
753
|
-
console.log(
|
|
754
|
-
`[TestRunAction] Skipping report generation for testRunId: ${testRun.runId} - test run was cancelled`,
|
|
755
|
-
)
|
|
756
|
-
} else if (reportPath) {
|
|
757
|
-
try {
|
|
758
|
-
const { storeReportFromFile } = await import('@/actions/reports/report-actions')
|
|
759
|
-
const reportResult = await storeReportFromFile(testRun.runId, reportPath)
|
|
760
|
-
if (reportResult.status === 200) {
|
|
761
|
-
console.log(`[TestRunAction] Report stored successfully for testRunId: ${testRun.runId}`)
|
|
762
|
-
} else {
|
|
763
|
-
console.warn(
|
|
764
|
-
`[TestRunAction] Failed to store report for testRunId: ${testRun.runId}: ${reportResult.error}`,
|
|
765
|
-
)
|
|
766
|
-
}
|
|
767
|
-
} catch (error) {
|
|
768
|
-
console.error(`[TestRunAction] Error storing report for testRunId: ${testRun.runId}:`, error)
|
|
769
|
-
// Don't fail the test run if report storage fails
|
|
770
|
-
}
|
|
771
|
-
} else {
|
|
772
|
-
console.warn(`[TestRunAction] No report path available for testRunId: ${testRun.runId}`)
|
|
773
|
-
}
|
|
774
|
-
})
|
|
775
|
-
.catch(async error => {
|
|
776
|
-
console.error(`[TestRunAction] Error executing test run for testRunId: ${testRun.runId}:`, error)
|
|
777
|
-
|
|
778
|
-
// Log error to Winston logger
|
|
779
|
-
logger.error(`Error executing test run: ${error instanceof Error ? error.message : String(error)}`)
|
|
780
|
-
if (error instanceof Error && error.stack) {
|
|
781
|
-
logger.error(error.stack)
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// Close Winston logger
|
|
785
|
-
await closeLogger(logger).catch(err => {
|
|
786
|
-
console.error(`[TestRunAction] Error closing logger for testRunId: ${testRun.runId}:`, err)
|
|
787
|
-
})
|
|
788
|
-
|
|
789
|
-
// Check current status before updating - preserve CANCELLED status if already set
|
|
790
|
-
const currentTestRun = await prisma.testRun.findUnique({
|
|
791
|
-
where: { id: testRun.id },
|
|
792
|
-
select: { status: true, result: true },
|
|
793
|
-
})
|
|
794
|
-
|
|
795
|
-
// Only update to COMPLETED if not already CANCELLED or CANCELLING
|
|
796
|
-
if (
|
|
797
|
-
currentTestRun &&
|
|
798
|
-
currentTestRun.status !== TestRunStatus.CANCELLED &&
|
|
799
|
-
currentTestRun.status !== TestRunStatus.CANCELLING
|
|
800
|
-
) {
|
|
801
|
-
// Update TestRun status to indicate failure
|
|
802
|
-
await prisma.testRun.update({
|
|
803
|
-
where: { id: testRun.id },
|
|
804
|
-
data: {
|
|
805
|
-
status: TestRunStatus.COMPLETED,
|
|
806
|
-
result: TestRunResult.FAILED,
|
|
807
|
-
completedAt: new Date(),
|
|
808
|
-
},
|
|
809
|
-
})
|
|
810
|
-
} else {
|
|
811
|
-
// Status is already CANCELLED or CANCELLING, just update completedAt if not set
|
|
812
|
-
if (currentTestRun && !currentTestRun.result) {
|
|
813
|
-
await prisma.testRun.update({
|
|
814
|
-
where: { id: testRun.id },
|
|
815
|
-
data: {
|
|
816
|
-
completedAt: new Date(),
|
|
817
|
-
},
|
|
818
|
-
})
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// Clean up the server-side event listener
|
|
823
|
-
cleanupListener()
|
|
824
|
-
})
|
|
825
|
-
} catch (error) {
|
|
826
|
-
// Catch any synchronous errors
|
|
827
|
-
console.error(`[TestRunAction] Synchronous error calling executeTestRun for testRunId: ${testRun.runId}:`, error)
|
|
828
|
-
console.error(`[TestRunAction] Error stack:`, error instanceof Error ? error.stack : 'No stack trace')
|
|
829
|
-
// Note: If executeTestRun throws synchronously, the listener won't be set up, so no cleanup needed
|
|
830
|
-
}
|
|
101
|
+
const result = await createTestRunFromValidatedValue(value)
|
|
831
102
|
|
|
832
103
|
return {
|
|
833
104
|
status: 200,
|
|
105
|
+
success: true,
|
|
834
106
|
message: 'Test run created successfully',
|
|
835
|
-
data: { testRunId:
|
|
107
|
+
data: { testRunId: result.runId, id: result.id },
|
|
836
108
|
}
|
|
837
109
|
} catch (error) {
|
|
838
110
|
console.error('Error creating test run:', error)
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
return {
|
|
842
|
-
status: 400,
|
|
843
|
-
error: 'A test run with this name already exists. Please choose a different name.',
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
return {
|
|
847
|
-
status: 500,
|
|
848
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Updates a test case status and result in a test run based on scenario completion
|
|
855
|
-
* @param testRunId - The test run ID (runId, not id)
|
|
856
|
-
* @param scenarioName - The scenario name from cucumber (format: "[Test Case Title] Description")
|
|
857
|
-
* @param status - The scenario status (passed, failed, skipped)
|
|
858
|
-
* @param tracePath - Optional trace path for failed scenarios
|
|
859
|
-
*/
|
|
860
|
-
export async function updateTestRunTestCaseStatusAction(
|
|
861
|
-
testRunId: string,
|
|
862
|
-
scenario: {
|
|
863
|
-
scenarioName: string
|
|
864
|
-
status: 'passed' | 'failed' | 'skipped' | 'unknown'
|
|
865
|
-
tracePath?: string
|
|
866
|
-
featureName?: string
|
|
867
|
-
scenarioTags?: string[]
|
|
868
|
-
},
|
|
869
|
-
): Promise<ActionResponse> {
|
|
870
|
-
try {
|
|
871
|
-
// Find the test run by runId
|
|
872
|
-
const testRun = await prisma.testRun.findUnique({
|
|
873
|
-
where: { runId: testRunId },
|
|
874
|
-
include: {
|
|
875
|
-
testCases: {
|
|
876
|
-
include: {
|
|
877
|
-
testCase: {
|
|
878
|
-
include: {
|
|
879
|
-
tags: true,
|
|
880
|
-
},
|
|
881
|
-
},
|
|
882
|
-
testSuite: {
|
|
883
|
-
include: {
|
|
884
|
-
tags: true,
|
|
885
|
-
},
|
|
886
|
-
},
|
|
887
|
-
},
|
|
888
|
-
},
|
|
889
|
-
},
|
|
890
|
-
})
|
|
891
|
-
|
|
892
|
-
if (!testRun) {
|
|
893
|
-
return {
|
|
894
|
-
status: 404,
|
|
895
|
-
error: 'Test run not found',
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const matchingTestCase = findMatchingTestRunTestCase(testRun.testCases, {
|
|
900
|
-
scenarioName: scenario.scenarioName,
|
|
901
|
-
scenarioTags: scenario.scenarioTags,
|
|
902
|
-
})
|
|
903
|
-
|
|
904
|
-
if (!matchingTestCase) {
|
|
905
|
-
console.log(
|
|
906
|
-
`[TestRunAction] No matching test case found for scenario: ${scenario.scenarioName}. This is expected when scenarios run without corresponding test cases in this test run.`,
|
|
907
|
-
)
|
|
908
|
-
return {
|
|
909
|
-
status: 200,
|
|
910
|
-
message: `Scenario "${scenario.scenarioName}" completed but has no corresponding test case in this test run`,
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Map status to TestRunTestCaseStatus and TestRunTestCaseResult
|
|
915
|
-
const testCaseStatus: TestRunTestCaseStatus = TestRunTestCaseStatus.COMPLETED
|
|
916
|
-
let testCaseResult: TestRunTestCaseResult
|
|
917
|
-
|
|
918
|
-
switch (scenario.status) {
|
|
919
|
-
case 'passed':
|
|
920
|
-
testCaseResult = TestRunTestCaseResult.PASSED
|
|
921
|
-
break
|
|
922
|
-
case 'failed':
|
|
923
|
-
testCaseResult = TestRunTestCaseResult.FAILED
|
|
924
|
-
break
|
|
925
|
-
case 'skipped':
|
|
926
|
-
testCaseResult = TestRunTestCaseResult.UNTESTED // Skipped is treated as untested
|
|
927
|
-
break
|
|
928
|
-
default:
|
|
929
|
-
testCaseResult = TestRunTestCaseResult.UNTESTED
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// Update the TestRunTestCase
|
|
933
|
-
await prisma.testRunTestCase.update({
|
|
934
|
-
where: { id: matchingTestCase.id },
|
|
935
|
-
data: {
|
|
936
|
-
status: testCaseStatus,
|
|
937
|
-
result: testCaseResult,
|
|
938
|
-
tracePath: scenario.tracePath || null,
|
|
939
|
-
},
|
|
940
|
-
})
|
|
941
|
-
|
|
942
|
-
// Update test case metrics
|
|
943
|
-
try {
|
|
944
|
-
await updateTestCaseMetrics(
|
|
945
|
-
matchingTestCase.testCaseId,
|
|
946
|
-
testCaseResult,
|
|
947
|
-
testRun.completedAt || testRun.startedAt || new Date(),
|
|
948
|
-
)
|
|
949
|
-
} catch (error) {
|
|
950
|
-
console.error(`[TestRunAction] Error updating metrics for test case ${matchingTestCase.testCaseId}:`, error)
|
|
951
|
-
// Don't fail the action if metrics update fails
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
return {
|
|
955
|
-
status: 200,
|
|
956
|
-
message: 'Test case status updated successfully',
|
|
957
|
-
}
|
|
958
|
-
} catch (error) {
|
|
959
|
-
console.error(
|
|
960
|
-
`[TestRunAction] Error updating test case status for testRunId: ${testRunId}, scenario: ${scenario.scenarioName}:`,
|
|
961
|
-
error,
|
|
962
|
-
)
|
|
963
|
-
return {
|
|
964
|
-
status: 500,
|
|
965
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
111
|
+
if (error instanceof ServiceError) {
|
|
112
|
+
return serviceErrorToActionResponse(error)
|
|
966
113
|
}
|
|
114
|
+
return unknownErrorToActionResponse(error)
|
|
967
115
|
}
|
|
968
116
|
}
|
|
969
117
|
|
|
970
|
-
/**
|
|
971
|
-
* Checks if a trace viewer is currently running for a test case
|
|
972
|
-
* @param testRunId - The test run ID (runId, not id)
|
|
973
|
-
* @param testCaseId - The test case ID (TestRunTestCase id, not TestCase id)
|
|
974
|
-
* @returns ActionResponse with isRunning status
|
|
975
|
-
*/
|
|
976
118
|
export async function checkTraceViewerStatusAction(testRunId: string, testCaseId: string): Promise<ActionResponse> {
|
|
977
119
|
try {
|
|
978
|
-
|
|
979
|
-
const testRun = await prisma.testRun.findUnique({
|
|
980
|
-
where: { runId: testRunId },
|
|
981
|
-
include: {
|
|
982
|
-
testCases: {
|
|
983
|
-
where: { id: testCaseId },
|
|
984
|
-
},
|
|
985
|
-
},
|
|
986
|
-
})
|
|
120
|
+
const outcome = await checkTraceViewerStatusService(testRunId, testCaseId)
|
|
987
121
|
|
|
988
|
-
if (
|
|
122
|
+
if (outcome.kind === 'test_run_not_found') {
|
|
989
123
|
return {
|
|
990
124
|
status: 404,
|
|
125
|
+
success: false,
|
|
991
126
|
error: 'Test run not found',
|
|
992
127
|
}
|
|
993
128
|
}
|
|
994
129
|
|
|
995
|
-
|
|
996
|
-
const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
|
|
997
|
-
if (!testRunTestCase) {
|
|
130
|
+
if (outcome.kind === 'test_case_not_in_run') {
|
|
998
131
|
return {
|
|
999
132
|
status: 404,
|
|
133
|
+
success: false,
|
|
1000
134
|
error: 'Test case not found in this test run',
|
|
1001
135
|
}
|
|
1002
136
|
}
|
|
1003
137
|
|
|
1004
|
-
// Check if trace viewer process is running
|
|
1005
|
-
const processName = `trace-viewer-${testCaseId}`
|
|
1006
|
-
const process = localExecutorAdapter.getProcess(processName)
|
|
1007
|
-
const isRunning = process?.isRunning ?? false
|
|
1008
|
-
|
|
1009
138
|
return {
|
|
1010
139
|
status: 200,
|
|
140
|
+
success: true,
|
|
1011
141
|
data: {
|
|
1012
|
-
isRunning,
|
|
1013
|
-
processName:
|
|
142
|
+
isRunning: outcome.isRunning,
|
|
143
|
+
processName: outcome.processName,
|
|
1014
144
|
},
|
|
1015
145
|
}
|
|
1016
146
|
} catch (error) {
|
|
@@ -1018,84 +148,52 @@ export async function checkTraceViewerStatusAction(testRunId: string, testCaseId
|
|
|
1018
148
|
`[TestRunAction] Error checking trace viewer status for testRunId: ${testRunId}, testCaseId: ${testCaseId}:`,
|
|
1019
149
|
error,
|
|
1020
150
|
)
|
|
1021
|
-
return
|
|
1022
|
-
status: 500,
|
|
1023
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1024
|
-
}
|
|
151
|
+
return unknownErrorToActionResponse(error)
|
|
1025
152
|
}
|
|
1026
153
|
}
|
|
1027
154
|
|
|
1028
|
-
/**
|
|
1029
|
-
* Spawns Playwright trace viewer for a failed test case
|
|
1030
|
-
* @param testRunId - The test run ID (runId, not id)
|
|
1031
|
-
* @param testCaseId - The test case ID (TestRunTestCase id, not TestCase id)
|
|
1032
|
-
* @returns ActionResponse indicating success or failure
|
|
1033
|
-
*/
|
|
1034
155
|
export async function spawnTraceViewerAction(testRunId: string, testCaseId: string): Promise<ActionResponse> {
|
|
1035
156
|
try {
|
|
1036
|
-
|
|
1037
|
-
const testRun = await prisma.testRun.findUnique({
|
|
1038
|
-
where: { runId: testRunId },
|
|
1039
|
-
include: {
|
|
1040
|
-
testCases: {
|
|
1041
|
-
where: { id: testCaseId },
|
|
1042
|
-
include: {
|
|
1043
|
-
testCase: true,
|
|
1044
|
-
},
|
|
1045
|
-
},
|
|
1046
|
-
},
|
|
1047
|
-
})
|
|
157
|
+
const outcome = await spawnTraceViewerService(testRunId, testCaseId)
|
|
1048
158
|
|
|
1049
|
-
if (
|
|
159
|
+
if (outcome.kind === 'test_run_not_found') {
|
|
1050
160
|
return {
|
|
1051
161
|
status: 404,
|
|
162
|
+
success: false,
|
|
1052
163
|
error: 'Test run not found',
|
|
1053
164
|
}
|
|
1054
165
|
}
|
|
1055
166
|
|
|
1056
|
-
|
|
1057
|
-
const testRunTestCase = testRun.testCases.find(tc => tc.id === testCaseId)
|
|
1058
|
-
if (!testRunTestCase) {
|
|
167
|
+
if (outcome.kind === 'test_case_not_in_run') {
|
|
1059
168
|
return {
|
|
1060
169
|
status: 404,
|
|
170
|
+
success: false,
|
|
1061
171
|
error: 'Test case not found in this test run',
|
|
1062
172
|
}
|
|
1063
173
|
}
|
|
1064
174
|
|
|
1065
|
-
|
|
1066
|
-
const tracePath = testRunTestCase.tracePath
|
|
1067
|
-
if (!tracePath) {
|
|
175
|
+
if (outcome.kind === 'no_trace_path') {
|
|
1068
176
|
return {
|
|
1069
177
|
status: 400,
|
|
178
|
+
success: false,
|
|
1070
179
|
error: 'No trace path available for this test case',
|
|
1071
180
|
}
|
|
1072
181
|
}
|
|
1073
182
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
// Validate trace file exists
|
|
1077
|
-
try {
|
|
1078
|
-
await fs.access(absoluteTracePath)
|
|
1079
|
-
} catch {
|
|
183
|
+
if (outcome.kind === 'trace_file_missing') {
|
|
1080
184
|
return {
|
|
1081
185
|
status: 404,
|
|
1082
|
-
|
|
186
|
+
success: false,
|
|
187
|
+
error: `Trace file not found at path: ${outcome.path}`,
|
|
1083
188
|
}
|
|
1084
189
|
}
|
|
1085
190
|
|
|
1086
|
-
// Spawn playwright show-trace command
|
|
1087
|
-
// The process is self-closing when the user closes the trace viewer
|
|
1088
|
-
const spawnedProcess = await localExecutorAdapter.spawnTraceViewer(testCaseId, absoluteTracePath)
|
|
1089
|
-
|
|
1090
|
-
console.log(
|
|
1091
|
-
`[TestRunAction] Spawned trace viewer process for testCaseId: ${testCaseId}, tracePath: ${absoluteTracePath}`,
|
|
1092
|
-
)
|
|
1093
|
-
|
|
1094
191
|
return {
|
|
1095
192
|
status: 200,
|
|
193
|
+
success: true,
|
|
1096
194
|
message: 'Trace viewer launched successfully',
|
|
1097
195
|
data: {
|
|
1098
|
-
processName:
|
|
196
|
+
processName: outcome.processName,
|
|
1099
197
|
},
|
|
1100
198
|
}
|
|
1101
199
|
} catch (error) {
|
|
@@ -1103,174 +201,69 @@ export async function spawnTraceViewerAction(testRunId: string, testCaseId: stri
|
|
|
1103
201
|
`[TestRunAction] Error spawning trace viewer for testRunId: ${testRunId}, testCaseId: ${testCaseId}:`,
|
|
1104
202
|
error,
|
|
1105
203
|
)
|
|
1106
|
-
return
|
|
1107
|
-
status: 500,
|
|
1108
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1109
|
-
}
|
|
204
|
+
return unknownErrorToActionResponse(error)
|
|
1110
205
|
}
|
|
1111
206
|
}
|
|
1112
207
|
|
|
1113
208
|
export async function cancelTestRunAction(testRunId: string): Promise<ActionResponse> {
|
|
1114
209
|
try {
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
if (!testRun) {
|
|
210
|
+
const outcome = await cancelTestRunService(testRunId)
|
|
211
|
+
|
|
212
|
+
if (outcome.kind === 'not_found') {
|
|
1119
213
|
return {
|
|
1120
214
|
status: 404,
|
|
215
|
+
success: false,
|
|
1121
216
|
error: 'Test run not found',
|
|
1122
217
|
}
|
|
1123
218
|
}
|
|
1124
219
|
|
|
1125
|
-
if (
|
|
1126
|
-
testRun.status !== TestRunStatus.RUNNING &&
|
|
1127
|
-
testRun.status !== TestRunStatus.QUEUED &&
|
|
1128
|
-
testRun.status !== TestRunStatus.CANCELLING
|
|
1129
|
-
) {
|
|
220
|
+
if (outcome.kind === 'invalid_state') {
|
|
1130
221
|
return {
|
|
1131
222
|
status: 400,
|
|
1132
|
-
|
|
223
|
+
success: false,
|
|
224
|
+
error: outcome.message,
|
|
1133
225
|
}
|
|
1134
226
|
}
|
|
1135
227
|
|
|
1136
|
-
|
|
1137
|
-
if (testRun.status === TestRunStatus.CANCELLING) {
|
|
228
|
+
if (outcome.kind === 'already_cancelling') {
|
|
1138
229
|
return {
|
|
1139
230
|
status: 200,
|
|
231
|
+
success: true,
|
|
1140
232
|
message: 'Test run cancellation is already in progress',
|
|
1141
233
|
}
|
|
1142
234
|
}
|
|
1143
235
|
|
|
1144
|
-
|
|
1145
|
-
await prisma.testRun.update({
|
|
1146
|
-
where: { id: testRun.id },
|
|
1147
|
-
data: {
|
|
1148
|
-
status: TestRunStatus.CANCELLING,
|
|
1149
|
-
},
|
|
1150
|
-
})
|
|
1151
|
-
|
|
1152
|
-
const process = processManager.get(testRunId)
|
|
1153
|
-
console.log(`[TestRunAction] Process: ${JSON.stringify(process)}`)
|
|
1154
|
-
|
|
1155
|
-
if (!process) {
|
|
1156
|
-
console.warn(`[TestRunAction] No process found for testRunId: ${testRunId}`)
|
|
1157
|
-
await prisma.testRun.update({
|
|
1158
|
-
where: { id: testRun.id },
|
|
1159
|
-
data: {
|
|
1160
|
-
status: TestRunStatus.CANCELLED,
|
|
1161
|
-
result: TestRunResult.CANCELLED,
|
|
1162
|
-
completedAt: new Date(),
|
|
1163
|
-
},
|
|
1164
|
-
})
|
|
236
|
+
if (outcome.kind === 'cancelled_no_process') {
|
|
1165
237
|
return {
|
|
1166
238
|
status: 200,
|
|
239
|
+
success: true,
|
|
1167
240
|
message: 'Test run cancelled successfully',
|
|
1168
241
|
}
|
|
1169
242
|
}
|
|
1170
243
|
|
|
1171
|
-
const killed = localExecutorAdapter.killProcess(process.name, 'SIGTERM')
|
|
1172
|
-
console.log(`[TestRunAction] Killed: ${killed}`)
|
|
1173
|
-
if (!killed) {
|
|
1174
|
-
const forceKilled = localExecutorAdapter.killProcess(process.name, 'SIGKILL')
|
|
1175
|
-
if (!forceKilled) {
|
|
1176
|
-
console.warn(`[TestRunAction] Failed to force kill process for testRunId: ${testRunId}`)
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
await prisma.testRun.update({
|
|
1181
|
-
where: { id: testRun.id },
|
|
1182
|
-
data: {
|
|
1183
|
-
status: TestRunStatus.CANCELLED,
|
|
1184
|
-
result: TestRunResult.CANCELLED,
|
|
1185
|
-
completedAt: new Date(),
|
|
1186
|
-
},
|
|
1187
|
-
})
|
|
1188
|
-
|
|
1189
|
-
await prisma.testRunTestCase.updateMany({
|
|
1190
|
-
where: {
|
|
1191
|
-
testRunId: testRun.id,
|
|
1192
|
-
status: {
|
|
1193
|
-
in: [TestRunTestCaseStatus.PENDING, TestRunTestCaseStatus.RUNNING],
|
|
1194
|
-
},
|
|
1195
|
-
},
|
|
1196
|
-
data: {
|
|
1197
|
-
status: TestRunTestCaseStatus.CANCELLED,
|
|
1198
|
-
result: TestRunTestCaseResult.UNTESTED,
|
|
1199
|
-
},
|
|
1200
|
-
})
|
|
1201
|
-
|
|
1202
244
|
revalidatePath('/test-runs')
|
|
1203
245
|
revalidatePath(`/test-runs/${testRunId}`)
|
|
1204
246
|
|
|
1205
247
|
return {
|
|
1206
248
|
status: 200,
|
|
249
|
+
success: true,
|
|
1207
250
|
message: 'Test run stopped successfully',
|
|
1208
251
|
}
|
|
1209
252
|
} catch (error) {
|
|
1210
253
|
console.error(`[TestRunAction] Error stopping test run ${testRunId}:`, error)
|
|
1211
|
-
return
|
|
1212
|
-
status: 500,
|
|
1213
|
-
error: `Server error occurred: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
export async function getMostRecentTestRunAction(): Promise<ActionResponse> {
|
|
1219
|
-
try {
|
|
1220
|
-
const testRun = await prisma.testRun.findFirst({
|
|
1221
|
-
orderBy: { completedAt: 'desc' },
|
|
1222
|
-
where: {
|
|
1223
|
-
completedAt: { not: null },
|
|
1224
|
-
status: TestRunStatus.COMPLETED,
|
|
1225
|
-
},
|
|
1226
|
-
include: {
|
|
1227
|
-
testCases: {
|
|
1228
|
-
include: {
|
|
1229
|
-
testCase: {
|
|
1230
|
-
include: {
|
|
1231
|
-
metrics: true, // Include metrics if needed
|
|
1232
|
-
},
|
|
1233
|
-
},
|
|
1234
|
-
},
|
|
1235
|
-
},
|
|
1236
|
-
environment: true,
|
|
1237
|
-
tags: true,
|
|
1238
|
-
},
|
|
1239
|
-
})
|
|
1240
|
-
|
|
1241
|
-
if (!testRun) {
|
|
1242
|
-
return {
|
|
1243
|
-
status: 404,
|
|
1244
|
-
error: 'No completed test run found',
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
return {
|
|
1249
|
-
status: 200,
|
|
1250
|
-
data: testRun,
|
|
1251
|
-
}
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
return {
|
|
1254
|
-
status: 500,
|
|
1255
|
-
error: `Server error occurred: ${error}`,
|
|
1256
|
-
}
|
|
254
|
+
return unknownErrorToActionResponse(error)
|
|
1257
255
|
}
|
|
1258
256
|
}
|
|
1259
257
|
|
|
1260
|
-
/**
|
|
1261
|
-
* Check if a test run name is unique
|
|
1262
|
-
*/
|
|
1263
258
|
export async function checkTestRunNameUniqueAction(name: string, excludeId?: string): Promise<ActionResponse> {
|
|
1264
259
|
try {
|
|
1265
|
-
const nameExists = await
|
|
260
|
+
const nameExists = await isTestRunNameTaken(name, excludeId)
|
|
1266
261
|
return {
|
|
1267
262
|
status: 200,
|
|
263
|
+
success: true,
|
|
1268
264
|
data: { isUnique: !nameExists },
|
|
1269
265
|
}
|
|
1270
266
|
} catch (error) {
|
|
1271
|
-
return
|
|
1272
|
-
status: 500,
|
|
1273
|
-
error: `Server error occurred: ${error}`,
|
|
1274
|
-
}
|
|
267
|
+
return unknownErrorToActionResponse(error)
|
|
1275
268
|
}
|
|
1276
269
|
}
|