1396 lines
43 KiB
Vue
1396 lines
43 KiB
Vue
<script setup lang="ts">
|
|
import { ref, watch, reactive } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Edit, Delete, Search, CopyDocument } from '@element-plus/icons-vue'
|
|
import JsonViewer from 'vue-json-viewer'
|
|
import type { Pair, TestResult, TestCaseWithSuite, TestCase } from './types'
|
|
import { NewSuggestedAPIsQuery, CreateFilter, GetHTTPMethods, FlattenObject } from './types'
|
|
import { Cache } from './cache'
|
|
import { API } from './net'
|
|
import EditButton from '../components/EditButton.vue'
|
|
import type { RunTestCaseRequest } from './net'
|
|
import { UIAPI } from './net-vue'
|
|
import type { TestCaseResponse } from './cache'
|
|
import { Magic } from './magicKeys'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { JSONPath } from 'jsonpath-plus'
|
|
import { Codemirror } from 'vue-codemirror'
|
|
import jsonlint from 'jsonlint-mod'
|
|
|
|
import CodeMirror from 'codemirror'
|
|
import 'codemirror/lib/codemirror.css'
|
|
import 'codemirror/addon/merge/merge.js'
|
|
import 'codemirror/addon/merge/merge.css'
|
|
|
|
import DiffMatchPatch from 'diff-match-patch';
|
|
|
|
window.diff_match_patch = DiffMatchPatch;
|
|
window.DIFF_DELETE = -1;
|
|
window.DIFF_INSERT = 1;
|
|
window.DIFF_EQUAL = 0;
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps({
|
|
name: String,
|
|
suite: String,
|
|
kindName: String,
|
|
historySuiteName: String,
|
|
historyCaseID: String
|
|
})
|
|
const emit = defineEmits(['updated','toHistoryPanel'])
|
|
|
|
let querySuggestedAPIs = NewSuggestedAPIsQuery(Cache.GetCurrentStore().name!, props.suite!)
|
|
const testResultActiveTab = ref(Cache.GetPreference().responseActiveTab)
|
|
watch(testResultActiveTab, Cache.WithResponseActiveTab)
|
|
Magic.Keys(() => {
|
|
testResultActiveTab.value = 'output'
|
|
}, ['Alt+KeyO'])
|
|
|
|
const parameters = ref([] as Pair[])
|
|
const requestLoading = ref(false)
|
|
const testResult = ref({ header: [] as Pair[] } as TestResult)
|
|
const sendRequest = async () => {
|
|
if (needUpdate.value) {
|
|
await saveTestCase(false, runTestCase)
|
|
needUpdate.value = false
|
|
} else {
|
|
runTestCase()
|
|
}
|
|
}
|
|
Magic.Keys(sendRequest, ['Alt+S', 'Alt+ß'])
|
|
|
|
const runTestCaseResultHandler = (e: any) => {
|
|
requestLoading.value = false
|
|
handleTestResult(e)
|
|
}
|
|
const runTestCase = () => {
|
|
requestLoading.value = true
|
|
const name = props.name
|
|
const suite = props.suite
|
|
const request = {
|
|
suiteName: suite,
|
|
name: name,
|
|
parameters: parameters.value
|
|
} as RunTestCaseRequest
|
|
|
|
if (batchRunMode.value) {
|
|
API.BatchRunTestCase({
|
|
count: batchRunCount.value,
|
|
interval: batchRunInterval.value,
|
|
request: request
|
|
}, runTestCaseResultHandler, (e) => {
|
|
parameters.value = []
|
|
|
|
requestLoading.value = false
|
|
UIAPI.ErrorTip(e)
|
|
parseResponseBody(e.body)
|
|
})
|
|
} else {
|
|
API.RunTestCase(request, runTestCaseResultHandler, (e) => {
|
|
parameters.value = []
|
|
|
|
requestLoading.value = false
|
|
UIAPI.ErrorTip(e)
|
|
parseResponseBody(e.body)
|
|
})
|
|
}
|
|
}
|
|
|
|
const parseResponseBody = (body: any) => {
|
|
if (body === '') {
|
|
return
|
|
}
|
|
|
|
try {
|
|
testResult.value.bodyLength = body.length
|
|
testResult.value.bodyObject = JSON.parse(body)
|
|
testResult.value.originBodyObject = JSON.parse(body)
|
|
} catch {
|
|
testResult.value.bodyText = body
|
|
}
|
|
}
|
|
|
|
const handleTestResult = (e: any) => {
|
|
testResult.value = e;
|
|
|
|
if (!isHistoryTestCase.value) {
|
|
handleTestResultError(e)
|
|
}
|
|
const isFilePath = e.body.startsWith("isFilePath-")
|
|
|
|
if(isFilePath){
|
|
isResponseFile.value = true
|
|
} else if(e.body !== ''){
|
|
testResult.value.bodyLength = e.body.length
|
|
testResult.value.bodyObject = JSON.parse(e.body);
|
|
testResult.value.originBodyObject = JSON.parse(e.body);
|
|
}
|
|
|
|
Cache.SetTestCaseResponseCache(suite + '-' + name, {
|
|
body: testResult.value.bodyObject,
|
|
output: e.output,
|
|
statusCode: testResult.value.statusCode
|
|
} as TestCaseResponse)
|
|
|
|
parameters.value = [];
|
|
}
|
|
|
|
const handleTestResultError = (e) => {
|
|
if (e.error !== '') {
|
|
ElMessage({
|
|
showClose: true,
|
|
message: e.error,
|
|
type: 'error'
|
|
});
|
|
} else {
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Pass!',
|
|
type: 'success'
|
|
});
|
|
}
|
|
}
|
|
|
|
const responseBodyFilterText = ref('')
|
|
function responseBodyFilter() {
|
|
if (responseBodyFilterText.value === '') {
|
|
testResult.value.bodyObject = testResult.value.originBodyObject
|
|
} else {
|
|
const query = JSONPath({
|
|
path: responseBodyFilterText.value,
|
|
json: testResult.value.originBodyObject,
|
|
resultType: 'value'
|
|
})
|
|
testResult.value.bodyObject = query[0]
|
|
}
|
|
}
|
|
|
|
const parameterDialogOpened = ref(false)
|
|
const batchRunMode = ref(false)
|
|
const batchRunCount = ref(1)
|
|
const batchRunInterval = ref('1s')
|
|
const openBatchRunDialog = () => {
|
|
batchRunMode.value = true
|
|
openParameterDialog()
|
|
}
|
|
function openParameterDialog() {
|
|
API.GetTestSuite(props.suite, (e) => {
|
|
parameters.value = e.param
|
|
parameterDialogOpened.value = true
|
|
}, UIAPI.ErrorTip)
|
|
}
|
|
|
|
function sendRequestWithParameter() {
|
|
parameterDialogOpened.value = false
|
|
sendRequest()
|
|
batchRunMode.value = false
|
|
}
|
|
|
|
function generateCode() {
|
|
const name = props.name
|
|
const suite = props.suite
|
|
const ID = props.historyCaseID
|
|
if (isHistoryTestCase.value == true){
|
|
API.HistoryGenerateCode({
|
|
id: ID,
|
|
generator: currentCodeGenerator.value
|
|
}, (e) => {
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Code generated!',
|
|
type: 'success'
|
|
})
|
|
if (currentCodeGenerator.value === "gRPCPayload") {
|
|
currentCodeContent.value = JSON.stringify(JSON.parse(e.message), null, 4)
|
|
} else {
|
|
currentCodeContent.value = e.message
|
|
}
|
|
}, UIAPI.ErrorTip)
|
|
} else{
|
|
API.GenerateCode({
|
|
suiteName: suite,
|
|
name: name,
|
|
generator: currentCodeGenerator.value
|
|
}, (e) => {
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Code generated!',
|
|
type: 'success'
|
|
})
|
|
if (currentCodeGenerator.value === "gRPCPayload") {
|
|
currentCodeContent.value = JSON.stringify(JSON.parse(e.message), null, 4)
|
|
} else {
|
|
currentCodeContent.value = e.message
|
|
}
|
|
}, UIAPI.ErrorTip)
|
|
}
|
|
|
|
}
|
|
|
|
function copyCode() {
|
|
navigator.clipboard.writeText(currentCodeContent.value);
|
|
}
|
|
|
|
const queryBodyFields = (queryString: string, cb: any) => {
|
|
if (!testResult.value.bodyObject || !FlattenObject(testResult.value.bodyObject)) {
|
|
cb([])
|
|
return
|
|
}
|
|
const keys = Object.getOwnPropertyNames(FlattenObject(testResult.value.bodyObject))
|
|
if (keys.length <= 0) {
|
|
cb([])
|
|
return
|
|
}
|
|
|
|
const pairs = [] as Pair[]
|
|
keys.forEach((e) => {
|
|
pairs.push({
|
|
key: e,
|
|
value: e
|
|
} as Pair)
|
|
})
|
|
|
|
const results = queryString ? pairs.filter(CreateFilter(queryString)) : pairs
|
|
// call callback function to return suggestions
|
|
cb(results)
|
|
}
|
|
|
|
const emptyTestCaseWithSuite: TestCaseWithSuite = {
|
|
suiteName: '',
|
|
data: {
|
|
name: '',
|
|
request: {
|
|
api: '',
|
|
method: '',
|
|
header: [],
|
|
query: [],
|
|
cookie: [],
|
|
form: [],
|
|
body: '',
|
|
filepath: ''
|
|
},
|
|
response: {
|
|
statusCode: 0,
|
|
body: '',
|
|
header: [],
|
|
bodyFieldsExpect: [],
|
|
verify: [],
|
|
schema: ''
|
|
}
|
|
}
|
|
}
|
|
|
|
const testCaseWithSuite = ref(emptyTestCaseWithSuite)
|
|
|
|
let name
|
|
let suite
|
|
let historySuiteName
|
|
let historyCaseID
|
|
const isHistoryTestCase = ref(false)
|
|
const HistoryTestCaseCreateTime = ref('')
|
|
|
|
function load() {
|
|
name = props.name
|
|
suite = props.suite
|
|
historySuiteName = props.historySuiteName
|
|
historyCaseID = props.historyCaseID
|
|
if (name === '' || suite === '') {
|
|
return
|
|
}
|
|
|
|
// load cache
|
|
const cache = Cache.GetTestCaseResponseCache(suite + '-' + name)
|
|
if (cache.body) {
|
|
testResult.value.bodyObject = cache.body
|
|
testResult.value.output = cache.output
|
|
testResult.value.statusCode = cache.statusCode
|
|
} else {
|
|
testResult.value.bodyObject = {}
|
|
testResult.value.output = ''
|
|
testResult.value.statusCode = 0
|
|
}
|
|
testResult.value.originBodyObject = testResult.value.bodyObject
|
|
|
|
if (historySuiteName != '' && historySuiteName != undefined) {
|
|
isHistoryTestCase.value = true
|
|
API.GetHistoryTestCaseWithResult({
|
|
historyCaseID : historyCaseID
|
|
}, (e) => {
|
|
setDefaultValues(e.data)
|
|
determineBodyType(e.data)
|
|
setTestCaseWithSuite(e.data,suite)
|
|
handleTestResult(e.testCaseResult[0])
|
|
HistoryTestCaseCreateTime.value = formatDate(e.createTime)
|
|
})
|
|
} else {
|
|
API.GetTestCase({
|
|
suiteName: suite,
|
|
name: name
|
|
}, (e) => {
|
|
setDefaultValues(e)
|
|
determineBodyType(e)
|
|
setTestCaseWithSuite(e,suite)
|
|
})
|
|
}
|
|
}
|
|
|
|
function formatDate(createTimeStr : string){
|
|
let parts = createTimeStr.split(/[T.Z]/);
|
|
let datePart = parts[0].split("-");
|
|
let timePart = parts[1].split(":");
|
|
|
|
let year = datePart[0];
|
|
let month = datePart[1];
|
|
let day = datePart[2];
|
|
let hours = timePart[0];
|
|
let minutes = timePart[1];
|
|
let seconds = timePart[2].split(".")[0];
|
|
|
|
let formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
return formattedDate
|
|
}
|
|
|
|
function determineBodyType(e: TestCase) {
|
|
e.request.header.forEach(item => {
|
|
if (item.key === "Content-Type") {
|
|
switch (item.value) {
|
|
case 'application/x-www-form-urlencoded':
|
|
bodyType.value = 4
|
|
break
|
|
case 'application/json':
|
|
bodyType.value = 5
|
|
break
|
|
case 'multipart/form-data':
|
|
bodyType.value = 6
|
|
|
|
e.request.form.forEach(fItem => {
|
|
if (fItem.key !== '' && fItem.key !== '') {
|
|
e.request.filepath = fItem.key + "=" + fItem.value
|
|
}
|
|
})
|
|
break
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function base64ToBinary(base64: string): Uint8Array {
|
|
const binaryString = atob(base64);
|
|
const len = binaryString.length;
|
|
const bytes = new Uint8Array(len);
|
|
for (let i = 0; i < len; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
const isResponseFile = ref(false)
|
|
function downloadResponseFile(){
|
|
API.DownloadResponseFile({
|
|
body: testResult.value.body
|
|
}, (e) => {
|
|
if (e && e.data) {
|
|
try {
|
|
const bytes = base64ToBinary(e.data);
|
|
const blob = new Blob([bytes], { type: 'mimeType' });
|
|
const link = document.createElement('a');
|
|
link.href = window.URL.createObjectURL(blob);
|
|
if (e.filename.indexOf('isFilePath-') === -1) {
|
|
link.download = e.filename;
|
|
} else {
|
|
link.download = e.filename.substring("isFilePath-".length);
|
|
}
|
|
|
|
console.log(e.filename);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
|
|
window.URL.revokeObjectURL(link.href);
|
|
document.body.removeChild(link);
|
|
} catch (error) {
|
|
console.error('Error during file download:', error);
|
|
}
|
|
} else {
|
|
console.error('No data to download.');
|
|
}
|
|
})
|
|
}
|
|
|
|
function setDefaultValues(e) {
|
|
if (e.request.method === '') {
|
|
e.request.method = 'GET'
|
|
}
|
|
|
|
e.request.header.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.request.cookie.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.request.query.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.request.form.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.response.header.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.response.bodyFieldsExpect.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
e.response.verify.push('')
|
|
if (e.response.statusCode === 0) {
|
|
e.response.statusCode = 200
|
|
}
|
|
}
|
|
|
|
function setTestCaseWithSuite(e, suite) {
|
|
e.suiteName = suite
|
|
testCaseWithSuite.value = {
|
|
suiteName: suite,
|
|
data: e
|
|
} as TestCaseWithSuite;
|
|
if (isHistoryTestCase.value == true){
|
|
testCaseWithSuite.value.data.request.api = `${testCaseWithSuite.value.data.suiteApi}${testCaseWithSuite.value.data.request.api}`
|
|
}
|
|
}
|
|
|
|
load()
|
|
watch(props, () => {
|
|
load()
|
|
})
|
|
|
|
const needUpdate = ref(false)
|
|
watch(testCaseWithSuite, (after, before) => {
|
|
if (before.data.name !== '' && after.data.name === before.data.name) {
|
|
needUpdate.value = true
|
|
}
|
|
}, { deep: true })
|
|
|
|
const saveLoading = ref(false)
|
|
function saveTestCase(tip: boolean = true, callback: (c: any) => void) {
|
|
UIAPI.UpdateTestCase(testCaseWithSuite.value, (e) => {
|
|
if (tip) {
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Saved.',
|
|
type: 'success'
|
|
})
|
|
}
|
|
|
|
if (callback) {
|
|
callback()
|
|
}
|
|
}, UIAPI.ErrorTip, saveLoading)
|
|
}
|
|
|
|
function deleteCase() {
|
|
const name = props.name
|
|
const suite = props.suite
|
|
const historyCaseID = props.historyCaseID
|
|
|
|
if (isHistoryTestCase.value == true){
|
|
deleteHistoryTestCase(historyCaseID)
|
|
} else {
|
|
deleteTestCase(name, suite)
|
|
}
|
|
}
|
|
|
|
function deleteHistoryTestCase(historyCaseID : string){
|
|
API.DeleteHistoryTestCase({ historyCaseID }, handleDeleteResponse);
|
|
}
|
|
|
|
function deleteTestCase(name : string, suite : string){
|
|
API.DeleteTestCase({ suiteName: suite, name }, handleDeleteResponse);
|
|
}
|
|
|
|
function handleDeleteResponse(e) {
|
|
if (e.ok) {
|
|
emit('updated', 'hello from child');
|
|
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Delete.',
|
|
type: 'success'
|
|
});
|
|
|
|
// Clean all the values
|
|
testCaseWithSuite.value = emptyTestCaseWithSuite;
|
|
} else {
|
|
UIAPI.ErrorTip(e);
|
|
}
|
|
}
|
|
|
|
const codeDialogOpened = ref(false)
|
|
const codeGenerators = ref('')
|
|
const currentCodeGenerator = ref('')
|
|
const currentCodeContent = ref('')
|
|
function openCodeDialog() {
|
|
codeDialogOpened.value = true
|
|
|
|
API.ListCodeGenerator((e) => {
|
|
codeGenerators.value = e.data
|
|
})
|
|
|
|
if (currentCodeGenerator.value !== '') {
|
|
generateCode()
|
|
}
|
|
}
|
|
watch(currentCodeGenerator, () => {
|
|
generateCode()
|
|
})
|
|
|
|
const historyDialogOpened = ref(false)
|
|
const historyForm = ref({ selectedID: '' })
|
|
const historyRecords = ref([]);
|
|
const selectedHistory = ref(null);
|
|
const viewHistoryRef = ref(null);
|
|
const formatHistoryCase = ref(null);
|
|
|
|
const rules = {
|
|
selectedID: [
|
|
{ required: true, message: 'Please select history TestCase', trigger: 'change' }
|
|
]
|
|
}
|
|
|
|
function openHistoryDialog(){
|
|
historyDialogOpened.value = true
|
|
name = props.name
|
|
suite = props.suite
|
|
API.GetTestCaseAllHistory({
|
|
suiteName : suite,
|
|
name: name,
|
|
}, (e) => {
|
|
historyRecords.value = e.data
|
|
historyRecords.value.forEach(record => {
|
|
record.createTime = formatDate(record.createTime)
|
|
setDefaultValues(record)
|
|
});
|
|
})
|
|
}
|
|
|
|
function handleDialogClose(){
|
|
caseRevertLoading.value = false
|
|
historyDialogOpened.value = false
|
|
historyForm.value.selectedID = ''
|
|
const target = document.getElementById('compareView');
|
|
target.innerHTML = ''
|
|
}
|
|
|
|
function handleHistoryChange(value) {
|
|
selectedHistory.value = historyRecords.value.find(record => record.ID === value);
|
|
const {
|
|
caseName: name,
|
|
suiteName,
|
|
request,
|
|
response,
|
|
historyHeader,
|
|
} = selectedHistory.value;
|
|
request.header = historyHeader
|
|
request.header.push({
|
|
key: '',
|
|
value: ''
|
|
})
|
|
formatHistoryCase.value = {
|
|
name,
|
|
suiteName,
|
|
request,
|
|
response
|
|
};
|
|
initCompare(testCaseWithSuite.value.data, formatHistoryCase.value)
|
|
}
|
|
|
|
function initCompare(value, historyValue) {
|
|
const formattedHistoryValue = JSON.stringify(historyValue, null, 2);
|
|
const formattedNewValue = JSON.stringify(value, null, 2);
|
|
const target = document.getElementById('compareView')
|
|
target.innerHTML = ''
|
|
|
|
const mergeView = CodeMirror.MergeView(target, {
|
|
value: formattedHistoryValue,
|
|
origLeft: null,
|
|
orig: formattedNewValue,
|
|
lineNumbers: true,
|
|
mode: { name: "javascript", json: true },
|
|
highlightDifferences: true,
|
|
collapseIdentical: true,
|
|
foldGutter:true,
|
|
lineWrapping:true,
|
|
styleActiveLine: true,
|
|
matchBrackets: true,
|
|
connect: 'align',
|
|
readOnly: true
|
|
})
|
|
}
|
|
|
|
const caseRevertLoading = ref(false)
|
|
const submitForm = async (formEl) => {
|
|
if (!formEl) return
|
|
await formEl.validate((valid: boolean, fields) => {
|
|
if (valid) {
|
|
caseRevertLoading.value = true
|
|
const historyTestCase = {
|
|
suiteName: props.suite,
|
|
data: formatHistoryCase.value
|
|
}
|
|
UIAPI.UpdateTestCase(historyTestCase, (e) => {
|
|
if(e.error == ""){
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Saved.',
|
|
type: 'success'
|
|
})
|
|
load()
|
|
}
|
|
}, UIAPI.ErrorTip, saveLoading)
|
|
handleDialogClose()
|
|
}
|
|
})
|
|
}
|
|
|
|
const goToHistory = async (formEl) => {
|
|
if (!formEl) return
|
|
await formEl.validate((valid: boolean, fields) => {
|
|
if (valid) {
|
|
caseRevertLoading.value = true
|
|
emit('toHistoryPanel', { ID: selectedHistory.value.ID, panelName: 'history' })
|
|
handleDialogClose()
|
|
}
|
|
})
|
|
}
|
|
|
|
const deleteAllHistory = async (formEl) => {
|
|
if (!formEl) return
|
|
caseRevertLoading.value = true
|
|
API.DeleteAllHistoryTestCase(props.suite, props.name, handleDeleteResponse)
|
|
handleDialogClose()
|
|
}
|
|
|
|
const options = GetHTTPMethods()
|
|
const requestActiveTab = ref(Cache.GetPreference().requestActiveTab)
|
|
watch(requestActiveTab, Cache.WatchRequestActiveTab)
|
|
Magic.Keys(() => {
|
|
requestActiveTab.value = 'query'
|
|
}, ['Alt+KeyQ'])
|
|
Magic.Keys(() => {
|
|
requestActiveTab.value = 'header'
|
|
}, ['Alt+KeyH'])
|
|
Magic.Keys(() => {
|
|
requestActiveTab.value = 'body'
|
|
}, ['Alt+KeyB'])
|
|
|
|
function bodyFiledExpectChange() {
|
|
const data = testCaseWithSuite.value.data.response.bodyFieldsExpect
|
|
let lastItem = data[data.length - 1]
|
|
if (lastItem.key !== '') {
|
|
data.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
|
|
function queryChange() {
|
|
const query = testCaseWithSuite.value.data.request.query
|
|
let lastItem = query[query.length - 1]
|
|
if (lastItem.key !== '') {
|
|
testCaseWithSuite.value.data.request.query.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
function headerChange() {
|
|
const header = testCaseWithSuite.value.data.request.header
|
|
let lastItem = header[header.length - 1]
|
|
if (lastItem.key !== '') {
|
|
testCaseWithSuite.value.data.request.header.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
function cookieChange(){
|
|
const cookie = testCaseWithSuite.value.data.request.cookie
|
|
let lastItem = cookie[cookie.length - 1]
|
|
if (lastItem.key !== '') {
|
|
testCaseWithSuite.value.data.request.cookie.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
|
|
const headerValues = ref([] as Pair[])
|
|
const headerSelect = (item: Record<string, any>) => {
|
|
headerValues.value = []
|
|
pupularHeaderPairs.value.filter((v) => {
|
|
if (v.key === item.value) {
|
|
headerValues.value.push({
|
|
key: v.value,
|
|
value: v.value
|
|
} as Pair)
|
|
}
|
|
})
|
|
}
|
|
function expectedHeaderChange() {
|
|
const header = testCaseWithSuite.value.data.response.header
|
|
let lastItem = header[header.length - 1]
|
|
if (lastItem.key !== '') {
|
|
testCaseWithSuite.value.data.response.header.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
function formChange() {
|
|
const form = testCaseWithSuite.value.data.request.form
|
|
let lastItem = form[form.length - 1]
|
|
if (lastItem.key !== '') {
|
|
testCaseWithSuite.value.data.request.form.push({
|
|
key: '',
|
|
value: ''
|
|
} as Pair)
|
|
}
|
|
}
|
|
|
|
const filepathChange = () => {
|
|
const items = testCaseWithSuite.value.data.request.filepath.split("=")
|
|
if (items && items.length > 1) {
|
|
testCaseWithSuite.value.data.request.form = [{
|
|
key: items[0],
|
|
value: items[1]
|
|
} as Pair]
|
|
}
|
|
}
|
|
const bodyType = ref(1)
|
|
function bodyTypeChange(e: number) {
|
|
let contentType = ""
|
|
switch (e) {
|
|
case 4:
|
|
contentType = 'application/x-www-form-urlencoded'
|
|
break;
|
|
case 5:
|
|
contentType = 'application/json'
|
|
break;
|
|
case 6:
|
|
contentType = 'multipart/form-data'
|
|
filepathChange()
|
|
break;
|
|
}
|
|
|
|
if (contentType !== "") {
|
|
testCaseWithSuite.value.data.request.header = insertOrUpdateIntoMap({
|
|
key: 'Content-Type',
|
|
value: contentType
|
|
} as Pair, testCaseWithSuite.value.data.request.header)
|
|
}
|
|
}
|
|
|
|
const lintingError = ref('')
|
|
function jsonFormat(space: number) {
|
|
const jsonText = testCaseWithSuite.value.data.request.body
|
|
if (bodyType.value !== 5 || jsonText === '') {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const jsonObj = jsonlint.parse(jsonText)
|
|
if (space >= 0) {
|
|
testCaseWithSuite.value.data.request.body = JSON.stringify(jsonObj, null, space)
|
|
}
|
|
lintingError.value = ''
|
|
} catch (e) {
|
|
lintingError.value = e.message
|
|
}
|
|
}
|
|
|
|
function insertOrUpdateIntoMap(pair: Pair, pairs: Pair[]) {
|
|
const index = pairs.findIndex((e) => e.key === pair.key)
|
|
if (index === -1) {
|
|
const oldPairs = pairs
|
|
pairs = [pair]
|
|
pairs = pairs.concat(oldPairs)
|
|
} else {
|
|
pairs[index] = pair
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
const pupularHeaders = ref([] as Pair[])
|
|
const pupularHeaderPairs = ref([] as Pair[])
|
|
API.PopularHeaders((e) => {
|
|
const headerCache = new Map<string, string>();
|
|
for (var i = 0; i < e.data.length; i++) {
|
|
const pair = {
|
|
key: e.data[i].key,
|
|
value: e.data[i].value
|
|
} as Pair
|
|
|
|
pupularHeaderPairs.value.push(pair)
|
|
|
|
if (!headerCache.get(pair.key)) {
|
|
headerCache.set(pair.key, "index")
|
|
|
|
pupularHeaders.value.push({
|
|
key: e.data[i].key,
|
|
value: e.data[i].value
|
|
} as Pair)
|
|
}
|
|
}
|
|
})
|
|
|
|
const queryPupularHeaders = (queryString: string, cb: (arg: any) => void) => {
|
|
const results = queryString
|
|
? pupularHeaders.value.filter(CreateFilter(queryString))
|
|
: pupularHeaders.value
|
|
|
|
results.forEach((e) => {
|
|
e.value = e.key
|
|
})
|
|
cb(results)
|
|
}
|
|
const queryHeaderValues = (queryString: string, cb: (arg: any) => void) => {
|
|
const results = queryString
|
|
? headerValues.value.filter(CreateFilter(queryString))
|
|
: headerValues.value
|
|
|
|
results.forEach((e) => {
|
|
e.value = e.key
|
|
})
|
|
cb(results)
|
|
}
|
|
|
|
const duplicateTestCaseDialog = ref(false)
|
|
const targetTestCaseName = ref('')
|
|
const openDuplicateTestCaseDialog = () => {
|
|
duplicateTestCaseDialog.value = true
|
|
targetTestCaseName.value = props.name + '-copy'
|
|
}
|
|
Magic.Keys(openDuplicateTestCaseDialog, ['Alt+KeyD'])
|
|
const duplicateTestCase = () => {
|
|
API.DuplicateTestCase(props.suite, props.suite, props.name, targetTestCaseName.value,(d) => {
|
|
duplicateTestCaseDialog.value = false
|
|
ElMessage({
|
|
showClose: true,
|
|
message: 'Duplicated.',
|
|
type: 'success'
|
|
})
|
|
emit('updated')
|
|
})
|
|
}
|
|
Magic.Keys(() => {
|
|
if (duplicateTestCaseDialog.value) {
|
|
duplicateTestCase()
|
|
}
|
|
}, ['Alt+KeyO'])
|
|
|
|
const renameTestCase = (name: string) => {
|
|
const suiteName = props.suite
|
|
API.RenameTestCase(suiteName, suiteName, props.name, name, (d) => {
|
|
emit('updated', suiteName, name)
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<el-container style="height: 100%;">
|
|
<el-header style="padding-left: 5px;">
|
|
<div style="margin-bottom: 5px">
|
|
<el-button type="primary" @click="saveTestCase" :icon="Edit" v-loading="saveLoading"
|
|
disabled v-if="Cache.GetCurrentStore().readOnly || isHistoryTestCase"
|
|
>{{ t('button.save') }}</el-button>
|
|
<el-button type="primary" @click="saveTestCase" :icon="Edit" v-loading="saveLoading"
|
|
v-if="!Cache.GetCurrentStore().readOnly && !isHistoryTestCase"
|
|
>{{ t('button.save') }}</el-button>
|
|
<el-button type="danger" @click="deleteCase" :icon="Delete">{{ t('button.delete') }}</el-button>
|
|
<el-button type="primary" @click="openDuplicateTestCaseDialog" :icon="CopyDocument" v-if="!isHistoryTestCase">{{ t('button.duplicate') }}</el-button>
|
|
<el-button type="primary" @click="openCodeDialog">{{ t('button.generateCode') }}</el-button>
|
|
<el-button type="primary" v-if="!isHistoryTestCase && Cache.GetCurrentStore().kind.name == 'atest-store-orm'" @click="openHistoryDialog">{{ t('button.viewHistory') }}</el-button>
|
|
<span v-if="isHistoryTestCase" style="margin-left: 15px;">{{ t('tip.runningAt') }}{{ HistoryTestCaseCreateTime }}</span>
|
|
<EditButton :value="props.name" @changed="renameTestCase"/>
|
|
</div>
|
|
<div>
|
|
<el-row>
|
|
<el-col :span="3">
|
|
<el-select
|
|
v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'"
|
|
v-model="testCaseWithSuite.data.request.method"
|
|
class="m-2"
|
|
placeholder="Method"
|
|
size="default"
|
|
test-id="case-editor-method"
|
|
:disabled="isHistoryTestCase"
|
|
>
|
|
<el-option
|
|
v-for="item in options"
|
|
:key="item.value"
|
|
:label="item.key"
|
|
:value="item.value"
|
|
>
|
|
<el-text class="mx-1" :type="item.type">{{ item.key }}</el-text>
|
|
</el-option>
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="18">
|
|
<el-autocomplete
|
|
v-model="testCaseWithSuite.data.request.api"
|
|
style="width: 100%"
|
|
:fetch-suggestions="querySuggestedAPIs"
|
|
:readonly="isHistoryTestCase">
|
|
<template #default="{ item }">
|
|
<div class="value">{{ item.request.method }}</div>
|
|
<span class="link">{{ item.request.api }}</span>
|
|
</template>
|
|
<template #prefix v-if="!testCaseWithSuite.data.request.api.startsWith('http://') && !testCaseWithSuite.data.request.api.startsWith('https://')">
|
|
{{ testCaseWithSuite.data.server }}
|
|
</template>
|
|
</el-autocomplete>
|
|
</el-col>
|
|
<el-col :span="3">
|
|
<el-dropdown split-button type="primary"
|
|
@click="sendRequest"
|
|
v-loading="requestLoading"
|
|
v-if="!isHistoryTestCase">
|
|
{{ t('button.send') }}
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item @click="openParameterDialog">{{ t('button.sendWithParam') }}</el-dropdown-item>
|
|
<el-dropdown-item @click="openBatchRunDialog">Batch Send</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</el-header>
|
|
|
|
<el-main style="padding-left: 5px;">
|
|
<el-tabs v-model="requestActiveTab">
|
|
<el-tab-pane name="query" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<template #label>
|
|
<el-badge :value="testCaseWithSuite.data.request.query.length - 1"
|
|
:hidden="testCaseWithSuite.data.request.query.length <=1 " class="item">Query</el-badge>
|
|
</template>
|
|
<el-table :data="testCaseWithSuite.data.request.query" style="width: 100%">
|
|
<el-table-column label="Key" width="180">
|
|
<template #default="scope">
|
|
<el-autocomplete
|
|
v-model="scope.row.key"
|
|
placeholder="Key"
|
|
@change="queryChange"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane name="header">
|
|
<template #label>
|
|
<el-badge :value="testCaseWithSuite.data.request.header.length - 1"
|
|
:hidden="testCaseWithSuite.data.request.header.length <= 1" class="item">Header</el-badge>
|
|
</template>
|
|
<el-table :data="testCaseWithSuite.data.request.header" style="width: 100%">
|
|
<el-table-column label="Key" width="180">
|
|
<template #default="scope">
|
|
<el-autocomplete
|
|
v-model="scope.row.key"
|
|
:fetch-suggestions="queryPupularHeaders"
|
|
placeholder="Key"
|
|
@change="headerChange"
|
|
@select="headerSelect"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-autocomplete
|
|
v-model="scope.row.value"
|
|
:fetch-suggestions="queryHeaderValues"
|
|
style="width: 100%;"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane name="cookie">
|
|
<template #label>
|
|
<el-badge :value="testCaseWithSuite.data.request.cookie.length - 1"
|
|
:hidden="testCaseWithSuite.data.request.cookie.length <= 1" class="item">Cookie</el-badge>
|
|
</template>
|
|
<el-table :data="testCaseWithSuite.data.request.cookie" style="width: 100%">
|
|
<el-table-column label="Key">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.key" placeholder="Key"
|
|
@change="cookieChange"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="value" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane name="body">
|
|
<span style="margin-right: 10px; padding-right: 5px;">
|
|
<el-button type="primary" @click="jsonFormat(4)">Beautify</el-button>
|
|
<el-button type="primary" @click="jsonFormat(0)">Minify</el-button>
|
|
<el-text class="mx-1">Choose the body format</el-text>
|
|
</span>
|
|
<template #label>
|
|
<el-badge :is-dot="testCaseWithSuite.data.request.body !== ''" class="item">Body</el-badge>
|
|
</template>
|
|
<el-radio-group v-model="bodyType" @change="bodyTypeChange">
|
|
<el-radio :value="1">none</el-radio>
|
|
<el-radio :value="2">form-data</el-radio>
|
|
<el-radio :value="3">raw</el-radio>
|
|
<el-radio :value="4">x-www-form-urlencoded</el-radio>
|
|
<el-radio :value="5">JSON</el-radio>
|
|
<el-radio :value="6">EmbedFile</el-radio>
|
|
</el-radio-group>
|
|
|
|
<div style="flex-grow: 1;">
|
|
<div v-if="bodyType === 6">
|
|
<el-row>
|
|
<el-col :span="4">Filename:</el-col>
|
|
<el-col :span="20">
|
|
<el-input v-model="testCaseWithSuite.data.request.filepath" placeholder="file=sample.txt" @change="filepathChange" />
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
<Codemirror v-if="bodyType === 3 || bodyType === 5 || bodyType === 6"
|
|
@blur="jsonFormat(-1)"
|
|
v-model="testCaseWithSuite.data.request.body"
|
|
:disabled="isHistoryTestCase"/>
|
|
<el-table :data="testCaseWithSuite.data.request.form" style="width: 100%" v-if="bodyType === 4">
|
|
<el-table-column label="Key" width="180">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.key" placeholder="Key" @change="formChange" :readonly="isHistoryTestCase"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
<div v-if="lintingError" style="color: red; margin-top: 10px;">
|
|
{{ lintingError }}
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="Expected" name="expected" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<el-row>
|
|
<el-col :span="4">
|
|
Status Code:
|
|
</el-col>
|
|
<el-col :span="20">
|
|
<el-input
|
|
v-model="testCaseWithSuite.data.response.statusCode"
|
|
class="w-50 m-2"
|
|
placeholder="Please input"
|
|
:readonly="isHistoryTestCase">
|
|
<template #append>
|
|
{{ t('httpCode.' + testCaseWithSuite.data.response.statusCode) }}
|
|
</template>
|
|
</el-input>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row>
|
|
<el-col :span="4">Body:</el-col>
|
|
<el-col :span="20">
|
|
<el-input
|
|
v-model="testCaseWithSuite.data.response.body"
|
|
:autosize="{ minRows: 4, maxRows: 8 }"
|
|
type="textarea"
|
|
placeholder="Expected Body"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</el-col>
|
|
</el-row>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="Expected Headers" name="expected-headers" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<el-table :data="testCaseWithSuite.data.response.header" style="width: 100%">
|
|
<el-table-column label="Key" width="180">
|
|
<template #default="scope">
|
|
<el-input
|
|
v-model="scope.row.key"
|
|
placeholder="Key"
|
|
@change="expectedHeaderChange"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="BodyFiledExpect" name="bodyFieldExpect" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<el-table :data="testCaseWithSuite.data.response.bodyFieldsExpect" style="width: 100%">
|
|
<el-table-column label="Key" width="180">
|
|
<template #default="scope">
|
|
<el-autocomplete
|
|
v-model="scope.row.key"
|
|
:fetch-suggestions="queryBodyFields"
|
|
clearable
|
|
placeholder="Key"
|
|
@change="bodyFiledExpectChange"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="Verify" name="verify" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<div v-for="verify in testCaseWithSuite.data.response.verify" :key="verify">
|
|
<el-input :value="verify" :readonly="isHistoryTestCase"/>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="Schema" name="schema" v-if="props.kindName !== 'tRPC' && props.kindName !== 'gRPC'">
|
|
<el-input
|
|
v-model="testCaseWithSuite.data.response.schema"
|
|
:autosize="{ minRows: 4, maxRows: 20 }"
|
|
type="textarea"
|
|
:readonly="isHistoryTestCase"
|
|
/>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
|
|
<el-drawer v-model="codeDialogOpened" size="50%">
|
|
<template #header>
|
|
<h4>{{ t('title.codeGenerator') }}</h4>
|
|
</template>
|
|
<template #default>
|
|
<div style="padding-bottom: 10px;">
|
|
<el-select
|
|
v-model="currentCodeGenerator"
|
|
class="m-2"
|
|
style="padding-right: 10px;"
|
|
size="default"
|
|
>
|
|
<el-option
|
|
v-for="item in codeGenerators"
|
|
:key="item.key"
|
|
:label="item.key"
|
|
:value="item.key"
|
|
/>
|
|
</el-select>
|
|
<el-button type="primary" @click="generateCode">{{ t('button.refresh') }}</el-button>
|
|
<el-button type="primary" @click="copyCode">{{ t('button.copy') }}</el-button>
|
|
</div>
|
|
<Codemirror v-model="currentCodeContent"/>
|
|
</template>
|
|
</el-drawer>
|
|
|
|
<el-dialog @close="handleDialogClose" v-model="historyDialogOpened" :title="t('button.viewHistory')" width="60%" draggable>
|
|
<el-form
|
|
ref="viewHistoryRef"
|
|
:model="historyForm"
|
|
status-icon label-width="120px"
|
|
:rules="rules"
|
|
>
|
|
<el-form-item :label="t('title.history')" prop="selectedID">
|
|
<el-row :gutter="20">
|
|
<el-col :span="20">
|
|
<el-select class="m-2"
|
|
filterable
|
|
clearable
|
|
v-model="historyForm.selectedID"
|
|
default-first-option
|
|
placeholder="History Case"
|
|
size="middle"
|
|
@change="handleHistoryChange"
|
|
>
|
|
<el-option
|
|
v-for="item in historyRecords"
|
|
:key="item.ID"
|
|
:label="item.createTime"
|
|
:value="item.ID"
|
|
/>
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<div style="display: flex;flex-wrap: nowrap;justify-content: flex-end;">
|
|
<el-button
|
|
type="primary"
|
|
@click="submitForm(viewHistoryRef)"
|
|
:loading="caseRevertLoading"
|
|
>{{ t('button.revert') }}
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
@click="goToHistory(viewHistoryRef)"
|
|
:loading="caseRevertLoading"
|
|
>{{ t('button.goToHistory') }}
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
@click="deleteAllHistory(viewHistoryRef)"
|
|
:loading="caseRevertLoading"
|
|
>{{ t('button.deleteAllHistory') }}
|
|
</el-button>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div id="compareView"></div>
|
|
</el-dialog>
|
|
|
|
<el-drawer v-model="parameterDialogOpened">
|
|
<template #header>
|
|
<h4>{{ t('title.apiRequestParameter') }}</h4>
|
|
</template>
|
|
<template #default>
|
|
<div v-if="batchRunMode">
|
|
<el-row>
|
|
<el-col :span="6">
|
|
Count:
|
|
</el-col>
|
|
<el-col :span="18">
|
|
<el-input v-model="batchRunCount" type="number" min="1" max="100"/>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row>
|
|
<el-col :span="6">
|
|
Interval:
|
|
</el-col>
|
|
<el-col :span="18">
|
|
<el-input v-model="batchRunInterval" />
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
<el-table :data="parameters" style="width: 100%"
|
|
:empty-text="t('tip.noParameter')">
|
|
<el-table-column :label="t('field.key')" width="180">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.key" placeholder="Key" @change="paramChange"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('field.value')">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" />
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-button type="primary" @click="sendRequestWithParameter">{{ t('button.send') }}</el-button>
|
|
</template>
|
|
</el-drawer>
|
|
</el-main>
|
|
|
|
<el-footer style="height: auto;">
|
|
<el-tabs v-model="testResultActiveTab">
|
|
<el-tab-pane name="output">
|
|
<template #label>
|
|
<el-badge :is-dot="testResult.output !== ''" class="item">{{ t('title.output') }}</el-badge>
|
|
</template>
|
|
<el-tag class="ml-2" type="success" v-if="testResult.statusCode && testResult.error === ''">{{ t('httpCode.' + testResult.statusCode) }}</el-tag>
|
|
<el-tag class="ml-2" type="danger" v-if="testResult.statusCode && testResult.error !== ''">{{ t('httpCode.' + testResult.statusCode) }}</el-tag>
|
|
|
|
<Codemirror v-model="testResult.output"/>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="Body" name="body">
|
|
<div v-if="testResult.bodyObject">
|
|
<el-input :prefix-icon="Search" @change="responseBodyFilter" v-model="responseBodyFilterText"
|
|
clearable placeholder="$.key">
|
|
<template #prepend v-if="testResult.bodyLength > 0">Body Size: {{testResult.bodyLength}}</template>
|
|
</el-input>
|
|
<JsonViewer :value="testResult.bodyObject" :expand-depth="5" copyable boxed sort />
|
|
</div>
|
|
<div v-else>
|
|
<Codemirror v-if="!isResponseFile" v-model="testResult.bodyText"/>
|
|
<div v-if="isResponseFile" style="padding-top: 10px;">
|
|
<el-row>
|
|
<el-col :span="10">
|
|
<div>Response body is too large, please download to view.</div>
|
|
</el-col>
|
|
<el-col :span="2">
|
|
<el-button type="primary" @click="downloadResponseFile">Download</el-button>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
<el-tab-pane name="response-header">
|
|
<template #label>
|
|
<el-badge :value="testResult.header.length"
|
|
:hidden="testResult.header.length === 0" class="item">Header</el-badge>
|
|
</template>
|
|
<el-table :data="testResult.header" style="width: 100%">
|
|
<el-table-column label="Key" width="200">
|
|
<template #default="scope">
|
|
<el-input v-model="scope.row.key" placeholder="Key" readonly="true" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="Value">
|
|
<template #default="scope">
|
|
<div style="display: flex; align-items: center">
|
|
<el-input v-model="scope.row.value" placeholder="Value" readonly="true" />
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</el-footer>
|
|
</el-container>
|
|
|
|
<el-drawer v-model="duplicateTestCaseDialog">
|
|
<template #default>
|
|
New Test Case Name:<el-input v-model="targetTestCaseName" />
|
|
</template>
|
|
<template #footer>
|
|
<el-button type="primary" @click="duplicateTestCase">{{ t('button.ok') }}</el-button>
|
|
</template>
|
|
</el-drawer>
|
|
</template>
|