From 448ebefc8789498d1d64a394c94505d93e356ed9 Mon Sep 17 00:00:00 2001 From: dyoganan_ford Date: Thu, 26 Feb 2026 19:56:20 +0530 Subject: [PATCH 1/2] collection runner - implement selction based interface --- packages/hoppscotch-common/locales/en.json | 8 +- .../hoppscotch-common/src/components.d.ts | 2 + .../http/test/RequestSelectionTree.vue | 161 ++++++++++ .../http/test/RequestSelectionTreeNode.vue | 274 ++++++++++++++++++ .../src/components/http/test/RunnerModal.vue | 34 ++- .../src/helpers/rest/document.ts | 7 + .../test-runner/test-runner.service.ts | 35 +++ 7 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 packages/hoppscotch-common/src/components/http/test/RequestSelectionTree.vue create mode 100644 packages/hoppscotch-common/src/components/http/test/RequestSelectionTreeNode.vue diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index c99c86bd680..39cb9dabf96 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -1943,7 +1943,13 @@ "invalid_json" : "Invalid JSON file format", "dataset_iterations_info" : "Dataset contains {rows} rows, running {total} iterations as per your configuration.", "dataset_iterations_exceeds" : "Dataset contains {rows} rows, but {extra} iterations will be run as per your configuration.", - "no_failed_tests": "No tests failed" + "no_failed_tests": "No tests failed", + "request_selection": "Request Selection", + "select_requests": "Select Requests to Run", + "select_all": "Select All", + "deselect_all": "Deselect All", + "run_selected": "Run Selected", + "all_requests_selected": "All requests selected" }, "ai_experiments": { "generate_request_name": "Generate Request Name Using AI", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index d0329b81c8d..7dce961fe9e 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -216,6 +216,8 @@ declare module 'vue' { HttpTestEnv: typeof import('./components/http/test/Env.vue')['default'] HttpTestFolder: typeof import('./components/http/test/Folder.vue')['default'] HttpTestRequest: typeof import('./components/http/test/Request.vue')['default'] + HttpTestRequestSelectionTree: typeof import('./components/http/test/RequestSelectionTree.vue')['default'] + HttpTestRequestSelectionTreeNode: typeof import('./components/http/test/RequestSelectionTreeNode.vue')['default'] HttpTestResponse: typeof import('./components/http/test/Response.vue')['default'] HttpTestResult: typeof import('./components/http/TestResult.vue')['default'] HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/http/test/RequestSelectionTree.vue b/packages/hoppscotch-common/src/components/http/test/RequestSelectionTree.vue new file mode 100644 index 00000000000..277637eca27 --- /dev/null +++ b/packages/hoppscotch-common/src/components/http/test/RequestSelectionTree.vue @@ -0,0 +1,161 @@ + + + diff --git a/packages/hoppscotch-common/src/components/http/test/RequestSelectionTreeNode.vue b/packages/hoppscotch-common/src/components/http/test/RequestSelectionTreeNode.vue new file mode 100644 index 00000000000..cc4d4fd488d --- /dev/null +++ b/packages/hoppscotch-common/src/components/http/test/RequestSelectionTreeNode.vue @@ -0,0 +1,274 @@ + + + diff --git a/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue b/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue index f285f394b59..97b5f315384 100644 --- a/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue +++ b/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue @@ -183,6 +183,19 @@ + +
+

+ {{ t("collection_runner.request_selection") }} +

+ +
+
{{ t("collection_runner.advanced_settings") }} @@ -379,9 +392,10 @@ import { useI18n } from "~/composables/i18n" import { HoppCollection } from "@hoppscotch/data" import { useService } from "dioc/vue" import { useToast } from "~/composables/toast" -import { TestRunnerConfig } from "~/helpers/rest/document" +import { TestRunnerConfig, RequestSelectionState } from "~/helpers/rest/document" import { copyToClipboard } from "~/helpers/utils/clipboard" import { RESTTabService } from "~/services/tab/rest" +import RequestSelectionTree from "./RequestSelectionTree.vue" import { parseCSV, parseJSON, @@ -463,12 +477,30 @@ const config = ref({ keepVariableValues: true, }) +const requestSelection = ref({}) +const collectionTreeForSelection = ref(null) + onMounted(async () => { if (props.prevConfig) { config.value = { ...config.value, ...props.prevConfig } } + + // Load collection tree for request selection + const tree = await getCollectionTree( + props.collectionRunnerData.type, + props.collectionRunnerData.collectionID + ) + + if (tree) { + collectionTreeForSelection.value = tree + } }) +// Sync requestSelection with config +watch(requestSelection, (newSelection) => { + config.value.requestSelection = newSelection +}, { deep: true }) + const runTests = async () => { const collectionTree = await getCollectionTree( props.collectionRunnerData.type, diff --git a/packages/hoppscotch-common/src/helpers/rest/document.ts b/packages/hoppscotch-common/src/helpers/rest/document.ts index bfff60723cd..a1d2d17590a 100644 --- a/packages/hoppscotch-common/src/helpers/rest/document.ts +++ b/packages/hoppscotch-common/src/helpers/rest/document.ts @@ -95,6 +95,12 @@ export type HoppCollectionSaveContext = } | null +export type RequestSelectionState = { + // Map of request path to selection state + // Path format: "folder_0/folder_1/request_2" or "request_0" for root-level requests + [path: string]: boolean +} + export type TestRunnerConfig = { iterations: number delay: number @@ -108,6 +114,7 @@ export type TestRunnerConfig = { rawContent?: string // Store the original file content for persistence fileName?: string // Store the original file name } + requestSelection?: RequestSelectionState // Track selected/unselected requests } export type HoppTestRunnerDocument = { diff --git a/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts b/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts index 6b9c0fc84fc..469fb389450 100644 --- a/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts +++ b/packages/hoppscotch-common/src/services/test-runner/test-runner.service.ts @@ -226,6 +226,17 @@ export class TestRunnerService extends Service { throw new Error("Test execution stopped") } + // Check if this request should be executed based on selection state + const requestPath = this.buildRequestPath(parentPath, i) + const shouldExecute = this.shouldExecuteRequest( + requestPath, + options.requestSelection + ) + + if (!shouldExecute) { + continue // Skip this request if not selected + } + const request = collection.requests[i] as TestRunnerRequest const currentPath = [...parentPath, i] @@ -507,4 +518,28 @@ export class TestRunnerService extends Service { return { passed, failed } } + + /** + * Builds a request path string from a path array + * Example: [0, 1, 2] -> "folder_0/folder_1/request_2" + */ + private buildRequestPath(parentPath: number[], requestIndex: number): string { + const folderPath = parentPath.map((idx) => `folder_${idx}`).join("/") + const requestPath = `request_${requestIndex}` + return folderPath ? `${folderPath}/${requestPath}` : requestPath + } + + /** + * Checks if a request should be executed based on selection state + * If no selection state is provided, all requests are executed + */ + private shouldExecuteRequest( + requestPath: string, + selectionState?: Record + ): boolean { + if (!selectionState || Object.keys(selectionState).length === 0) { + return true // Execute all if no selection state + } + return selectionState[requestPath] ?? false + } } From e4b50e8e22dcd7269b5b1e13018ff7507dc00573 Mon Sep 17 00:00:00 2001 From: dyoganan_ford Date: Tue, 3 Mar 2026 21:02:13 +0530 Subject: [PATCH 2/2] feat(collection-runner): selection-based request execution with drag-to-reorder --- packages/hoppscotch-common/locales/en.json | 10 +- .../hoppscotch-common/src/components.d.ts | 1 + .../components/http/test/RequestRunOrder.vue | 269 ++++++++++++++++++ .../src/components/http/test/RunnerModal.vue | 97 +++++-- .../src/helpers/rest/document.ts | 1 + .../test-runner/test-runner.service.ts | 221 +++++++++++++- 6 files changed, 569 insertions(+), 30 deletions(-) create mode 100644 packages/hoppscotch-common/src/components/http/test/RequestRunOrder.vue diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index 39cb9dabf96..aaec37eb7e3 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -1949,7 +1949,15 @@ "select_all": "Select All", "deselect_all": "Deselect All", "run_selected": "Run Selected", - "all_requests_selected": "All requests selected" + "all_requests_selected": "All requests selected", + "expand_modal": "Expand", + "collapse_modal": "Collapse", + "loading_requests": "Loading requests...", + "select_tab": "Select", + "order_tab": "Order", + "drag_to_reorder": "Drag to reorder", + "will_run": "will run", + "reset_order": "Reset" }, "ai_experiments": { "generate_request_name": "Generate Request Name Using AI", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 7dce961fe9e..8b6a2cea7b7 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -216,6 +216,7 @@ declare module 'vue' { HttpTestEnv: typeof import('./components/http/test/Env.vue')['default'] HttpTestFolder: typeof import('./components/http/test/Folder.vue')['default'] HttpTestRequest: typeof import('./components/http/test/Request.vue')['default'] + HttpTestRequestRunOrder: typeof import('./components/http/test/RequestRunOrder.vue')['default'] HttpTestRequestSelectionTree: typeof import('./components/http/test/RequestSelectionTree.vue')['default'] HttpTestRequestSelectionTreeNode: typeof import('./components/http/test/RequestSelectionTreeNode.vue')['default'] HttpTestResponse: typeof import('./components/http/test/Response.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/http/test/RequestRunOrder.vue b/packages/hoppscotch-common/src/components/http/test/RequestRunOrder.vue new file mode 100644 index 00000000000..5897afd4566 --- /dev/null +++ b/packages/hoppscotch-common/src/components/http/test/RequestRunOrder.vue @@ -0,0 +1,269 @@ + + + diff --git a/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue b/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue index 97b5f315384..1f5813a956e 100644 --- a/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue +++ b/packages/hoppscotch-common/src/components/http/test/RunnerModal.vue @@ -3,14 +3,38 @@ dialog :title="t('collection_runner.run_collection')" :full-width-body="true" + :styles="modalStyles" @close="closeModal" >