feat: test script and pre-request script wiring
This commit is contained in:
@@ -74,7 +74,7 @@
|
|||||||
<ButtonPrimary
|
<ButtonPrimary
|
||||||
id="send"
|
id="send"
|
||||||
class="rounded-none"
|
class="rounded-none"
|
||||||
:label="!loading ? $t('send') : $('cancel')"
|
:label="!loading ? $t('send') : $t('cancel')"
|
||||||
:shortcuts="[getSpecialKey(), 'G']"
|
:shortcuts="[getSpecialKey(), 'G']"
|
||||||
outline
|
outline
|
||||||
@click.native="!loading ? newSendRequest() : cancelRequest()"
|
@click.native="!loading ? newSendRequest() : cancelRequest()"
|
||||||
@@ -188,16 +188,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
updateRESTResponse,
|
updateRESTResponse,
|
||||||
restRequest$,
|
|
||||||
restEndpoint$,
|
restEndpoint$,
|
||||||
setRESTEndpoint,
|
setRESTEndpoint,
|
||||||
restMethod$,
|
restMethod$,
|
||||||
updateRESTMethod,
|
updateRESTMethod,
|
||||||
} from "~/newstore/RESTSession"
|
} from "~/newstore/RESTSession"
|
||||||
import { createRESTNetworkRequestStream } from "~/helpers/network"
|
|
||||||
import { currentEnvironment$ } from "~/newstore/environments"
|
|
||||||
import { getEffectiveRESTRequestStream } from "~/helpers/utils/EffectiveURL"
|
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
|
import { runRESTRequest$ } from "~/helpers/RequestRunner"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -220,7 +217,6 @@ export default {
|
|||||||
showCurlImportModal: false,
|
showCurlImportModal: false,
|
||||||
showCodegenModal: false,
|
showCodegenModal: false,
|
||||||
navigatorShare: navigator.share,
|
navigatorShare: navigator.share,
|
||||||
effectiveStream$: null,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -228,10 +224,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
newMethod$: restMethod$,
|
newMethod$: restMethod$,
|
||||||
newEndpoint$: restEndpoint$,
|
newEndpoint$: restEndpoint$,
|
||||||
effectiveStream$: getEffectiveRESTRequestStream(
|
|
||||||
restRequest$,
|
|
||||||
currentEnvironment$
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -247,10 +239,7 @@ export default {
|
|||||||
newSendRequest() {
|
newSendRequest() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$subscribeTo(
|
this.$subscribeTo(
|
||||||
createRESTNetworkRequestStream(
|
runRESTRequest$(),
|
||||||
this.effectiveStream$,
|
|
||||||
currentEnvironment$
|
|
||||||
),
|
|
||||||
(responseState) => {
|
(responseState) => {
|
||||||
console.log(responseState)
|
console.log(responseState)
|
||||||
updateRESTResponse(responseState)
|
updateRESTResponse(responseState)
|
||||||
|
|||||||
@@ -79,12 +79,14 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
import { defineComponent } from "@nuxtjs/composition-api"
|
||||||
import { useTestScript } from "~/newstore/RESTSession"
|
import { useTestScript, restTestResults$ } from "~/newstore/RESTSession"
|
||||||
|
import { useReadonlyStream } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
testScript: useTestScript(),
|
testScript: useTestScript(),
|
||||||
|
testResults: useReadonlyStream(restTestResults$, null),
|
||||||
testReports: [],
|
testReports: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
147
helpers/RequestRunner.ts
Normal file
147
helpers/RequestRunner.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { Observable } from "rxjs"
|
||||||
|
import { filter } from "rxjs/operators"
|
||||||
|
import getEnvironmentVariablesFromScript from "./preRequest"
|
||||||
|
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
||||||
|
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||||
|
import { createRESTNetworkRequestStream } from "./network"
|
||||||
|
import runTestScriptWithVariables from "./postwomanTesting"
|
||||||
|
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
||||||
|
import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a REST network request along with all the
|
||||||
|
* other side processes (like running test scripts)
|
||||||
|
*/
|
||||||
|
export function runRESTRequest$(): Observable<HoppRESTResponse> {
|
||||||
|
const envs = getEnvironmentVariablesFromScript(
|
||||||
|
getRESTRequest().preRequestScript
|
||||||
|
)
|
||||||
|
|
||||||
|
const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
|
||||||
|
name: "Env",
|
||||||
|
variables: Object.keys(envs).map((key) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
value: envs[key],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const stream = createRESTNetworkRequestStream(effectiveRequest)
|
||||||
|
|
||||||
|
// Run Test Script when request ran successfully
|
||||||
|
const subscription = stream
|
||||||
|
.pipe(filter((res) => res.type === "success"))
|
||||||
|
.subscribe((res) => {
|
||||||
|
const testReport: {
|
||||||
|
report: "" // ¯\_(ツ)_/¯
|
||||||
|
testResults: Array<
|
||||||
|
| {
|
||||||
|
result: "FAIL"
|
||||||
|
message: string
|
||||||
|
styles: { icon: "close"; class: "cl-error-response" }
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
result: "PASS"
|
||||||
|
styles: { icon: "check"; class: "success-response" }
|
||||||
|
}
|
||||||
|
| { startBlock: string; styles: { icon: ""; class: "" } }
|
||||||
|
| { endBlock: true; styles: { icon: ""; class: "" } }
|
||||||
|
>
|
||||||
|
errors: [] // ¯\_(ツ)_/¯
|
||||||
|
} = runTestScriptWithVariables(effectiveRequest.testScript, {
|
||||||
|
response: res,
|
||||||
|
}) as any
|
||||||
|
|
||||||
|
setRESTTestResults(translateToNewTestResults(testReport))
|
||||||
|
|
||||||
|
subscription.unsubscribe()
|
||||||
|
})
|
||||||
|
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTestPass(x: any): x is {
|
||||||
|
result: "PASS"
|
||||||
|
styles: { icon: "check"; class: "success-response" }
|
||||||
|
} {
|
||||||
|
return x.result !== undefined && x.result === "PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTestFail(x: any): x is {
|
||||||
|
result: "FAIL"
|
||||||
|
message: string
|
||||||
|
styles: { icon: "close"; class: "cl-error-response" }
|
||||||
|
} {
|
||||||
|
return x.result !== undefined && x.result === "FAIL"
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartBlock(
|
||||||
|
x: any
|
||||||
|
): x is { startBlock: string; styles: { icon: ""; class: "" } } {
|
||||||
|
return x.startBlock !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEndBlock(
|
||||||
|
x: any
|
||||||
|
): x is { endBlock: true; styles: { icon: ""; class: "" } } {
|
||||||
|
return x.endBlock !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateToNewTestResults(testReport: {
|
||||||
|
report: "" // ¯\_(ツ)_/¯
|
||||||
|
testResults: Array<
|
||||||
|
| {
|
||||||
|
result: "FAIL"
|
||||||
|
message: string
|
||||||
|
styles: { icon: "close"; class: "cl-error-response" }
|
||||||
|
}
|
||||||
|
| { result: "PASS"; styles: { icon: "check"; class: "success-response" } }
|
||||||
|
| { startBlock: string; styles: { icon: ""; class: "" } }
|
||||||
|
| { endBlock: true; styles: { icon: ""; class: "" } }
|
||||||
|
>
|
||||||
|
errors: [] // ¯\_(ツ)_/¯
|
||||||
|
}): HoppTestResult {
|
||||||
|
// Build a stack of test data which we eventually build up based on the results
|
||||||
|
const testsStack: HoppTestData[] = [
|
||||||
|
{
|
||||||
|
description: "root",
|
||||||
|
tests: [],
|
||||||
|
expectResults: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
testReport.testResults.forEach((result) => {
|
||||||
|
// This is a test block start, push an empty test to the stack
|
||||||
|
if (isStartBlock(result)) {
|
||||||
|
testsStack.push({
|
||||||
|
description: result.startBlock,
|
||||||
|
tests: [],
|
||||||
|
expectResults: [],
|
||||||
|
})
|
||||||
|
} else if (isEndBlock(result)) {
|
||||||
|
// End of the block, pop the stack and add it as a child to the current stack top
|
||||||
|
const testData = testsStack.pop()!
|
||||||
|
testsStack[testsStack.length - 1].tests.push(testData)
|
||||||
|
} else if (isTestPass(result)) {
|
||||||
|
// A normal PASS expectation
|
||||||
|
testsStack[testsStack.length - 1].expectResults.push({
|
||||||
|
status: "pass",
|
||||||
|
})
|
||||||
|
} else if (isTestFail(result)) {
|
||||||
|
// A normal FAIL expectation
|
||||||
|
testsStack[testsStack.length - 1].expectResults.push({
|
||||||
|
status: "fail",
|
||||||
|
message: result.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// We should end up with only the root stack entry
|
||||||
|
if (testsStack.length !== 1) throw new Error("Invalid test result structure")
|
||||||
|
|
||||||
|
return {
|
||||||
|
expectResults: testsStack[0].expectResults,
|
||||||
|
tests: testsStack[0].tests,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ export default function getEnvironmentVariablesFromScript(script: string) {
|
|||||||
// run pre-request script within this function so that it has access to the pw object.
|
// run pre-request script within this function so that it has access to the pw object.
|
||||||
// eslint-disable-next-line no-new-func
|
// eslint-disable-next-line no-new-func
|
||||||
new Function("pw", script)(pw)
|
new Function("pw", script)(pw)
|
||||||
} catch (_e) {}
|
} catch (_e) { }
|
||||||
|
|
||||||
return _variables
|
return _variables
|
||||||
}
|
}
|
||||||
|
|||||||
14
helpers/types/HoppTestResult.ts
Normal file
14
helpers/types/HoppTestResult.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type HoppTestExpectResult =
|
||||||
|
| { status: "pass" }
|
||||||
|
| { status: "fail" | "error"; message: string }
|
||||||
|
|
||||||
|
export type HoppTestData = {
|
||||||
|
description: string
|
||||||
|
expectResults: HoppTestExpectResult[]
|
||||||
|
tests: HoppTestData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HoppTestResult = {
|
||||||
|
tests: HoppTestData[]
|
||||||
|
expectResults: HoppTestExpectResult[]
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "~/helpers/types/HoppRESTRequest"
|
} from "~/helpers/types/HoppRESTRequest"
|
||||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||||
import { useStream } from "~/helpers/utils/composables"
|
import { useStream } from "~/helpers/utils/composables"
|
||||||
|
import { HoppTestResult } from "~/helpers/types/HoppTestResult"
|
||||||
|
|
||||||
function getParamsInURL(url: string): { key: string; value: string }[] {
|
function getParamsInURL(url: string): { key: string; value: string }[] {
|
||||||
const result: { key: string; value: string }[] = []
|
const result: { key: string; value: string }[] = []
|
||||||
@@ -114,6 +115,7 @@ function updateURLParam(
|
|||||||
type RESTSession = {
|
type RESTSession = {
|
||||||
request: HoppRESTRequest
|
request: HoppRESTRequest
|
||||||
response: HoppRESTResponse | null
|
response: HoppRESTResponse | null
|
||||||
|
testResults: HoppTestResult | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultRESTSession: RESTSession = {
|
const defaultRESTSession: RESTSession = {
|
||||||
@@ -127,6 +129,7 @@ const defaultRESTSession: RESTSession = {
|
|||||||
testScript: "// pw.expect('variable').toBe('value');",
|
testScript: "// pw.expect('variable').toBe('value');",
|
||||||
},
|
},
|
||||||
response: null,
|
response: null,
|
||||||
|
testResults: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatchers = defineDispatchers({
|
const dispatchers = defineDispatchers({
|
||||||
@@ -320,6 +323,14 @@ const dispatchers = defineDispatchers({
|
|||||||
response: null,
|
response: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setTestResults(
|
||||||
|
_curr: RESTSession,
|
||||||
|
{ newResults }: { newResults: HoppTestResult | null }
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
testResults: newResults,
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers)
|
const restSessionStore = new DispatchingStore(defaultRESTSession, dispatchers)
|
||||||
@@ -459,6 +470,15 @@ export function clearRESTResponse() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setRESTTestResults(newResults: HoppTestResult | null) {
|
||||||
|
restSessionStore.dispatch({
|
||||||
|
dispatcher: "setTestResults",
|
||||||
|
payload: {
|
||||||
|
newResults,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const restRequest$ = restSessionStore.subject$.pipe(
|
export const restRequest$ = restSessionStore.subject$.pipe(
|
||||||
pluck("request"),
|
pluck("request"),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
@@ -514,6 +534,11 @@ export const completedRESTResponse$ = restResponse$.pipe(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const restTestResults$ = restSessionStore.subject$.pipe(
|
||||||
|
pluck("testResults"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Vue 3 composable function that gives access to a ref
|
* A Vue 3 composable function that gives access to a ref
|
||||||
* which is updated to the preRequestScript value in the store.
|
* which is updated to the preRequestScript value in the store.
|
||||||
|
|||||||
Reference in New Issue
Block a user