feat: collection runner (#3600)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com> Co-authored-by: nivedin <nivedinp@gmail.com>
This commit is contained in:
@@ -89,6 +89,9 @@ export class ParameterMenuService extends Service implements ContextMenu {
|
||||
|
||||
const tabService = getService(RESTTabService)
|
||||
|
||||
if (tabService.currentActiveTab.value.document.type === "test-runner")
|
||||
return
|
||||
|
||||
const currentActiveRequest =
|
||||
tabService.currentActiveTab.value.document.type === "request"
|
||||
? tabService.currentActiveTab.value.document.request
|
||||
|
||||
@@ -55,9 +55,9 @@ export class URLMenuService extends Service implements ContextMenu {
|
||||
}
|
||||
|
||||
this.restTab.createNewTab({
|
||||
type: "request",
|
||||
request: request,
|
||||
isDirty: false,
|
||||
type: "request",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { computed, markRaw, reactive } from "vue"
|
||||
import { Component, Ref, ref, watch } from "vue"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { RESTTabService } from "../tab/rest"
|
||||
|
||||
/**
|
||||
* Defines how to render the text in an Inspector Result
|
||||
*/
|
||||
@@ -127,18 +126,24 @@ export class InspectionService extends Service {
|
||||
watch(
|
||||
() => [this.inspectors.entries(), this.restTab.currentActiveTab.value.id],
|
||||
() => {
|
||||
const currentTabRequest = computed(() =>
|
||||
this.restTab.currentActiveTab.value.document.type === "request"
|
||||
const currentTabRequest = computed(() => {
|
||||
if (
|
||||
this.restTab.currentActiveTab.value.document.type === "test-runner"
|
||||
)
|
||||
return null
|
||||
|
||||
return this.restTab.currentActiveTab.value.document.type === "request"
|
||||
? this.restTab.currentActiveTab.value.document.request
|
||||
: this.restTab.currentActiveTab.value.document.response
|
||||
.originalRequest
|
||||
)
|
||||
})
|
||||
|
||||
const currentTabResponse = computed(() =>
|
||||
this.restTab.currentActiveTab.value.document.type === "request"
|
||||
? this.restTab.currentActiveTab.value.document.response
|
||||
: null
|
||||
)
|
||||
const currentTabResponse = computed(() => {
|
||||
if (this.restTab.currentActiveTab.value.document.type === "request") {
|
||||
return this.restTab.currentActiveTab.value.document.response
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const reqRef = computed(() => currentTabRequest.value)
|
||||
const resRef = computed(() => currentTabResponse.value)
|
||||
@@ -147,6 +152,7 @@ export class InspectionService extends Service {
|
||||
const debouncedRes = refDebounced(resRef, 1000, { maxWait: 2000 })
|
||||
|
||||
const inspectorRefs = Array.from(this.inspectors.values()).map((x) =>
|
||||
// @ts-expect-error - This is a valid call
|
||||
x.getInspections(debouncedReq, debouncedRes)
|
||||
)
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ export class AuthorizationInspectorService
|
||||
const activeTabDocument =
|
||||
this.restTabService.currentActiveTab.value.document
|
||||
|
||||
if (activeTabDocument.type === "example-response") {
|
||||
if (
|
||||
activeTabDocument.type === "example-response" ||
|
||||
activeTabDocument.type === "test-runner"
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -60,6 +63,7 @@ export class AuthorizationInspectorService
|
||||
req: Readonly<Ref<HoppRESTRequest | HoppRESTResponseOriginalRequest>>
|
||||
) {
|
||||
return computed(() => {
|
||||
if (!req.value) return []
|
||||
const currentInterceptorIDValue =
|
||||
this.interceptorService.currentInterceptorID.value
|
||||
|
||||
|
||||
@@ -77,10 +77,12 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.response.originalRequest
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
|
||||
const environmentVariables = [
|
||||
...currentTabRequest.requestVariables,
|
||||
...(currentTabRequest?.requestVariables ?? []),
|
||||
...this.aggregateEnvsWithSecrets.value,
|
||||
]
|
||||
|
||||
@@ -191,11 +193,13 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.response.originalRequest
|
||||
: currentTab.document.type === "example-response"
|
||||
? currentTab.document.response.originalRequest
|
||||
: null
|
||||
|
||||
const environmentVariables =
|
||||
this.filterNonEmptyEnvironmentVariables([
|
||||
...currentTabRequest.requestVariables.map((env) => ({
|
||||
...(currentTabRequest?.requestVariables ?? []).map((env) => ({
|
||||
...env,
|
||||
secret: false,
|
||||
sourceEnv: "RequestVariable",
|
||||
@@ -300,6 +304,8 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
||||
return computed(() => {
|
||||
const results: InspectorResult[] = []
|
||||
|
||||
if (!req.value) return results
|
||||
|
||||
const headers = req.value.headers
|
||||
|
||||
const params = req.value.params
|
||||
|
||||
@@ -40,6 +40,9 @@ export class HeaderInspectorService extends Service implements Inspector {
|
||||
) {
|
||||
return computed(() => {
|
||||
const results: InspectorResult[] = []
|
||||
|
||||
if (!req.value) return results
|
||||
|
||||
const headers = req.value.headers
|
||||
|
||||
const headerKeys = Object.values(headers).map((header) => header.key)
|
||||
|
||||
@@ -25,7 +25,7 @@ const DEFAULT_SETTINGS = getDefaultSettings()
|
||||
|
||||
export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 4,
|
||||
v: 5,
|
||||
name: "Echo",
|
||||
folders: [],
|
||||
requests: [
|
||||
@@ -51,7 +51,7 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
|
||||
export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
|
||||
{
|
||||
v: 4,
|
||||
v: 5,
|
||||
name: "Echo",
|
||||
folders: [],
|
||||
requests: [
|
||||
|
||||
@@ -698,22 +698,22 @@ export class PersistenceService extends Service {
|
||||
|
||||
try {
|
||||
if (restTabStateData) {
|
||||
let parsedGqlTabStateData = JSON.parse(restTabStateData)
|
||||
let parsedRESTTabStateData = JSON.parse(restTabStateData)
|
||||
|
||||
// Validate data read from localStorage
|
||||
const result = REST_TAB_STATE_SCHEMA.safeParse(parsedGqlTabStateData)
|
||||
const result = REST_TAB_STATE_SCHEMA.safeParse(parsedRESTTabStateData)
|
||||
|
||||
if (result.success) {
|
||||
parsedGqlTabStateData = result.data
|
||||
parsedRESTTabStateData = result.data
|
||||
} else {
|
||||
this.showErrorToast(restTabStateKey)
|
||||
window.localStorage.setItem(
|
||||
`${restTabStateKey}-backup`,
|
||||
JSON.stringify(parsedGqlTabStateData)
|
||||
JSON.stringify(parsedRESTTabStateData)
|
||||
)
|
||||
}
|
||||
|
||||
this.restTabService.loadTabsFromPersistedState(parsedGqlTabStateData)
|
||||
this.restTabService.loadTabsFromPersistedState(parsedRESTTabStateData)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
HoppRESTRequest,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequestResponse,
|
||||
HoppCollection,
|
||||
} from "@hoppscotch/data"
|
||||
import { entityReference } from "verzod"
|
||||
import { z } from "zod"
|
||||
@@ -75,36 +76,13 @@ const SettingsDefSchema = z.object({
|
||||
ENABLE_AI_EXPERIMENTS: z.optional(z.boolean()),
|
||||
})
|
||||
|
||||
// Common properties shared across REST & GQL collections
|
||||
const HoppCollectionSchemaCommonProps = z
|
||||
.object({
|
||||
v: z.number(),
|
||||
name: z.string(),
|
||||
id: z.optional(z.string()),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const HoppRESTRequestSchema = entityReference(HoppRESTRequest)
|
||||
|
||||
const HoppGQLRequestSchema = entityReference(HoppGQLRequest)
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppRESTCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppRESTCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppRESTRequestSchema)),
|
||||
const HoppRESTCollectionSchema = entityReference(HoppCollection)
|
||||
|
||||
auth: z.optional(HoppRESTAuth),
|
||||
headers: z.optional(HoppRESTHeaders),
|
||||
}).strict()
|
||||
|
||||
// @ts-expect-error recursive schema
|
||||
const HoppGQLCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
||||
folders: z.array(z.lazy(() => HoppGQLCollectionSchema)),
|
||||
requests: z.optional(z.array(HoppGQLRequestSchema)),
|
||||
|
||||
auth: z.optional(HoppGQLAuth),
|
||||
headers: z.optional(z.array(GQLHeader)),
|
||||
}).strict()
|
||||
const HoppGQLCollectionSchema = entityReference(HoppCollection)
|
||||
|
||||
export const VUEX_SCHEMA = z.object({
|
||||
postwoman: z.optional(
|
||||
@@ -551,6 +529,33 @@ export const REST_TAB_STATE_SCHEMA = z
|
||||
saveContext: z.optional(HoppRESTSaveContextSchema),
|
||||
isDirty: z.boolean(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("test-runner").catch("test-runner"),
|
||||
config: z.object({
|
||||
delay: z.number(),
|
||||
iterations: z.number(),
|
||||
keepVariableValues: z.boolean(),
|
||||
persistResponses: z.boolean(),
|
||||
stopOnError: z.boolean(),
|
||||
}),
|
||||
status: z.enum(["idle", "running", "stopped", "error"]),
|
||||
collection: HoppRESTCollectionSchema,
|
||||
collectionType: z.enum(["my-collections", "team-collections"]),
|
||||
collectionID: z.optional(z.string()),
|
||||
resultCollection: z.optional(HoppRESTCollectionSchema),
|
||||
testRunnerMeta: z.object({
|
||||
totalRequests: z.number(),
|
||||
completedRequests: z.number(),
|
||||
totalTests: z.number(),
|
||||
passedTests: z.number(),
|
||||
failedTests: z.number(),
|
||||
totalTime: z.number(),
|
||||
}),
|
||||
request: z.nullable(entityReference(HoppRESTRequest)),
|
||||
response: z.nullable(HoppRESTResponseSchema),
|
||||
testResults: z.optional(z.nullable(HoppTestResultSchema)),
|
||||
isDirty: z.boolean(),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
),
|
||||
|
||||
@@ -325,9 +325,9 @@ export class CollectionsSpotlightSearcherService
|
||||
|
||||
this.restTab.createNewTab(
|
||||
{
|
||||
type: "request",
|
||||
request: req,
|
||||
isDirty: false,
|
||||
type: "request",
|
||||
saveContext: {
|
||||
originLocation: "user-collection",
|
||||
folderPath: folderPath.join("/"),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Container } from "dioc"
|
||||
import { isEqual } from "lodash-es"
|
||||
import { computed } from "vue"
|
||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||
import { HoppRESTSaveContext, HoppTabDocument } from "~/helpers/rest/document"
|
||||
import { TabService } from "./tab"
|
||||
import { Container } from "dioc"
|
||||
|
||||
export class RESTTabService extends TabService<HoppTabDocument> {
|
||||
public static readonly ID = "REST_TAB_SERVICE"
|
||||
@@ -52,6 +52,8 @@ export class RESTTabService extends TabService<HoppTabDocument> {
|
||||
public getTabRefWithSaveContext(ctx: HoppRESTSaveContext) {
|
||||
for (const tab of this.tabMap.values()) {
|
||||
// For `team-collection` request id can be considered unique
|
||||
if (tab.document.type === "test-runner") continue
|
||||
|
||||
if (ctx?.originLocation === "team-collection") {
|
||||
if (
|
||||
tab.document.saveContext?.originLocation === "team-collection" &&
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
import {
|
||||
HoppCollection,
|
||||
HoppRESTHeaders,
|
||||
HoppRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { Service } from "dioc"
|
||||
import * as E from "fp-ts/Either"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { Ref } from "vue"
|
||||
import { runTestRunnerRequest } from "~/helpers/RequestRunner"
|
||||
import {
|
||||
HoppTestRunnerDocument,
|
||||
TestRunnerConfig,
|
||||
} from "~/helpers/rest/document"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { HoppTestData, HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||
import { HoppTab } from "../tab"
|
||||
|
||||
export type TestRunnerOptions = {
|
||||
stopRef: Ref<boolean>
|
||||
} & TestRunnerConfig
|
||||
|
||||
export type TestRunnerRequest = HoppRESTRequest & {
|
||||
type: "test-response"
|
||||
response?: HoppRESTResponse | null
|
||||
testResults?: HoppTestResult | null
|
||||
isLoading?: boolean
|
||||
error?: string
|
||||
renderResults?: boolean
|
||||
passedTests: number
|
||||
failedTests: number
|
||||
}
|
||||
|
||||
function delay(timeMS: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(resolve, timeMS)
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
reject(new Error("Operation cancelled"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export class TestRunnerService extends Service {
|
||||
public static readonly ID = "TEST_RUNNER_SERVICE"
|
||||
|
||||
public runTests(
|
||||
tab: Ref<HoppTab<HoppTestRunnerDocument>>,
|
||||
collection: HoppCollection,
|
||||
options: TestRunnerOptions
|
||||
) {
|
||||
// Reset the result collection
|
||||
tab.value.document.status = "running"
|
||||
tab.value.document.resultCollection = {
|
||||
v: collection.v,
|
||||
id: collection.id,
|
||||
name: collection.name,
|
||||
auth: collection.auth,
|
||||
headers: collection.headers,
|
||||
folders: [],
|
||||
requests: [],
|
||||
}
|
||||
|
||||
this.runTestCollection(tab, collection, options)
|
||||
.then(() => {
|
||||
tab.value.document.status = "stopped"
|
||||
})
|
||||
.catch((error) => {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === "Test execution stopped"
|
||||
) {
|
||||
tab.value.document.status = "stopped"
|
||||
} else {
|
||||
tab.value.document.status = "error"
|
||||
console.error("Test runner failed:", error)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
tab.value.document.status = "stopped"
|
||||
})
|
||||
}
|
||||
|
||||
private async runTestCollection(
|
||||
tab: Ref<HoppTab<HoppTestRunnerDocument>>,
|
||||
collection: HoppCollection,
|
||||
options: TestRunnerOptions,
|
||||
parentPath: number[] = [],
|
||||
parentHeaders?: HoppRESTHeaders,
|
||||
parentAuth?: HoppRESTRequest["auth"]
|
||||
) {
|
||||
try {
|
||||
// Compute inherited auth and headers for this collection
|
||||
const inheritedAuth =
|
||||
collection.auth?.authType === "inherit" && collection.auth.authActive
|
||||
? parentAuth || { authType: "none", authActive: false }
|
||||
: collection.auth || { authType: "none", authActive: false }
|
||||
|
||||
const inheritedHeaders: HoppRESTHeaders = [
|
||||
...(parentHeaders || []),
|
||||
...collection.headers,
|
||||
]
|
||||
|
||||
// Process folders progressively
|
||||
for (let i = 0; i < collection.folders.length; i++) {
|
||||
if (options.stopRef?.value) {
|
||||
tab.value.document.status = "stopped"
|
||||
throw new Error("Test execution stopped")
|
||||
}
|
||||
|
||||
const folder = collection.folders[i]
|
||||
const currentPath = [...parentPath, i]
|
||||
|
||||
// Add folder to the result collection
|
||||
this.addFolderToPath(
|
||||
tab.value.document.resultCollection!,
|
||||
currentPath,
|
||||
{
|
||||
...cloneDeep(folder),
|
||||
folders: [],
|
||||
requests: [],
|
||||
}
|
||||
)
|
||||
|
||||
// Pass inherited headers and auth to the folder
|
||||
await this.runTestCollection(
|
||||
tab,
|
||||
folder,
|
||||
options,
|
||||
currentPath,
|
||||
inheritedHeaders,
|
||||
inheritedAuth
|
||||
)
|
||||
}
|
||||
|
||||
// Process requests progressively
|
||||
for (let i = 0; i < collection.requests.length; i++) {
|
||||
if (options.stopRef?.value) {
|
||||
tab.value.document.status = "stopped"
|
||||
throw new Error("Test execution stopped")
|
||||
}
|
||||
|
||||
const request = collection.requests[i] as TestRunnerRequest
|
||||
const currentPath = [...parentPath, i]
|
||||
|
||||
// Add request to the result collection before execution
|
||||
this.addRequestToPath(
|
||||
tab.value.document.resultCollection!,
|
||||
currentPath,
|
||||
cloneDeep(request)
|
||||
)
|
||||
|
||||
// Update the request with inherited headers and auth before execution
|
||||
const finalRequest = {
|
||||
...request,
|
||||
auth:
|
||||
request.auth.authType === "inherit" && request.auth.authActive
|
||||
? inheritedAuth
|
||||
: request.auth,
|
||||
headers: [...inheritedHeaders, ...request.headers],
|
||||
}
|
||||
|
||||
await this.runTestRequest(
|
||||
tab,
|
||||
finalRequest,
|
||||
collection,
|
||||
options,
|
||||
currentPath
|
||||
)
|
||||
|
||||
if (options.delay && options.delay > 0) {
|
||||
try {
|
||||
await delay(options.delay)
|
||||
} catch (error) {
|
||||
if (options.stopRef?.value) {
|
||||
tab.value.document.status = "stopped"
|
||||
throw new Error("Test execution stopped")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === "Test execution stopped"
|
||||
) {
|
||||
throw error
|
||||
}
|
||||
tab.value.document.status = "error"
|
||||
console.error("Collection execution failed:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private addFolderToPath(
|
||||
collection: HoppCollection,
|
||||
path: number[],
|
||||
folder: HoppCollection
|
||||
) {
|
||||
let current = collection
|
||||
|
||||
// Navigate to the parent folder
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current.folders[path[i]]
|
||||
}
|
||||
|
||||
// Add the folder at the specified index
|
||||
if (path.length > 0) {
|
||||
current.folders[path[path.length - 1]] = folder
|
||||
}
|
||||
}
|
||||
|
||||
private addRequestToPath(
|
||||
collection: HoppCollection,
|
||||
path: number[],
|
||||
request: TestRunnerRequest
|
||||
) {
|
||||
let current = collection
|
||||
|
||||
// Navigate to the parent folder
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current.folders[path[i]]
|
||||
}
|
||||
|
||||
// Add the request at the specified index
|
||||
if (path.length > 0) {
|
||||
current.requests[path[path.length - 1]] = request
|
||||
}
|
||||
}
|
||||
|
||||
private updateRequestAtPath(
|
||||
collection: HoppCollection,
|
||||
path: number[],
|
||||
updates: Partial<TestRunnerRequest>
|
||||
) {
|
||||
let current = collection
|
||||
|
||||
// Navigate to the parent folder
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
current = current.folders[path[i]]
|
||||
}
|
||||
|
||||
// Update the request at the specified index
|
||||
if (path.length > 0) {
|
||||
const index = path[path.length - 1]
|
||||
current.requests[index] = {
|
||||
...current.requests[index],
|
||||
...updates,
|
||||
} as TestRunnerRequest
|
||||
}
|
||||
}
|
||||
|
||||
private async runTestRequest(
|
||||
tab: Ref<HoppTab<HoppTestRunnerDocument>>,
|
||||
request: TestRunnerRequest,
|
||||
collection: HoppCollection,
|
||||
options: TestRunnerOptions,
|
||||
path: number[]
|
||||
) {
|
||||
if (options.stopRef?.value) {
|
||||
throw new Error("Test execution stopped")
|
||||
}
|
||||
|
||||
try {
|
||||
// Update request status in the result collection
|
||||
this.updateRequestAtPath(tab.value.document.resultCollection!, path, {
|
||||
isLoading: true,
|
||||
error: undefined,
|
||||
})
|
||||
|
||||
const results = await runTestRunnerRequest(request)
|
||||
|
||||
if (options.stopRef?.value) {
|
||||
throw new Error("Test execution stopped")
|
||||
}
|
||||
|
||||
if (results && E.isRight(results)) {
|
||||
const { response, testResult } = results.right
|
||||
const { passed, failed } = this.getTestResultInfo(testResult)
|
||||
|
||||
tab.value.document.testRunnerMeta.totalTests += passed + failed
|
||||
tab.value.document.testRunnerMeta.passedTests += passed
|
||||
tab.value.document.testRunnerMeta.failedTests += failed
|
||||
|
||||
// Update request with results in the result collection
|
||||
this.updateRequestAtPath(tab.value.document.resultCollection!, path, {
|
||||
testResults: testResult,
|
||||
response: options.persistResponses ? response : null,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
if (response.type === "success" || response.type === "fail") {
|
||||
tab.value.document.testRunnerMeta.totalTime +=
|
||||
response.meta.responseDuration
|
||||
tab.value.document.testRunnerMeta.completedRequests += 1
|
||||
}
|
||||
} else {
|
||||
const errorMsg = "Request execution failed"
|
||||
|
||||
// Update request with error in the result collection
|
||||
this.updateRequestAtPath(tab.value.document.resultCollection!, path, {
|
||||
error: errorMsg,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
if (options.stopOnError) {
|
||||
tab.value.document.status = "stopped"
|
||||
throw new Error("Test execution stopped due to error")
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === "Test execution stopped"
|
||||
) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const errorMsg =
|
||||
error instanceof Error ? error.message : "Unknown error occurred"
|
||||
|
||||
// Update request with error in the result collection
|
||||
this.updateRequestAtPath(tab.value.document.resultCollection!, path, {
|
||||
error: errorMsg,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
if (options.stopOnError) {
|
||||
tab.value.document.status = "stopped"
|
||||
throw new Error("Test execution stopped due to error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getTestResultInfo(testResult: HoppTestData) {
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
|
||||
for (const result of testResult.expectResults) {
|
||||
if (result.status === "pass") {
|
||||
passed++
|
||||
} else if (result.status === "fail") {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
for (const nestedTest of testResult.tests) {
|
||||
const nestedResult = this.getTestResultInfo(nestedTest)
|
||||
passed += nestedResult.passed
|
||||
failed += nestedResult.failed
|
||||
}
|
||||
|
||||
return { passed, failed }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user