<template>
|
<div>
|
<el-table
|
:data="[summary]"
|
size="medium"
|
style="width: 100%"
|
border
|
stripe
|
:header-cell-style="{ textAlign: 'center', background: '#F8F8FA' }"
|
:cell-style="{ textAlign: 'center' }"
|
>
|
<el-table-column label="测试时间" width="160">
|
<template v-slot="scope">
|
<span>{{ scope.row.time.start_at | timestampToTime }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="持续时间" width="100">
|
<template v-slot="scope">
|
<span
|
v-text="scope.row.time.duration.toFixed(3) + ' 秒'"
|
></span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="总数" width="100">
|
<template v-slot="scope">
|
<el-tag>{{ scope.row.stat.testsRun }}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="成功" width="100">
|
<template v-slot="scope">
|
<el-tag type="success">{{
|
scope.row.stat.successes
|
}}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="失败" width="100">
|
<template v-slot="scope">
|
<el-tag type="danger">{{ scope.row.stat.failures }}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="错误" width="100">
|
<template v-slot="scope">
|
<el-tag type="warning">{{ scope.row.stat.errors }}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="跳过" width="100">
|
<template v-slot="scope">
|
<el-tag type="info">{{ scope.row.stat.skipped }}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="平台">
|
<template v-slot="scope">
|
<el-popover trigger="hover" placement="top">
|
<p>
|
HttpRunner:
|
{{ scope.row.platform.httprunner_version }}
|
</p>
|
<p>Python: {{ scope.row.platform.python_version }}</p>
|
<div slot="reference" class="name-wrapper">
|
<el-tag size="medium">
|
{{ scope.row.platform.platform }}</el-tag
|
>
|
</div>
|
</el-popover>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<br />
|
<br />
|
<slot v-for="(item, index) in summary.details">
|
<div :key="index">
|
<span style="font-weight: bold; font-size: medium;">{{
|
item.name
|
}}</span>
|
<el-popover placement="top-start" width="400" trigger="hover">
|
<pre class="code-block">{{ item.in_out }}</pre>
|
<el-button slot="reference" round type="text"
|
>参数 & 输出</el-button
|
>
|
</el-popover>
|
</div>
|
<el-table
|
:data="item.records"
|
@expand-change="expandChange"
|
:row-class-name="tableRowClassName"
|
style="width: 100%"
|
border
|
:header-cell-style="{
|
textAlign: 'center',
|
background: '#F8F8FA'
|
}"
|
:cell-style="{ textAlign: 'center' }"
|
>
|
<el-table-column type="expand" fixed>
|
<template v-slot="props">
|
<el-tabs @tab-click="handleClick">
|
<el-tab-pane label="请求">
|
<pre
|
class="code-block"
|
v-html="
|
handleRequest(
|
props.row.meta_data.request
|
)
|
"
|
></pre>
|
</el-tab-pane>
|
<el-tab-pane
|
label="内容"
|
v-if="
|
props.row.meta_data.response.jsonCopy !==
|
null
|
"
|
>
|
<v-jsoneditor
|
ref="jsonEditor"
|
v-model="
|
props.row.meta_data.response.jsonCopy
|
"
|
:options="options"
|
:plus="true"
|
:height="height"
|
@error="onError"
|
></v-jsoneditor>
|
</el-tab-pane>
|
<el-tab-pane label="响应">
|
<pre
|
class="code-block"
|
v-text="
|
handleResponse(
|
props.row.meta_data.response
|
)
|
"
|
></pre>
|
</el-tab-pane>
|
<el-tab-pane
|
label="验证器"
|
v-if="
|
props.row.meta_data.validators.length !== 0
|
"
|
>
|
<el-table
|
:data="props.row.meta_data.validators"
|
stripe
|
border
|
style="width: 100%"
|
>
|
<el-table-column
|
prop="check_result"
|
label="是否通过"
|
width="80"
|
></el-table-column>
|
<el-table-column
|
prop="check"
|
label="取值表达式"
|
width="350"
|
>
|
</el-table-column>
|
<el-table-column
|
prop="check_value"
|
label="实际值"
|
:formatter="checkValueFormatter"
|
>
|
</el-table-column>
|
<el-table-column
|
prop="comparator"
|
label="比较器"
|
:formatter="comparatorFormatter"
|
></el-table-column>
|
<el-table-column
|
prop="expect"
|
label="期望值"
|
:formatter="expectValueFormatter"
|
></el-table-column>
|
<el-table-column
|
prop="desc"
|
label="描述"
|
:formatter="descValueFormatter"
|
></el-table-column>
|
</el-table>
|
</el-tab-pane>
|
<el-tab-pane
|
label="Exception"
|
v-if="props.row.attachment !== ''"
|
>
|
<pre
|
class="code-block"
|
v-html="props.row.attachment"
|
></pre>
|
</el-tab-pane>
|
</el-tabs>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="名 称">
|
<template v-slot="scope">
|
<span
|
:title="scope.row.name"
|
class="report-table-hidden"
|
>{{ scope.row.name }}</span
|
>
|
</template>
|
</el-table-column>
|
<el-table-column label="请求地址">
|
<template v-slot="scope">
|
<span
|
:title="scope.row.meta_data.request.url"
|
class="report-table-hidden"
|
>{{ scope.row.meta_data.request.url }}</span
|
>
|
</template>
|
</el-table-column>
|
<el-table-column label="请求方法" width="100">
|
<template v-slot="scope">
|
<span :class="scope.row.meta_data.request.method">{{
|
scope.row.meta_data.request.method
|
}}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="响应时间 (ms)" width="130">
|
<template v-slot="scope">
|
<span>{{
|
scope.row.meta_data.response.elapsed_ms
|
}}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="测试结果" width="100">
|
<template v-slot="scope">
|
<div :class="scope.row.status">
|
{{ statusFormatter(scope.row.status) }}
|
</div>
|
</template>
|
</el-table-column>
|
<el-table-column label="转换" width="200">
|
<template slot-scope="scope">
|
<el-popover
|
placement="right-start"
|
width="400"
|
trigger="hover"
|
>
|
<pre class="code-block">{{
|
scope.row.meta_data.curl
|
}}</pre>
|
<el-button
|
slot="reference"
|
round
|
type="text"
|
@click="
|
copyDataText(
|
scope.row.meta_data.curl,
|
'curl'
|
)
|
"
|
>curl</el-button
|
>
|
</el-popover>
|
</template>
|
</el-table-column>
|
</el-table>
|
</slot>
|
</div>
|
</template>
|
|
<script>
|
export default {
|
name: "DebugReport",
|
props: {
|
summary: {
|
required: true
|
}
|
},
|
computed: {
|
height() {
|
return (window.screen.height - 464).toString() + "px";
|
}
|
},
|
data() {
|
let self = this;
|
return {
|
jsonPathOrValue: "",
|
expandedRows: [],
|
options: {
|
onModeChange(newMode, oldMode) {
|
if (newMode === "view") {
|
self.$refs.jsonEditor[0].editor.expandAll();
|
}
|
},
|
onEvent(node, event) {
|
if (event.type === "click") {
|
let value = node.value;
|
// 当前点击的位置有value, 复制value
|
if (value) {
|
self.jsonPathOrValue = value;
|
self.copyData("复制json value成功");
|
} else {
|
// 当前点击的位置没有value, 复制path
|
let arr = node.path;
|
arr.unshift("content");
|
self.jsonPathOrValue = arr.join(".");
|
self.copyData("复制json path成功");
|
}
|
}
|
},
|
mode: "view",
|
modes: ["view", "code"] // allowed modes
|
}
|
};
|
},
|
methods: {
|
checkValueFormatter(row, column) {
|
return this.valueFormatter(row.check_value);
|
},
|
expectValueFormatter(row, column) {
|
return this.valueFormatter(row.expect);
|
},
|
descValueFormatter(row, column) {
|
return this.valueFormatter(row.desc);
|
},
|
valueFormatter(value) {
|
let parsedValue = "";
|
switch (typeof value) {
|
case "object":
|
parsedValue = JSON.stringify(value);
|
break;
|
case "boolean":
|
if (value === true) {
|
parsedValue = "True";
|
} else {
|
parsedValue = "False";
|
}
|
break;
|
default:
|
parsedValue = value;
|
}
|
return parsedValue;
|
},
|
expandChange(row, expandedRow) {
|
this.expandedRows = expandedRow.length;
|
},
|
tableRowClassName({ row, rowIndex }) {
|
row.row_index = rowIndex;
|
},
|
handleClick(tab) {
|
if (tab.label === "Content") {
|
for (let i = 0; i < this.expandedRows; i++) {
|
this.$refs.jsonEditor[i * 2].editor.expandAll();
|
}
|
}
|
},
|
truncateString(str, num) {
|
if (str.length > num) {
|
return str.slice(0, num) + "...";
|
} else {
|
return str;
|
}
|
},
|
copyDataText(text, title) {
|
this.$copyText(text).then(
|
() => {
|
this.$notify.success({
|
title: "复制" + title + "成功",
|
message: this.truncateString(text, 30),
|
duration: 2000
|
});
|
},
|
e => {
|
this.$notify.error({
|
title: "复制" + title + "失败",
|
message: e,
|
duration: 2000
|
});
|
}
|
);
|
},
|
copyData(title) {
|
this.$copyText(this.jsonPathOrValue).then(
|
() => {
|
this.$notify.success({
|
title: title,
|
message: this.truncateString(this.jsonPathOrValue, 30),
|
duration: 2000
|
});
|
},
|
e => {
|
this.$notify.error({
|
title: "复制提取路径错误",
|
message: e,
|
duration: 2000
|
});
|
}
|
);
|
},
|
onError() {
|
console.log("error");
|
},
|
handleRequest(request) {
|
const keys = ["start_timestamp"];
|
keys.forEach(item => {
|
delete request[item];
|
});
|
|
try {
|
request["body"] = JSON.parse(request["body"]);
|
} catch (e) {
|
console.log(e);
|
}
|
return request;
|
},
|
handleResponse(response) {
|
const keys = [
|
"response_time_ms",
|
"encoding",
|
"ok",
|
"reason",
|
"url",
|
"text",
|
"json",
|
"content_size",
|
"content_type"
|
];
|
keys.forEach(item => {
|
delete response[item];
|
});
|
return response;
|
},
|
comparatorFormatter(row, column) {
|
const comparatorMap = {
|
'equals': '等于',
|
'less_than': '小于',
|
'less_than_or_equals': '小于等于',
|
'greater_than': '大于',
|
'greater_than_or_equals': '大于等于',
|
'not_equals': '不等于',
|
'string_equals': '字符串相等',
|
'length_equals': '长度相等',
|
'length_greater_than': '长度大于',
|
'length_greater_than_or_equals': '长度大于等于',
|
'length_less_than': '长度小于',
|
'length_less_than_or_equals': '长度小于等于',
|
'contains': '包含',
|
'not_contains': '不包含',
|
'contained_by': '被包含',
|
'list_any_item_contains': '列表任意元素包含',
|
'list_all_item_contains': '列表所有元素包含',
|
'type_match': '类型匹配',
|
'regex_match': '正则匹配',
|
'startswith': '以...开头',
|
'endswith': '以...结尾'
|
};
|
return comparatorMap[row.comparator] || row.comparator;
|
},
|
statusFormatter(status) {
|
const statusMap = {
|
'success': '成功',
|
'failure': '失败',
|
'error': '错误',
|
'skipped': '跳过'
|
};
|
return statusMap[status] || status;
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
pre {
|
white-space: pre-wrap;
|
word-wrap: break-word;
|
}
|
|
.report-table-hidden {
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
</style>
|