Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
MeterSphere
metersphere
提交
e9e27f42
M
metersphere
项目概览
MeterSphere
/
metersphere
上一次同步 大约 3 年
通知
25
Star
1
Fork
1
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
metersphere
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e9e27f42
编写于
4月 16, 2020
作者:
C
Captain.B
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'origin/dev' into dev
# Conflicts: # backend/pom.xml
上级
b7ae9c5e
3634544a
变更
32
隐藏空白更改
内联
并排
Showing
32 changed file
with
1908 addition
and
792 deletion
+1908
-792
backend/pom.xml
backend/pom.xml
+8
-4
backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java
...rc/main/java/io/metersphere/base/domain/ZaleniumTest.java
+0
-194
backend/src/main/java/io/metersphere/commons/constants/TestCaseConstants.java
...a/io/metersphere/commons/constants/TestCaseConstants.java
+5
-0
backend/src/main/java/io/metersphere/controller/TestCaseController.java
...in/java/io/metersphere/controller/TestCaseController.java
+13
-5
backend/src/main/java/io/metersphere/excel/domain/ExcelErrData.java
...c/main/java/io/metersphere/excel/domain/ExcelErrData.java
+42
-0
backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java
.../main/java/io/metersphere/excel/domain/ExcelResponse.java
+25
-0
backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java
...n/java/io/metersphere/excel/domain/TestCaseExcelData.java
+145
-0
backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java
...java/io/metersphere/excel/listener/EasyExcelListener.java
+140
-0
backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java
...a/io/metersphere/excel/listener/TestCaseDataListener.java
+141
-0
backend/src/main/java/io/metersphere/excel/utils/EasyExcelUtil.java
...c/main/java/io/metersphere/excel/utils/EasyExcelUtil.java
+30
-0
backend/src/main/java/io/metersphere/excel/utils/ExcelValidateHelper.java
.../java/io/metersphere/excel/utils/ExcelValidateHelper.java
+32
-0
backend/src/main/java/io/metersphere/exception/ExcelException.java
...rc/main/java/io/metersphere/exception/ExcelException.java
+18
-0
backend/src/main/java/io/metersphere/service/TestCaseNodeService.java
...main/java/io/metersphere/service/TestCaseNodeService.java
+141
-2
backend/src/main/java/io/metersphere/service/TestCaseService.java
...src/main/java/io/metersphere/service/TestCaseService.java
+122
-10
frontend/src/business/components/api/test/ApiScenarioConfig.vue
...nd/src/business/components/api/test/ApiScenarioConfig.vue
+170
-0
frontend/src/business/components/api/test/EditApiTest.vue
frontend/src/business/components/api/test/EditApiTest.vue
+0
-242
frontend/src/business/components/api/test/components/ApiBody.vue
...d/src/business/components/api/test/components/ApiBody.vue
+47
-0
frontend/src/business/components/api/test/components/ApiCollapse.vue
...c/business/components/api/test/components/ApiCollapse.vue
+70
-0
frontend/src/business/components/api/test/components/ApiCollapseItem.vue
...siness/components/api/test/components/ApiCollapseItem.vue
+125
-0
frontend/src/business/components/api/test/components/ApiKeyValue.vue
...c/business/components/api/test/components/ApiKeyValue.vue
+83
-0
frontend/src/business/components/api/test/components/ApiRequest.vue
...rc/business/components/api/test/components/ApiRequest.vue
+151
-0
frontend/src/business/components/api/test/components/ApiRequestForm.vue
...usiness/components/api/test/components/ApiRequestForm.vue
+75
-0
frontend/src/business/components/api/test/components/ApiScenarioForm.vue
...siness/components/api/test/components/ApiScenarioForm.vue
+49
-0
frontend/src/business/components/api/test/components/ApiTestRuntimeConfig.vue
...s/components/api/test/components/ApiTestRuntimeConfig.vue
+0
-121
frontend/src/business/components/api/test/components/ApiTestSceneConfig.vue
...ess/components/api/test/components/ApiTestSceneConfig.vue
+0
-196
frontend/src/business/components/common/router/router.js
frontend/src/business/components/common/router/router.js
+5
-5
frontend/src/business/components/performance/report/components/TestOverview.vue
...components/performance/report/components/TestOverview.vue
+81
-10
frontend/src/business/components/track/case/TestCase.vue
frontend/src/business/components/track/case/TestCase.vue
+1
-0
frontend/src/business/components/track/case/components/TestCaseExport.vue
...iness/components/track/case/components/TestCaseExport.vue
+17
-0
frontend/src/business/components/track/case/components/TestCaseImport.vue
...iness/components/track/case/components/TestCaseImport.vue
+135
-0
frontend/src/business/components/track/case/components/TestCaseList.vue
...usiness/components/track/case/components/TestCaseList.vue
+16
-2
frontend/src/i18n/zh-CN.js
frontend/src/i18n/zh-CN.js
+21
-1
未找到文件。
backend/pom.xml
浏览文件 @
e9e27f42
...
...
@@ -36,10 +36,6 @@
<artifactId>
spring-boot-starter-tomcat
</artifactId>
<groupId>
org.springframework.boot
</groupId>
</exclusion>
<exclusion>
<artifactId>
hibernate-validator
</artifactId>
<groupId>
org.hibernate.validator
</groupId>
</exclusion>
</exclusions>
</dependency>
...
...
@@ -138,6 +134,14 @@
<artifactId>
ApacheJMeter_core
</artifactId>
<version>
${jmeter.version}
</version>
</dependency>
<!-- easyexcel -->
<dependency>
<groupId>
com.alibaba
</groupId>
<artifactId>
easyexcel
</artifactId>
<version>
2.1.7
</version>
</dependency>
</dependencies>
<build>
...
...
backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java
已删除
100644 → 0
浏览文件 @
b7ae9c5e
package
io.metersphere.base.domain
;
public
class
ZaleniumTest
{
private
String
seleniumSessionId
;
private
String
testName
;
private
String
timestamp
;
private
String
addedToDashboardTime
;
private
String
browser
;
private
String
browserVersion
;
private
String
proxyName
;
private
String
platform
;
private
String
fileName
;
private
String
fileExtension
;
private
String
videoFolderPath
;
private
String
logsFolderPath
;
private
String
testNameNoExtension
;
private
String
screenDimension
;
private
String
timeZone
;
private
String
build
;
private
String
testFileNameTemplate
;
private
String
browserDriverLogFileName
;
private
String
retentionDate
;
private
String
testStatus
;
private
boolean
videoRecorded
;
public
String
getSeleniumSessionId
()
{
return
seleniumSessionId
;
}
public
void
setSeleniumSessionId
(
String
seleniumSessionId
)
{
this
.
seleniumSessionId
=
seleniumSessionId
;
}
public
String
getTestName
()
{
return
testName
;
}
public
void
setTestName
(
String
testName
)
{
this
.
testName
=
testName
;
}
public
String
getTimestamp
()
{
return
timestamp
;
}
public
void
setTimestamp
(
String
timestamp
)
{
this
.
timestamp
=
timestamp
;
}
public
String
getAddedToDashboardTime
()
{
return
addedToDashboardTime
;
}
public
void
setAddedToDashboardTime
(
String
addedToDashboardTime
)
{
this
.
addedToDashboardTime
=
addedToDashboardTime
;
}
public
String
getBrowser
()
{
return
browser
;
}
public
void
setBrowser
(
String
browser
)
{
this
.
browser
=
browser
;
}
public
String
getBrowserVersion
()
{
return
browserVersion
;
}
public
void
setBrowserVersion
(
String
browserVersion
)
{
this
.
browserVersion
=
browserVersion
;
}
public
String
getProxyName
()
{
return
proxyName
;
}
public
void
setProxyName
(
String
proxyName
)
{
this
.
proxyName
=
proxyName
;
}
public
String
getPlatform
()
{
return
platform
;
}
public
void
setPlatform
(
String
platform
)
{
this
.
platform
=
platform
;
}
public
String
getFileName
()
{
return
fileName
;
}
public
void
setFileName
(
String
fileName
)
{
this
.
fileName
=
fileName
;
}
public
String
getFileExtension
()
{
return
fileExtension
;
}
public
void
setFileExtension
(
String
fileExtension
)
{
this
.
fileExtension
=
fileExtension
;
}
public
String
getVideoFolderPath
()
{
return
videoFolderPath
;
}
public
void
setVideoFolderPath
(
String
videoFolderPath
)
{
this
.
videoFolderPath
=
videoFolderPath
;
}
public
String
getLogsFolderPath
()
{
return
logsFolderPath
;
}
public
void
setLogsFolderPath
(
String
logsFolderPath
)
{
this
.
logsFolderPath
=
logsFolderPath
;
}
public
String
getTestNameNoExtension
()
{
return
testNameNoExtension
;
}
public
void
setTestNameNoExtension
(
String
testNameNoExtension
)
{
this
.
testNameNoExtension
=
testNameNoExtension
;
}
public
String
getScreenDimension
()
{
return
screenDimension
;
}
public
void
setScreenDimension
(
String
screenDimension
)
{
this
.
screenDimension
=
screenDimension
;
}
public
String
getTimeZone
()
{
return
timeZone
;
}
public
void
setTimeZone
(
String
timeZone
)
{
this
.
timeZone
=
timeZone
;
}
public
String
getBuild
()
{
return
build
;
}
public
void
setBuild
(
String
build
)
{
this
.
build
=
build
;
}
public
String
getTestFileNameTemplate
()
{
return
testFileNameTemplate
;
}
public
void
setTestFileNameTemplate
(
String
testFileNameTemplate
)
{
this
.
testFileNameTemplate
=
testFileNameTemplate
;
}
public
String
getBrowserDriverLogFileName
()
{
return
browserDriverLogFileName
;
}
public
void
setBrowserDriverLogFileName
(
String
browserDriverLogFileName
)
{
this
.
browserDriverLogFileName
=
browserDriverLogFileName
;
}
public
String
getRetentionDate
()
{
return
retentionDate
;
}
public
void
setRetentionDate
(
String
retentionDate
)
{
this
.
retentionDate
=
retentionDate
;
}
public
String
getTestStatus
()
{
return
testStatus
;
}
public
void
setTestStatus
(
String
testStatus
)
{
this
.
testStatus
=
testStatus
;
}
public
boolean
isVideoRecorded
()
{
return
videoRecorded
;
}
public
void
setVideoRecorded
(
boolean
videoRecorded
)
{
this
.
videoRecorded
=
videoRecorded
;
}
}
backend/src/main/java/io/metersphere/commons/constants/TestCaseConstants.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.commons.constants
;
public
class
TestCaseConstants
{
public
static
final
int
MAX_NODE_DEPTH
=
5
;
}
backend/src/main/java/io/metersphere/controller/TestCaseController.java
浏览文件 @
e9e27f42
...
...
@@ -6,16 +6,14 @@ import io.metersphere.base.domain.*;
import
io.metersphere.commons.utils.PageUtils
;
import
io.metersphere.commons.utils.Pager
;
import
io.metersphere.controller.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.controller.request.testplan.QueryTestPlanRequest
;
import
io.metersphere.dto.LoadTestDTO
;
import
io.metersphere.dto.TestCaseNodeDTO
;
import
io.metersphere.dto.TestPlanCaseDTO
;
import
io.metersphere.service.TestCaseNodeService
;
import
io.metersphere.excel.domain.ExcelResponse
;
import
io.metersphere.service.TestCaseService
;
import
io.metersphere.user.SessionUtils
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
import
javax.annotation.Resource
;
import
javax.servlet.http.HttpServletResponse
;
import
java.util.List
;
@RequestMapping
(
"/test/case"
)
...
...
@@ -74,5 +72,15 @@ public class TestCaseController {
return
testCaseService
.
deleteTestCase
(
testCaseId
);
}
@PostMapping
(
"/import/{projectId}"
)
public
ExcelResponse
testCaseImport
(
MultipartFile
file
,
@PathVariable
String
projectId
){
return
testCaseService
.
testCaseImport
(
file
,
projectId
);
}
@GetMapping
(
"/export/template"
)
public
void
testCaseTemplateExport
(
HttpServletResponse
response
){
testCaseService
.
testCaseTemplateExport
(
response
);
}
}
backend/src/main/java/io/metersphere/excel/domain/ExcelErrData.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.domain
;
public
class
ExcelErrData
<
T
>
{
private
T
t
;
private
Integer
rowNum
;
private
String
errMsg
;
public
ExcelErrData
(){}
public
ExcelErrData
(
T
t
,
Integer
rowNum
,
String
errMsg
){
this
.
t
=
t
;
this
.
rowNum
=
rowNum
;
this
.
errMsg
=
errMsg
;
}
public
T
getT
()
{
return
t
;
}
public
void
setT
(
T
t
)
{
this
.
t
=
t
;
}
public
String
getErrMsg
()
{
return
errMsg
;
}
public
void
setErrMsg
(
String
errMsg
)
{
this
.
errMsg
=
errMsg
;
}
public
Integer
getRowNum
()
{
return
rowNum
;
}
public
void
setRowNum
(
Integer
rowNum
)
{
this
.
rowNum
=
rowNum
;
}
}
\ No newline at end of file
backend/src/main/java/io/metersphere/excel/domain/ExcelResponse.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.domain
;
import
java.util.List
;
public
class
ExcelResponse
<
T
>
{
private
Boolean
success
;
private
List
<
ExcelErrData
<
T
>>
errList
;
public
Boolean
getSuccess
()
{
return
success
;
}
public
void
setSuccess
(
Boolean
success
)
{
this
.
success
=
success
;
}
public
List
<
ExcelErrData
<
T
>>
getErrList
()
{
return
errList
;
}
public
void
setErrList
(
List
<
ExcelErrData
<
T
>>
errList
)
{
this
.
errList
=
errList
;
}
}
backend/src/main/java/io/metersphere/excel/domain/TestCaseExcelData.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.domain
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
com.alibaba.excel.annotation.write.style.ColumnWidth
;
import
com.alibaba.excel.annotation.write.style.ContentRowHeight
;
import
org.hibernate.validator.constraints.Length
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.Pattern
;
@ColumnWidth
(
15
)
public
class
TestCaseExcelData
{
@NotBlank
@Length
(
max
=
50
)
@ExcelProperty
(
"用例名称"
)
private
String
name
;
@NotBlank
@Length
(
max
=
1000
)
@ExcelProperty
(
"所属模块"
)
@ColumnWidth
(
30
)
@Pattern
(
regexp
=
"^(?!.*//).*$"
,
message
=
"格式不正确"
)
private
String
nodePath
;
@NotBlank
@ExcelProperty
(
"用例类型"
)
@Pattern
(
regexp
=
"(^functional$)|(^performance$)|(^api$)"
,
message
=
"必须为functional、performance、api"
)
private
String
type
;
@NotBlank
@ExcelProperty
(
"维护人"
)
private
String
maintainer
;
@NotBlank
@ExcelProperty
(
"优先级"
)
@Pattern
(
regexp
=
"(^P0$)|(^P1$)|(^P2$)|(^P3$)"
,
message
=
"必须为P0、P1、P2、P3"
)
private
String
priority
;
@NotBlank
@ExcelProperty
(
"测试方式"
)
@Pattern
(
regexp
=
"(^manual$)|(^auto$)"
,
message
=
"必须为manual、auto"
)
private
String
method
;
@ColumnWidth
(
50
)
@ExcelProperty
(
"前置条件"
)
@Length
(
min
=
0
,
max
=
1000
)
private
String
prerequisite
;
@ColumnWidth
(
50
)
@ExcelProperty
(
"备注"
)
@Length
(
max
=
1000
)
private
String
remark
;
@ColumnWidth
(
50
)
@ExcelProperty
(
"步骤描述"
)
@Length
(
max
=
1000
)
private
String
stepDesc
;
@ColumnWidth
(
50
)
@ExcelProperty
(
"预期结果"
)
@Length
(
max
=
1000
)
private
String
stepResult
;
public
String
getNodePath
()
{
return
nodePath
;
}
public
void
setNodePath
(
String
nodePath
)
{
this
.
nodePath
=
nodePath
;
}
public
String
getName
()
{
return
name
;
}
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
public
String
getType
()
{
return
type
;
}
public
void
setType
(
String
type
)
{
this
.
type
=
type
;
}
public
String
getMaintainer
()
{
return
maintainer
;
}
public
void
setMaintainer
(
String
maintainer
)
{
this
.
maintainer
=
maintainer
;
}
public
String
getPriority
()
{
return
priority
;
}
public
void
setPriority
(
String
priority
)
{
this
.
priority
=
priority
;
}
public
String
getMethod
()
{
return
method
;
}
public
void
setMethod
(
String
method
)
{
this
.
method
=
method
;
}
public
String
getPrerequisite
()
{
return
prerequisite
;
}
public
void
setPrerequisite
(
String
prerequisite
)
{
this
.
prerequisite
=
prerequisite
;
}
public
String
getRemark
()
{
return
remark
;
}
public
void
setRemark
(
String
remark
)
{
this
.
remark
=
remark
;
}
public
String
getStepDesc
()
{
return
stepDesc
;
}
public
void
setStepDesc
(
String
stepDesc
)
{
this
.
stepDesc
=
stepDesc
;
}
public
String
getStepResult
()
{
return
stepResult
;
}
public
void
setStepResult
(
String
stepResult
)
{
this
.
stepResult
=
stepResult
;
}
}
backend/src/main/java/io/metersphere/excel/listener/EasyExcelListener.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.listener
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
com.alibaba.excel.context.AnalysisContext
;
import
com.alibaba.excel.event.AnalysisEventListener
;
import
com.alibaba.excel.exception.ExcelAnalysisException
;
import
com.alibaba.excel.util.StringUtils
;
import
io.metersphere.commons.utils.LogUtil
;
import
io.metersphere.excel.utils.ExcelValidateHelper
;
import
io.metersphere.excel.domain.ExcelErrData
;
import
java.lang.reflect.Field
;
import
java.util.*
;
public
abstract
class
EasyExcelListener
<
T
>
extends
AnalysisEventListener
<
T
>
{
protected
List
<
ExcelErrData
<
T
>>
errList
=
new
ArrayList
<>();
protected
List
<
T
>
list
=
new
ArrayList
<>();
/**
* 每隔2000条存储数据库,然后清理list ,方便内存回收
*/
protected
static
final
int
BATCH_COUNT
=
2000
;
protected
Class
<
T
>
clazz
;
public
EasyExcelListener
(
Class
<
T
>
clazz
){
this
.
clazz
=
clazz
;
}
/**
* 这个每一条数据解析都会来调用
*
* @param t
* @param analysisContext
*/
@Override
public
void
invoke
(
T
t
,
AnalysisContext
analysisContext
)
{
String
errMsg
;
Integer
rowIndex
=
analysisContext
.
readRowHolder
().
getRowIndex
();
try
{
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
errMsg
=
ExcelValidateHelper
.
validateEntity
(
t
);
//自定义校验规则
errMsg
=
validate
(
t
,
errMsg
);
}
catch
(
NoSuchFieldException
e
)
{
errMsg
=
"解析数据出错"
;
LogUtil
.
error
(
e
.
getMessage
(),
e
);
}
if
(!
StringUtils
.
isEmpty
(
errMsg
))
{
ExcelErrData
excelErrData
=
new
ExcelErrData
(
t
,
rowIndex
,
"第"
+
rowIndex
+
"行出错:"
+
errMsg
);
errList
.
add
(
excelErrData
);
}
else
{
list
.
add
(
t
);
}
if
(
list
.
size
()
>
BATCH_COUNT
)
{
saveData
();
list
.
clear
();
}
}
/**
* 可重写该方法
* 自定义校验规则
* @param data
* @param errMsg
* @return
*/
public
String
validate
(
T
data
,
String
errMsg
)
{
return
errMsg
;
}
/**
* 自定义数据保存操作
*/
public
abstract
void
saveData
();
@Override
public
void
doAfterAllAnalysed
(
AnalysisContext
analysisContext
)
{
saveData
();
list
.
clear
();
}
/**
* 校验excel头部
* @param headMap 传入excel的头部(第一行数据)数据的index,name
* @param context
*/
@Override
public
void
invokeHeadMap
(
Map
<
Integer
,
String
>
headMap
,
AnalysisContext
context
)
{
super
.
invokeHeadMap
(
headMap
,
context
);
if
(
clazz
!=
null
){
try
{
Set
<
String
>
fieldNameSet
=
getFieldNameSet
(
clazz
);
Collection
<
String
>
values
=
headMap
.
values
();
for
(
String
key
:
fieldNameSet
)
{
if
(!
values
.
contains
(
key
)){
throw
new
ExcelAnalysisException
(
"缺少头部信息:"
+
key
);
}
}
}
catch
(
NoSuchFieldException
e
)
{
e
.
printStackTrace
();
}
}
}
/**
* @description: 获取注解里ExcelProperty的value
*/
public
Set
<
String
>
getFieldNameSet
(
Class
clazz
)
throws
NoSuchFieldException
{
Set
<
String
>
result
=
new
HashSet
<>();
Field
field
;
Field
[]
fields
=
clazz
.
getDeclaredFields
();
for
(
int
i
=
0
;
i
<
fields
.
length
;
i
++)
{
field
=
clazz
.
getDeclaredField
(
fields
[
i
].
getName
());
field
.
setAccessible
(
true
);
ExcelProperty
excelProperty
=
field
.
getAnnotation
(
ExcelProperty
.
class
);
if
(
excelProperty
!=
null
){
StringBuilder
value
=
new
StringBuilder
();
for
(
String
v
:
excelProperty
.
value
())
{
value
.
append
(
v
);
}
result
.
add
(
value
.
toString
());
}
}
return
result
;
}
public
List
<
ExcelErrData
<
T
>>
getErrList
()
{
return
errList
;
}
}
\ No newline at end of file
backend/src/main/java/io/metersphere/excel/listener/TestCaseDataListener.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.listener
;
import
com.alibaba.fastjson.JSONArray
;
import
com.alibaba.fastjson.JSONObject
;
import
io.metersphere.excel.domain.TestCaseExcelData
;
import
io.metersphere.base.domain.TestCaseWithBLOBs
;
import
io.metersphere.commons.constants.TestCaseConstants
;
import
io.metersphere.commons.utils.BeanUtils
;
import
io.metersphere.service.TestCaseService
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.UUID
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
java.util.stream.Collectors
;
public
class
TestCaseDataListener
extends
EasyExcelListener
<
TestCaseExcelData
>
{
private
TestCaseService
testCaseService
;
private
String
projectId
;
Set
<
String
>
testCaseNames
;
Set
<
String
>
userNames
;
public
TestCaseDataListener
(
TestCaseService
testCaseService
,
String
projectId
,
Set
<
String
>
testCaseNames
,
Set
<
String
>
userNames
,
Class
<
TestCaseExcelData
>
clazz
)
{
super
(
clazz
);
this
.
testCaseService
=
testCaseService
;
this
.
projectId
=
projectId
;
this
.
testCaseNames
=
testCaseNames
;
this
.
userNames
=
userNames
;
}
@Override
public
String
validate
(
TestCaseExcelData
data
,
String
errMsg
)
{
String
nodePath
=
data
.
getNodePath
();
StringBuilder
stringBuilder
=
new
StringBuilder
(
errMsg
);
if
(
nodePath
.
split
(
"/"
).
length
>
TestCaseConstants
.
MAX_NODE_DEPTH
+
1
)
{
stringBuilder
.
append
(
"节点最多为"
+
TestCaseConstants
.
MAX_NODE_DEPTH
+
"层;"
);
}
if
(
nodePath
.
trim
().
contains
(
" "
))
{
stringBuilder
.
append
(
"所属模块不能包含空格"
);
}
if
(!
userNames
.
contains
(
data
.
getMaintainer
()))
{
stringBuilder
.
append
(
"该工作空间下无该用户:"
+
data
.
getMaintainer
()
+
";"
);
}
if
(
testCaseNames
.
contains
(
data
.
getName
()))
{
stringBuilder
.
append
(
"该项目下已存在该测试用例:"
+
data
.
getName
()
+
";"
);
}
return
stringBuilder
.
toString
();
}
@Override
public
void
saveData
()
{
//无错误数据才插入数据
if
(!
errList
.
isEmpty
())
{
return
;
}
List
<
TestCaseWithBLOBs
>
result
=
list
.
stream
()
.
map
(
item
->
this
.
convert2TestCase
(
item
))
.
collect
(
Collectors
.
toList
());
testCaseService
.
saveImportData
(
result
,
projectId
);
}
private
TestCaseWithBLOBs
convert2TestCase
(
TestCaseExcelData
data
)
{
TestCaseWithBLOBs
testCase
=
new
TestCaseWithBLOBs
();
BeanUtils
.
copyBean
(
testCase
,
data
);
testCase
.
setId
(
UUID
.
randomUUID
().
toString
());
testCase
.
setProjectId
(
this
.
projectId
);
testCase
.
setCreateTime
(
System
.
currentTimeMillis
());
testCase
.
setUpdateTime
(
System
.
currentTimeMillis
());
String
nodePath
=
data
.
getNodePath
();
if
(!
nodePath
.
startsWith
(
"/"
))
{
nodePath
=
"/"
+
nodePath
;
}
if
(
nodePath
.
endsWith
(
"/"
))
{
nodePath
=
nodePath
.
substring
(
0
,
nodePath
.
length
()
-
1
);
}
testCase
.
setNodePath
(
nodePath
);
JSONArray
jsonArray
=
new
JSONArray
();
String
[]
stepDesc
=
new
String
[
0
];
String
[]
stepRes
=
new
String
[
0
];
if
(
data
.
getStepDesc
()
!=
null
)
{
stepDesc
=
data
.
getStepDesc
().
split
(
"\n"
);
}
if
(
data
.
getStepResult
()
!=
null
)
{
stepRes
=
data
.
getStepResult
().
split
(
"\n"
);
}
String
pattern
=
"(^\\d+)(\\.)?"
;
int
index
=
stepDesc
.
length
>
stepRes
.
length
?
stepDesc
.
length
:
stepRes
.
length
;
for
(
int
i
=
0
;
i
<
index
;
i
++){
JSONObject
step
=
new
JSONObject
();
step
.
put
(
"num"
,
i
+
1
);
Pattern
descPattern
=
Pattern
.
compile
(
pattern
);
Pattern
resPattern
=
Pattern
.
compile
(
pattern
);
if
(
i
<
stepDesc
.
length
)
{
Matcher
descMatcher
=
descPattern
.
matcher
(
stepDesc
[
i
]);
if
(
descMatcher
.
find
())
{
step
.
put
(
"desc"
,
descMatcher
.
replaceAll
(
""
));
}
else
{
step
.
put
(
"desc"
,
stepDesc
[
i
]);
}
}
if
(
i
<
stepRes
.
length
)
{
Matcher
resMatcher
=
resPattern
.
matcher
(
stepRes
[
i
]);
if
(
resMatcher
.
find
())
{
step
.
put
(
"result"
,
resMatcher
.
replaceAll
(
""
));
}
else
{
step
.
put
(
"result"
,
stepRes
[
i
]);
}
}
jsonArray
.
add
(
step
);
}
testCase
.
setSteps
(
jsonArray
.
toJSONString
());
return
testCase
;
}
}
backend/src/main/java/io/metersphere/excel/utils/EasyExcelUtil.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.utils
;
import
com.alibaba.excel.EasyExcel
;
import
io.metersphere.commons.utils.LogUtil
;
import
io.metersphere.exception.ExcelException
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.io.UnsupportedEncodingException
;
import
java.net.URLEncoder
;
import
java.util.List
;
public
class
EasyExcelUtil
{
public
static
void
export
(
HttpServletResponse
response
,
Class
clazz
,
List
data
,
String
fileName
,
String
sheetName
)
{
response
.
setContentType
(
"application/vnd.ms-excel"
);
response
.
setCharacterEncoding
(
"utf-8"
);
try
{
response
.
setHeader
(
"Content-disposition"
,
"attachment;filename="
+
URLEncoder
.
encode
(
fileName
,
"UTF-8"
)
+
".xlsx"
);
EasyExcel
.
write
(
response
.
getOutputStream
(),
clazz
).
sheet
(
sheetName
).
doWrite
(
data
);
}
catch
(
UnsupportedEncodingException
e
)
{
LogUtil
.
error
(
e
.
getMessage
(),
e
);
throw
new
ExcelException
(
"不支持UTF-8编码"
);
}
catch
(
IOException
e
)
{
LogUtil
.
error
(
e
.
getMessage
(),
e
);
throw
new
ExcelException
(
"IO异常"
);
}
}
}
backend/src/main/java/io/metersphere/excel/utils/ExcelValidateHelper.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.excel.utils
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
javax.validation.ConstraintViolation
;
import
javax.validation.Validation
;
import
javax.validation.Validator
;
import
javax.validation.groups.Default
;
import
java.lang.reflect.Field
;
import
java.util.Set
;
public
class
ExcelValidateHelper
{
private
ExcelValidateHelper
(){}
private
static
Validator
validator
=
Validation
.
buildDefaultValidatorFactory
().
getValidator
();
public
static
<
T
>
String
validateEntity
(
T
obj
)
throws
NoSuchFieldException
{
StringBuilder
result
=
new
StringBuilder
();
Set
<
ConstraintViolation
<
T
>>
set
=
validator
.
validate
(
obj
,
Default
.
class
);
if
(
set
!=
null
&&
!
set
.
isEmpty
())
{
for
(
ConstraintViolation
<
T
>
cv
:
set
)
{
Field
declaredField
=
obj
.
getClass
().
getDeclaredField
(
cv
.
getPropertyPath
().
toString
());
ExcelProperty
annotation
=
declaredField
.
getAnnotation
(
ExcelProperty
.
class
);
//拼接错误信息,包含当前出错数据的标题名字+错误信息
result
.
append
(
annotation
.
value
()[
0
]+
cv
.
getMessage
()).
append
(
";"
);
}
}
return
result
.
toString
();
}
}
\ No newline at end of file
backend/src/main/java/io/metersphere/exception/ExcelException.java
0 → 100644
浏览文件 @
e9e27f42
package
io.metersphere.exception
;
/**
* @author jianxing.chen
*/
public
class
ExcelException
extends
RuntimeException
{
private
static
final
long
serialVersionUID
=
1L
;
public
ExcelException
(
String
message
,
Exception
e
){
super
(
message
,
e
);
}
public
ExcelException
(
String
message
){
super
(
message
);
}
}
backend/src/main/java/io/metersphere/service/TestCaseNodeService.java
浏览文件 @
e9e27f42
...
...
@@ -6,8 +6,11 @@ import io.metersphere.base.mapper.TestCaseMapper;
import
io.metersphere.base.mapper.TestCaseNodeMapper
;
import
io.metersphere.base.mapper.TestPlanMapper
;
import
io.metersphere.base.mapper.TestPlanTestCaseMapper
;
import
io.metersphere.commons.constants.TestCaseConstants
;
import
io.metersphere.commons.utils.BeanUtils
;
import
io.metersphere.dto.TestCaseNodeDTO
;
import
io.metersphere.exception.ExcelException
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
...
...
@@ -30,8 +33,8 @@ public class TestCaseNodeService {
public
int
addNode
(
TestCaseNode
node
)
{
if
(
node
.
getLevel
()
>
5
){
throw
new
RuntimeException
(
"模块树最大深度为
5
层!"
);
if
(
node
.
getLevel
()
>
TestCaseConstants
.
MAX_NODE_DEPTH
){
throw
new
RuntimeException
(
"模块树最大深度为
"
+
TestCaseConstants
.
MAX_NODE_DEPTH
+
"
层!"
);
}
node
.
setCreateTime
(
System
.
currentTimeMillis
());
node
.
setUpdateTime
(
System
.
currentTimeMillis
());
...
...
@@ -196,4 +199,140 @@ public class TestCaseNodeService {
TestPlan
testPlan
=
testPlanMapper
.
selectByPrimaryKey
(
planId
);
return
getNodeTreeByProjectId
(
testPlan
.
getProjectId
());
}
public
Map
<
String
,
Integer
>
createNodeByTestCases
(
List
<
TestCaseWithBLOBs
>
testCases
,
String
projectId
)
{
List
<
TestCaseNodeDTO
>
nodeTrees
=
getNodeTreeByProjectId
(
projectId
);
Map
<
String
,
Integer
>
pathMap
=
new
HashMap
<>();
List
<
String
>
nodePaths
=
testCases
.
stream
()
.
map
(
TestCase:
:
getNodePath
)
.
collect
(
Collectors
.
toList
());
nodePaths
.
forEach
(
path
->
{
if
(
path
==
null
)
{
throw
new
ExcelException
(
"所属模块不能为空!"
);
}
List
<
String
>
nodeNameList
=
new
ArrayList
<>(
Arrays
.
asList
(
path
.
split
(
"/"
)));
Iterator
<
String
>
pathIterator
=
nodeNameList
.
iterator
();
Boolean
hasNode
=
false
;
String
rootNodeName
=
null
;
if
(
nodeNameList
.
size
()
<=
1
)
{
throw
new
ExcelException
(
"创建模块失败:"
+
path
);
}
else
{
pathIterator
.
next
();
pathIterator
.
remove
();
rootNodeName
=
pathIterator
.
next
().
trim
();
//原来没有,新建的树nodeTrees也不包含
for
(
TestCaseNodeDTO
nodeTree
:
nodeTrees
)
{
if
(
StringUtils
.
equals
(
rootNodeName
,
nodeTree
.
getName
()))
{
hasNode
=
true
;
createNodeByPathIterator
(
pathIterator
,
"/"
+
rootNodeName
,
nodeTree
,
pathMap
,
projectId
,
2
);
};
}
}
if
(!
hasNode
)
{
createNodeByPath
(
pathIterator
,
rootNodeName
,
null
,
projectId
,
1
,
""
,
pathMap
);
}
});
return
pathMap
;
}
/**
* 根据目标节点路径,创建相关节点
* @param pathIterator 遍历子路径
* @param path 当前路径
* @param treeNode 当前节点
* @param pathMap 记录节点路径对应的nodeId
*/
private
void
createNodeByPathIterator
(
Iterator
<
String
>
pathIterator
,
String
path
,
TestCaseNodeDTO
treeNode
,
Map
<
String
,
Integer
>
pathMap
,
String
projectId
,
Integer
level
)
{
List
<
TestCaseNodeDTO
>
children
=
treeNode
.
getChildren
();
if
(
children
==
null
||
children
.
isEmpty
()
||
!
pathIterator
.
hasNext
())
{
pathMap
.
put
(
path
,
treeNode
.
getId
());
if
(
pathIterator
.
hasNext
())
{
createNodeByPath
(
pathIterator
,
pathIterator
.
next
().
trim
(),
treeNode
,
projectId
,
level
,
path
,
pathMap
);
}
return
;
}
String
nodeName
=
pathIterator
.
next
().
trim
();
Boolean
hasNode
=
false
;
for
(
TestCaseNodeDTO
child
:
children
)
{
if
(
StringUtils
.
equals
(
nodeName
,
child
.
getName
()))
{
hasNode
=
true
;
createNodeByPathIterator
(
pathIterator
,
path
+
"/"
+
child
.
getName
(),
child
,
pathMap
,
projectId
,
level
+
1
);
};
}
//若子节点中不包含该目标节点,则在该节点下创建
if
(!
hasNode
)
{
createNodeByPath
(
pathIterator
,
nodeName
,
treeNode
,
projectId
,
level
,
path
,
pathMap
);
}
}
/**
*
* @param pathIterator 迭代器,遍历子节点
* @param nodeName 当前节点
* @param pNode 父节点
*/
private
void
createNodeByPath
(
Iterator
<
String
>
pathIterator
,
String
nodeName
,
TestCaseNodeDTO
pNode
,
String
projectId
,
Integer
level
,
String
rootPath
,
Map
<
String
,
Integer
>
pathMap
)
{
StringBuilder
path
=
new
StringBuilder
(
rootPath
);
path
.
append
(
"/"
+
nodeName
);
Integer
pid
=
null
;
//创建过不创建
if
(
pathMap
.
get
(
path
.
toString
())
!=
null
)
{
pid
=
pathMap
.
get
(
path
.
toString
());
level
++;
}
else
{
pid
=
insertTestCaseNode
(
nodeName
,
pNode
==
null
?
null
:
pNode
.
getId
(),
projectId
,
level
);
pathMap
.
put
(
path
.
toString
(),
pid
);
}
while
(
pathIterator
.
hasNext
())
{
String
nextNodeName
=
pathIterator
.
next
();
path
.
append
(
"/"
+
nextNodeName
);
if
(
pathMap
.
get
(
path
.
toString
())
!=
null
)
{
pid
=
pathMap
.
get
(
path
.
toString
());
level
++;
}
else
{
pid
=
insertTestCaseNode
(
nextNodeName
,
pid
,
projectId
,
level
);
pathMap
.
put
(
path
.
toString
(),
pid
);
}
}
}
private
Integer
insertTestCaseNode
(
String
nodeName
,
Integer
pId
,
String
projectId
,
Integer
level
)
{
TestCaseNode
testCaseNode
=
new
TestCaseNode
();
testCaseNode
.
setName
(
nodeName
.
trim
());
testCaseNode
.
setpId
(
pId
);
testCaseNode
.
setProjectId
(
projectId
);
testCaseNode
.
setCreateTime
(
System
.
currentTimeMillis
());
testCaseNode
.
setUpdateTime
(
System
.
currentTimeMillis
());
testCaseNode
.
setLevel
(
level
);
testCaseNodeMapper
.
insert
(
testCaseNode
);
return
testCaseNode
.
getId
();
}
}
backend/src/main/java/io/metersphere/service/TestCaseService.java
浏览文件 @
e9e27f42
package
io.metersphere.service
;
import
com.alibaba.excel.EasyExcel
;
import
com.alibaba.excel.EasyExcelFactory
;
import
com.alibaba.fastjson.JSON
;
import
com.github.pagehelper.PageHelper
;
import
io.metersphere.base.domain.*
;
import
io.metersphere.base.mapper.ProjectMapper
;
import
io.metersphere.base.mapper.TestCaseMapper
;
import
io.metersphere.base.mapper.TestPlanMapper
;
import
io.metersphere.base.mapper.TestPlanTestCaseMapper
;
import
io.metersphere.base.mapper.*
;
import
io.metersphere.base.mapper.ext.ExtTestCaseMapper
;
import
io.metersphere.commons.utils.LogUtil
;
import
io.metersphere.controller.request.testcase.QueryTestCaseRequest
;
import
io.metersphere.dto.TestPlanCaseDTO
;
import
io.metersphere.excel.domain.ExcelErrData
;
import
io.metersphere.excel.domain.ExcelResponse
;
import
io.metersphere.excel.domain.TestCaseExcelData
;
import
io.metersphere.excel.listener.EasyExcelListener
;
import
io.metersphere.excel.listener.TestCaseDataListener
;
import
io.metersphere.excel.utils.EasyExcelUtil
;
import
io.metersphere.user.SessionUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.ibatis.session.ExecutorType
;
import
org.apache.ibatis.session.SqlSession
;
import
org.apache.ibatis.session.SqlSessionFactory
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.web.multipart.MultipartFile
;
import
javax.annotation.Resource
;
import
java
.util.ArrayList
;
import
java.
util.List
;
import
java.
util.Map
;
import
java.util.
UUID
;
import
java
x.servlet.http.HttpServletResponse
;
import
java.
io.IOException
;
import
java.
net.URLEncoder
;
import
java.util.
*
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
@Service
@Transactional
(
rollbackFor
=
Exception
.
class
)
...
...
@@ -41,6 +51,15 @@ public class TestCaseService {
@Resource
ProjectMapper
projectMapper
;
@Resource
SqlSessionFactory
sqlSessionFactory
;
@Resource
TestCaseNodeService
testCaseNodeService
;
@Resource
UserMapper
userMapper
;
public
void
addTestCase
(
TestCaseWithBLOBs
testCase
)
{
testCase
.
setId
(
UUID
.
randomUUID
().
toString
());
testCase
.
setCreateTime
(
System
.
currentTimeMillis
());
...
...
@@ -144,4 +163,97 @@ public class TestCaseService {
}
return
projectMapper
.
selectByPrimaryKey
(
testCaseWithBLOBs
.
getProjectId
());
}
public
ExcelResponse
testCaseImport
(
MultipartFile
file
,
String
projectId
)
{
try
{
ExcelResponse
excelResponse
=
new
ExcelResponse
();
String
currentWorkspaceId
=
SessionUtils
.
getCurrentWorkspaceId
();
QueryTestCaseRequest
queryTestCaseRequest
=
new
QueryTestCaseRequest
();
queryTestCaseRequest
.
setProjectId
(
projectId
);
List
<
TestCase
>
testCases
=
extTestCaseMapper
.
getTestCaseNames
(
queryTestCaseRequest
);
Set
<
String
>
testCaseNames
=
testCases
.
stream
()
.
map
(
TestCase:
:
getName
)
.
collect
(
Collectors
.
toSet
());
UserExample
userExample
=
new
UserExample
();
userExample
.
createCriteria
().
andLastWorkspaceIdEqualTo
(
currentWorkspaceId
);
List
<
User
>
users
=
userMapper
.
selectByExample
(
userExample
);
Set
<
String
>
userNames
=
users
.
stream
().
map
(
User:
:
getName
).
collect
(
Collectors
.
toSet
());
EasyExcelListener
easyExcelListener
=
new
TestCaseDataListener
(
this
,
projectId
,
testCaseNames
,
userNames
,
TestCaseExcelData
.
class
);
EasyExcelFactory
.
read
(
file
.
getInputStream
(),
TestCaseExcelData
.
class
,
easyExcelListener
).
sheet
().
doRead
();
List
<
ExcelErrData
<
TestCaseExcelData
>>
errList
=
easyExcelListener
.
getErrList
();
//如果包含错误信息就导出错误信息
if
(!
errList
.
isEmpty
())
{
excelResponse
.
setSuccess
(
false
);
excelResponse
.
setErrList
(
errList
);
}
else
{
excelResponse
.
setSuccess
(
true
);
}
return
excelResponse
;
}
catch
(
IOException
e
)
{
LogUtil
.
error
(
e
.
getMessage
(),
e
);
e
.
printStackTrace
();
}
return
null
;
}
public
void
saveImportData
(
List
<
TestCaseWithBLOBs
>
testCases
,
String
projectId
)
{
Map
<
String
,
Integer
>
nodePathMap
=
testCaseNodeService
.
createNodeByTestCases
(
testCases
,
projectId
);
SqlSession
sqlSession
=
sqlSessionFactory
.
openSession
(
ExecutorType
.
BATCH
);
TestCaseMapper
mapper
=
sqlSession
.
getMapper
(
TestCaseMapper
.
class
);
if
(!
testCases
.
isEmpty
())
{
testCases
.
forEach
(
testcase
->
{
testcase
.
setNodeId
(
nodePathMap
.
get
(
testcase
.
getNodePath
()));
mapper
.
insert
(
testcase
);
});
}
sqlSession
.
flushStatements
();
}
public
void
testCaseTemplateExport
(
HttpServletResponse
response
)
{
EasyExcelUtil
.
export
(
response
,
TestCaseExcelData
.
class
,
generateExportTemplate
(),
"测试用例模版"
,
"模版"
);
}
private
List
<
TestCaseExcelData
>
generateExportTemplate
()
{
List
<
TestCaseExcelData
>
list
=
new
ArrayList
<
TestCaseExcelData
>();
StringBuilder
path
=
new
StringBuilder
(
""
);
List
<
String
>
types
=
Arrays
.
asList
(
"functional"
,
"performance"
,
"api"
);
List
<
String
>
methods
=
Arrays
.
asList
(
"manual"
,
"auto"
);
for
(
int
i
=
1
;
i
<=
5
;
i
++)
{
TestCaseExcelData
data
=
new
TestCaseExcelData
();
data
.
setName
(
"测试用例"
+
i
);
path
.
append
(
"/"
+
"模块"
+
i
);
data
.
setNodePath
(
path
.
toString
());
data
.
setPriority
(
"P"
+
i
%
4
);
data
.
setType
(
types
.
get
(
i
%
3
));
data
.
setMethod
(
methods
.
get
(
i
%
2
));
data
.
setPrerequisite
(
"前置条件选填"
);
data
.
setStepDesc
(
"1. 每个步骤以换行分隔\n2. 步骤前可标序号\n3. 测试步骤和结果选填"
);
data
.
setStepResult
(
"1. 每条结果以换行分隔\n2. 结果前可标序号\n3. 测试步骤和结果选填"
);
data
.
setMaintainer
(
"admin"
);
data
.
setRemark
(
"备注选填"
);
list
.
add
(
data
);
}
list
.
add
(
new
TestCaseExcelData
());
TestCaseExcelData
explain
=
new
TestCaseExcelData
();
explain
.
setName
(
"同一项目下测试用例名称不能重复!"
);
explain
.
setNodePath
(
"模块名称请按照'/模块1/模块2'的格式书写; 错误格式示例:('/', '/tes//test'); 若无该模块,则自动创建模块"
);
explain
.
setType
(
"用例类型必须为:functional、performance、api"
);
explain
.
setMethod
(
"测试方式必须为:manual、auto"
);
explain
.
setPriority
(
"优先级必须为:P0、P1、P2、P3"
);
explain
.
setMaintainer
(
"维护人必须为该工作空间相关人员"
);
list
.
add
(
explain
);
return
list
;
}
}
frontend/src/business/components/api/test/ApiScenarioConfig.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div
class=
"container"
>
<div
class=
"main-content"
>
<el-card>
<el-container
class=
"scenario-container"
>
<el-header>
<span
class=
"scenario-title"
>
场景配置
</span>
</el-header>
<el-container>
<el-aside
class=
"scenario-aside"
>
<div
class=
"scenario-list"
>
<ms-api-collapse
v-model=
"activeName"
@
change=
"handleChange"
accordion
>
<ms-api-collapse-item
v-for=
"(scenario, index) in scenarios"
:key=
"index"
:title=
"scenario.name"
:name=
"index"
>
<template
slot=
"title"
>
<div
class=
"scenario-name"
>
{{
scenario
.
name
}}
</div>
<el-dropdown
trigger=
"click"
@
command=
"handleCommand"
>
<span
class=
"el-dropdown-link el-icon-more scenario-btn"
/>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item
:command=
"
{type:'delete', index:index}">删除场景
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</
template
>
<ms-api-request
:requests=
"scenario.requests"
:open=
"select"
/>
</ms-api-collapse-item>
</ms-api-collapse>
</div>
<el-button
class=
"scenario-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"create"
/>
</el-aside>
<el-main
class=
"scenario-main"
>
<div
class=
"scenario-form"
>
<ms-api-scenario-form
:scenario=
"selected"
></ms-api-scenario-form>
<ms-api-request-form
:request=
"selected"
></ms-api-request-form>
</div>
</el-main>
</el-container>
</el-container>
</el-card>
</div>
</div>
</template>
<
script
>
import
MsApiCollapseItem
from
"
./components/ApiCollapseItem
"
;
import
MsApiCollapse
from
"
./components/ApiCollapse
"
;
import
MsApiRequest
from
"
./components/ApiRequest
"
;
import
MsApiRequestForm
from
"
./components/ApiRequestForm
"
;
import
MsApiScenarioForm
from
"
./components/ApiScenarioForm
"
;
export
default
{
name
:
"
MsApiScenarioConfig
"
,
components
:
{
MsApiScenarioForm
,
MsApiRequestForm
,
MsApiRequest
,
MsApiCollapse
,
MsApiCollapseItem
},
data
()
{
return
{
activeName
:
0
,
scenarios
:
[],
selected
:
Object
}
},
methods
:
{
handleChange
:
function
(
index
)
{
this
.
select
(
this
.
scenarios
[
index
]);
},
handleCommand
:
function
(
command
)
{
switch
(
command
.
type
)
{
case
"
delete
"
:
this
.
deleteScenario
(
command
.
index
);
break
;
}
},
createScenario
:
function
()
{
return
{
type
:
"
Scenario
"
,
name
:
"
Scenario
"
,
address
:
""
,
file
:
""
,
variables
:
[],
headers
:
[],
requests
:
[]
}
},
deleteScenario
:
function
(
index
)
{
this
.
scenarios
.
splice
(
index
,
1
);
if
(
this
.
scenarios
.
length
===
0
)
{
this
.
create
();
}
},
create
:
function
()
{
let
scenario
=
this
.
createScenario
();
this
.
scenarios
.
push
(
scenario
);
},
select
:
function
(
obj
)
{
this
.
selected
=
obj
;
}
},
created
()
{
if
(
this
.
scenarios
.
length
===
0
)
{
this
.
create
();
this
.
select
(
this
.
scenarios
[
0
]);
}
}
}
</
script
>
<
style
scoped
>
.scenario-container
{
height
:
calc
(
100vh
-
150px
);
min-height
:
600px
;
}
.scenario-title
{
font-size
:
16px
;
margin-left
:
-20px
;
}
.scenario-aside
{
position
:
relative
;
border-radius
:
4px
;
border
:
1px
solid
#EBEEF5
;
box-sizing
:
border-box
;
}
.scenario-list
{
overflow-y
:
auto
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
28px
;
}
.scenario-name
{
font-size
:
14px
;
width
:
100%
;
}
.scenario-btn
{
text-align
:
center
;
padding
:
13px
;
}
.scenario-create
{
position
:
absolute
;
bottom
:
0
;
width
:
100%
;
}
.scenario-main
{
position
:
relative
;
margin-left
:
20px
;
border
:
1px
solid
#EBEEF5
;
}
.scenario-form
{
padding
:
20px
;
overflow-y
:
auto
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
}
</
style
>
frontend/src/business/components/api/test/EditApiTest.vue
已删除
100644 → 0
浏览文件 @
b7ae9c5e
<
template
>
<div
class=
"edit-testplan-container"
>
<div
class=
"main-content"
>
<el-card>
<el-row>
<el-col
:span=
"10"
>
<el-input
:placeholder=
"$t('load_test.input_name')"
v-model=
"testPlan.name"
class=
"input-with-select"
>
<template
v-slot:prepend
>
<el-select
v-model=
"testPlan.projectId"
:placeholder=
"$t('load_test.select_project')"
>
<el-option
v-for=
"item in projects"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
</el-option>
</el-select>
</
template
>
</el-input>
</el-col>
<el-button
type=
"primary"
plain
@
click=
"save"
>
{{$t('commons.save')}}
</el-button>
<el-button
type=
"primary"
plain
@
click=
"saveAndRun"
>
{{$t('load_test.save_and_run')}}
</el-button>
<el-button
type=
"warning"
plain
@
click=
"cancel"
>
{{$t('commons.cancel')}}
</el-button>
</el-row>
<el-tabs
class=
"testplan-config"
v-model=
"active"
type=
"border-card"
:stretch=
"true"
>
<el-tab-pane
:label=
"$t('load_test.basic_config')"
>
<api-test-scene-config
:test-plan=
"testPlan"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('load_test.runtime_config')"
>
<api-test-runtime-config
:test-plan=
"testPlan"
ref=
"runtimeConfig"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<
script
>
import
ApiTestSceneConfig
from
'
./components/ApiTestSceneConfig
'
;
import
ApiTestRuntimeConfig
from
'
./components/ApiTestRuntimeConfig
'
;
export
default
{
name
:
"
EditApiTest
"
,
data
()
{
return
{
result
:
{},
testPlan
:
{},
listProjectPath
:
"
/project/listAll
"
,
savePath
:
"
/api/save
"
,
editPath
:
"
/api/edit
"
,
runPath
:
"
/api/run
"
,
projects
:
[],
active
:
'
0
'
,
tabs
:
[{
title
:
this
.
$t
(
'
load_test.basic_config
'
),
id
:
'
0
'
,
component
:
'
ApiTestSceneConfig
'
},
{
title
:
this
.
$t
(
'
load_test.runtime_config
'
),
id
:
'
1
'
,
component
:
'
ApiTestRuntimeConfig
'
}]
}
},
components
:
{
ApiTestSceneConfig
,
ApiTestRuntimeConfig
,
},
watch
:
{
'
$route
'
(
to
)
{
// 如果是创建测试
if
(
to
.
name
===
'
createFucTest
'
)
{
window
.
location
.
reload
();
return
;
}
let
testId
=
to
.
path
.
split
(
'
/
'
)[
4
];
// find testId
if
(
testId
)
{
this
.
$get
(
'
/api/get/
'
+
testId
,
response
=>
{
this
.
testPlan
=
response
.
data
;
});
}
}
},
created
()
{
let
testId
=
this
.
$route
.
path
.
split
(
'
/
'
)[
4
];
if
(
testId
)
{
this
.
$get
(
'
/api/get/
'
+
testId
,
response
=>
{
this
.
testPlan
=
response
.
data
;
});
}
this
.
listProjects
();
},
methods
:
{
listProjects
()
{
this
.
result
=
this
.
$get
(
this
.
listProjectPath
,
response
=>
{
this
.
projects
=
response
.
data
;
})
},
save
()
{
if
(
!
this
.
validTestPlan
())
{
return
;
}
let
options
=
this
.
getSaveOption
();
this
.
result
=
this
.
$request
(
options
,
()
=>
{
this
.
$message
({
message
:
this
.
$t
(
'
commons.save_success
'
),
type
:
'
success
'
});
this
.
$refs
.
runtimeConfig
.
cancelAllEdit
();
this
.
$router
.
push
({
path
:
'
/api/test/all
'
})
});
},
saveAndRun
()
{
if
(
!
this
.
validTestPlan
())
{
return
;
}
let
options
=
this
.
getSaveOption
();
this
.
result
=
this
.
$request
(
options
,
(
response
)
=>
{
this
.
testPlan
.
id
=
response
.
data
;
this
.
$message
({
message
:
this
.
$t
(
'
commons.save_success
'
),
type
:
'
success
'
});
this
.
result
=
this
.
$post
(
this
.
runPath
,
{
id
:
this
.
testPlan
.
id
},
()
=>
{
this
.
$message
({
message
:
this
.
$t
(
'
load_test.is_running
'
),
type
:
'
success
'
});
})
});
},
getSaveOption
()
{
let
formData
=
new
FormData
();
let
url
=
this
.
testPlan
.
id
?
this
.
editPath
:
this
.
savePath
;
if
(
!
this
.
testPlan
.
file
.
id
)
{
formData
.
append
(
"
file
"
,
this
.
testPlan
.
file
);
}
this
.
testPlan
.
runtimeConfiguration
=
JSON
.
stringify
(
this
.
$refs
.
runtimeConfig
.
configurations
());
// file属性不需要json化
let
requestJson
=
JSON
.
stringify
(
this
.
testPlan
,
function
(
key
,
value
)
{
return
key
===
"
file
"
?
undefined
:
value
});
formData
.
append
(
'
request
'
,
new
Blob
([
requestJson
],
{
type
:
"
application/json
"
}));
return
{
method
:
'
POST
'
,
url
:
url
,
data
:
formData
,
headers
:
{
'
Content-Type
'
:
undefined
}
};
},
cancel
()
{
this
.
$router
.
push
({
path
:
'
/api/test/all
'
})
},
validTestPlan
()
{
if
(
!
this
.
testPlan
.
name
)
{
this
.
$message
({
message
:
this
.
$t
(
'
load_test.test_name_is_null
'
),
type
:
'
error
'
});
return
false
;
}
if
(
!
this
.
testPlan
.
projectId
)
{
this
.
$message
({
message
:
this
.
$t
(
'
load_test.project_is_null
'
),
type
:
'
error
'
});
return
false
;
}
if
(
!
this
.
testPlan
.
file
)
{
this
.
$message
({
message
:
this
.
$t
(
'
load_test.jmx_is_null
'
),
type
:
'
error
'
});
return
false
;
}
if
(
!
this
.
$refs
.
runtimeConfig
.
validConfig
())
{
return
false
;
}
/// todo: 其他校验
return
true
;
}
}
}
</
script
>
<
style
scoped
>
.edit-testplan-container
{
float
:
none
;
text-align
:
center
;
padding
:
15px
;
width
:
100%
;
height
:
100%
;
box-sizing
:
border-box
;
}
.edit-testplan-container
.main-content
{
margin
:
0
auto
;
width
:
100%
;
max-width
:
1200px
;
}
.edit-testplan-container
.testplan-config
{
margin-top
:
15px
;
}
.el-select
{
min-width
:
130px
;
}
.edit-testplan-container
.input-with-select
.el-input-group__prepend
{
background-color
:
#fff
;
}
.advanced-config
{
height
:
calc
(
100vh
-
280px
);
overflow
:
auto
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiBody.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div>
<el-radio-group
v-model=
"body.type"
size=
"mini"
>
<el-radio-button
label=
"kv"
>
{{
$t
(
'
api_test.request.body_kv
'
)
}}
</el-radio-button>
<el-radio-button
label=
"text"
>
{{
$t
(
'
api_test.request.body_text
'
)
}}
</el-radio-button>
</el-radio-group>
<ms-api-key-value
:items=
"body.kvs"
v-if=
"isKV"
/>
<el-input
class=
"textarea"
type=
"textarea"
v-model=
"body.text"
:autosize=
"
{ minRows: 10, maxRows: 25}" resize="none"
v-else/>
</div>
</
template
>
<
script
>
import
MsApiKeyValue
from
"
./ApiKeyValue
"
;
export
default
{
name
:
"
MsApiBody
"
,
components
:
{
MsApiKeyValue
},
props
:
{
body
:
Object
},
data
()
{
return
{};
},
methods
:
{},
computed
:
{
isKV
()
{
return
this
.
body
.
type
===
"
kv
"
;
}
}
}
</
script
>
<
style
scoped
>
.textarea
{
margin-top
:
10px
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiCollapse.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div
role=
"tablist"
aria-multiselectable=
"true"
>
<slot></slot>
</div>
</
template
>
<
script
>
export
default
{
name
:
'
MsApiCollapse
'
,
componentName
:
'
MsApiCollapse
'
,
props
:
{
accordion
:
Boolean
,
value
:
{
type
:
[
Array
,
String
,
Number
],
default
()
{
return
[];
}
}
},
data
()
{
return
{
activeNames
:
[].
concat
(
this
.
value
)
};
},
provide
()
{
return
{
collapse
:
this
};
},
watch
:
{
value
(
value
)
{
this
.
activeNames
=
[].
concat
(
value
);
}
},
methods
:
{
setActiveNames
(
activeNames
)
{
activeNames
=
[].
concat
(
activeNames
);
let
value
=
this
.
accordion
?
activeNames
[
0
]
:
activeNames
;
this
.
activeNames
=
activeNames
;
this
.
$emit
(
'
input
'
,
value
);
this
.
$emit
(
'
change
'
,
value
);
},
handleItemClick
(
item
)
{
if
(
this
.
accordion
)
{
this
.
setActiveNames
(
(
this
.
activeNames
[
0
]
||
this
.
activeNames
[
0
]
===
0
)
&&
item
.
name
);
}
else
{
let
activeNames
=
this
.
activeNames
.
slice
(
0
);
let
index
=
activeNames
.
indexOf
(
item
.
name
);
if
(
index
>
-
1
)
{
activeNames
.
splice
(
index
,
1
);
}
else
{
activeNames
.
push
(
item
.
name
);
}
this
.
setActiveNames
(
activeNames
);
}
}
},
created
()
{
this
.
$on
(
'
item-click
'
,
this
.
handleItemClick
);
}
};
</
script
>
frontend/src/business/components/api/test/components/ApiCollapseItem.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div
class=
"el-collapse-item"
:class=
"
{'is-active': isActive, 'is-disabled': disabled }">
<div
role=
"tab"
:aria-expanded=
"isActive"
:aria-controls=
"`el-collapse-content-$
{id}`"
:aria-describedby="`el-collapse-content-${id}`"
>
<div
class=
"el-collapse-item__header"
@
click=
"handleHeaderClick"
role=
"button"
:id=
"`el-collapse-head-$
{id}`"
:tabindex="disabled ? undefined : 0"
@keyup.space.enter.stop="handleEnterClick"
:class="{
'focusing': focusing,
'is-active': isActive
}"
@focus="handleFocus"
@blur="focusing = false"
>
<i
class=
"el-collapse-item__arrow el-icon-arrow-right"
:class=
"
{'is-active': isActive}">
</i>
<slot
name=
"title"
>
{{
title
}}
</slot>
</div>
</div>
<el-collapse-transition>
<div
class=
"el-collapse-item__wrap"
v-show=
"isActive"
role=
"tabpanel"
:aria-hidden=
"!isActive"
:aria-labelledby=
"`el-collapse-head-$
{id}`"
:id="`el-collapse-content-${id}`"
>
<div
class=
"el-collapse-item__content"
>
<slot></slot>
</div>
</div>
</el-collapse-transition>
</div>
</
template
>
<
script
>
import
Emitter
from
'
element-ui/src/mixins/emitter
'
;
import
{
generateId
}
from
'
element-ui/src/utils/util
'
;
export
default
{
name
:
'
MsApiCollapseItem
'
,
componentName
:
'
MsApiCollapseItem
'
,
mixins
:
[
Emitter
],
data
()
{
return
{
contentWrapStyle
:
{
height
:
'
auto
'
,
display
:
'
block
'
},
contentHeight
:
0
,
focusing
:
false
,
isClick
:
false
,
id
:
generateId
()
};
},
inject
:
[
'
collapse
'
],
props
:
{
title
:
String
,
name
:
{
type
:
[
String
,
Number
],
default
()
{
return
this
.
_uid
;
}
},
disabled
:
Boolean
},
computed
:
{
isActive
()
{
return
this
.
collapse
.
activeNames
.
indexOf
(
this
.
name
)
>
-
1
;
}
},
methods
:
{
handleFocus
()
{
setTimeout
(()
=>
{
if
(
!
this
.
isClick
)
{
this
.
focusing
=
true
;
}
else
{
this
.
isClick
=
false
;
}
},
50
);
},
handleHeaderClick
()
{
if
(
this
.
disabled
)
return
;
this
.
dispatch
(
'
MsApiCollapse
'
,
'
item-click
'
,
this
);
this
.
focusing
=
false
;
this
.
isClick
=
true
;
},
handleEnterClick
()
{
this
.
dispatch
(
'
MsApiCollapse
'
,
'
item-click
'
,
this
);
}
}
};
</
script
>
<
style
scoped
>
.el-collapse-item__header
{
padding-left
:
7px
;
}
.el-collapse-item__header.is-active
{
background-color
:
#E9E9E9
;
}
.el-collapse-item__content
{
padding-bottom
:
0
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiKeyValue.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div>
<span
class=
"kv-description"
v-if=
"description"
>
{{
description
}}
</span>
<div
class=
"kv-row"
v-for=
"(item, index) in items"
:key=
"index"
>
<el-row
type=
"flex"
:gutter=
"20"
justify=
"space-between"
align=
"middle"
>
<el-col
:span=
"11"
>
<el-input
v-model=
"item.key"
placeholder=
"Key"
size=
"small"
maxlength=
"100"
@
change=
"check"
/>
</el-col>
<el-col
:span=
"11"
>
<el-input
v-model=
"item.value"
placeholder=
"Value"
size=
"small"
maxlength=
"100"
@
change=
"check"
/>
</el-col>
<el-col
:span=
"1"
>
<el-button
size=
"mini"
class=
"el-icon-delete-solid"
circle
@
click=
"remove(index)"
/>
</el-col>
</el-row>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
MsApiKeyValue
"
,
props
:
{
description
:
String
,
items
:
Array
},
methods
:
{
create
:
function
()
{
return
{
key
:
""
,
value
:
""
}
},
remove
:
function
(
index
)
{
this
.
items
.
splice
(
index
,
1
);
if
(
this
.
items
.
length
===
0
)
{
this
.
items
.
push
(
this
.
create
());
}
},
check
:
function
()
{
let
isNeedCreate
=
true
;
let
removeIndex
=
-
1
;
this
.
items
.
forEach
((
item
,
index
)
=>
{
if
(
item
.
key
===
""
&&
item
.
value
===
""
)
{
// 多余的空行
if
(
index
!==
this
.
items
.
length
-
1
)
{
removeIndex
=
index
;
}
// 没有空行,需要创建空行
isNeedCreate
=
false
;
}
});
if
(
isNeedCreate
)
{
this
.
items
.
push
(
this
.
create
());
}
if
(
removeIndex
!==
-
1
)
{
this
.
remove
(
removeIndex
);
}
// TODO 检查key重复
}
},
created
()
{
if
(
this
.
items
.
length
===
0
)
{
this
.
items
.
push
(
this
.
create
());
}
}
}
</
script
>
<
style
scoped
>
.kv-description
{
font-size
:
14px
;
}
.kv-row
{
margin-top
:
10px
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiRequest.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div
class=
"request-container"
>
<div
class=
"request-item"
v-for=
"(request, index) in requests"
:key=
"index"
@
click=
"select(request)"
:class=
"
{'selected': isSelected(request)}">
<span
class=
"request-method"
>
{{
request
.
method
}}
</span>
<span
class=
"request-name"
>
{{
request
.
name
}}
</span>
<span
class=
"request-btn"
>
<el-dropdown
trigger=
"click"
@
command=
"handleCommand"
>
<span
class=
"el-dropdown-link el-icon-more"
></span>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item
:command=
"
{type: 'copy', index: index}">复制请求
</el-dropdown-item>
<el-dropdown-item
:command=
"
{type: 'delete', index: index}">删除请求
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</span>
</div>
<el-button
class=
"request-create"
type=
"primary"
size=
"mini"
icon=
"el-icon-plus"
plain
@
click=
"create"
/>
</div>
</
template
>
<
script
>
import
{
generateId
}
from
'
element-ui/src/utils/util
'
;
export
default
{
name
:
"
MsApiRequest
"
,
props
:
{
requests
:
Array
,
open
:
Function
},
data
()
{
return
{
selected
:
0
}
},
computed
:
{
isSelected
()
{
return
function
(
request
)
{
return
this
.
selected
.
randomId
===
request
.
randomId
;
}
}
},
methods
:
{
create
:
function
()
{
let
request
=
this
.
createRequest
();
this
.
requests
.
push
(
request
);
},
handleCommand
:
function
(
command
)
{
switch
(
command
.
type
)
{
case
"
copy
"
:
this
.
copyRequest
(
command
.
index
);
break
;
case
"
delete
"
:
this
.
deleteRequest
(
command
.
index
);
break
;
}
},
copyRequest
:
function
(
index
)
{
let
request
=
this
.
requests
[
index
];
this
.
requests
.
push
(
JSON
.
parse
(
JSON
.
stringify
(
request
)));
},
deleteRequest
:
function
(
index
)
{
this
.
requests
.
splice
(
index
,
1
);
if
(
this
.
requests
.
length
===
0
)
{
this
.
create
();
}
},
createRequest
:
function
()
{
return
{
randomId
:
generateId
(),
type
:
"
Request
"
,
method
:
"
GET
"
,
name
:
""
,
parameters
:
[],
headers
:
[],
body
:
{
type
:
"
kv
"
,
kvs
:
[],
text
:
""
},
assertions
:
[],
extract
:
[]
}
},
select
:
function
(
request
)
{
this
.
selected
=
request
;
this
.
open
(
request
);
}
},
created
()
{
if
(
this
.
requests
.
length
===
0
)
{
this
.
create
();
this
.
select
(
this
.
requests
[
0
]);
}
}
}
</
script
>
<
style
scoped
>
.request-item
{
border-left
:
5px
solid
#1E90FF
;
line-height
:
40px
;
max-height
:
40px
;
border-top
:
1px
solid
#EBEEF5
;
cursor
:
pointer
;
}
.request-item
:first-child
{
border-top
:
0
;
}
.request-item
:hover
,
.request-item.selected
:hover
{
background-color
:
#ECF5FF
;
}
.request-item.selected
{
background-color
:
#F5F5F5
;
}
.request-method
{
padding
:
0
5px
;
width
:
60px
;
color
:
#1E90FF
;
}
.request-name
{
font-size
:
14px
;
width
:
100%
;
}
.request-btn
{
float
:
right
;
text-align
:
center
;
height
:
40px
;
}
.request-btn
.el-icon-more
{
padding
:
13px
;
}
.request-create
{
width
:
100%
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiRequestForm.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<el-form
:model=
"request"
:rules=
"rules"
ref=
"request"
label-width=
"100px"
label-position=
"left"
v-if=
"isRequest"
>
<el-form-item
:label=
"$t('api_test.request.name')"
prop=
"name"
>
<el-input
v-model=
"request.name"
></el-input>
</el-form-item>
<el-form-item
:label=
"$t('api_test.request.url')"
prop=
"url"
>
<el-input
v-model=
"request.url"
:placeholder=
"$t('api_test.request.url_describe')"
>
<el-select
v-model=
"request.method"
slot=
"prepend"
class=
"request-method-select"
>
<el-option
label=
"GET"
value=
"GET"
></el-option>
<el-option
label=
"POST"
value=
"POST"
></el-option>
<el-option
label=
"PUT"
value=
"PUT"
></el-option>
<el-option
label=
"PATCH"
value=
"PATCH"
></el-option>
<el-option
label=
"DELETE"
value=
"DELETE"
></el-option>
<el-option
label=
"OPTIONS"
value=
"OPTIONS"
></el-option>
<el-option
label=
"HEAD"
value=
"HEAD"
></el-option>
<el-option
label=
"CONNECT"
value=
"CONNECT"
></el-option>
</el-select>
</el-input>
</el-form-item>
<el-tabs
v-model=
"activeName"
>
<el-tab-pane
:label=
"$t('api_test.request.parameters')"
name=
"parameters"
>
<ms-api-key-value
:items=
"request.parameters"
:description=
"$t('api_test.request.parameters_desc')"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.headers')"
name=
"headers"
>
<ms-api-key-value
:items=
"request.headers"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.body')"
name=
"body"
v-if=
"isNotGet"
>
<ms-api-body
:body=
"request.body"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.assertions')"
name=
"assertions"
v-if=
"false"
>
TODO
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.request.extract')"
name=
"extract"
v-if=
"false"
>
TODO
</el-tab-pane>
</el-tabs>
</el-form>
</
template
>
<
script
>
import
MsApiKeyValue
from
"
./ApiKeyValue
"
;
import
MsApiBody
from
"
./ApiBody
"
;
export
default
{
name
:
"
MsApiRequestForm
"
,
components
:
{
MsApiBody
,
MsApiKeyValue
},
props
:
{
request
:
Object
},
data
()
{
return
{
activeName
:
"
parameters
"
,
rules
:
{}
}
},
computed
:
{
isRequest
()
{
return
this
.
request
.
type
===
"
Request
"
;
},
isNotGet
()
{
return
this
.
request
.
method
!==
"
GET
"
;
}
}
}
</
script
>
<
style
scoped
>
.request-method-select
{
width
:
110px
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiScenarioForm.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<el-form
:model=
"scenario"
:rules=
"rules"
ref=
"scenario"
label-width=
"100px"
label-position=
"left"
v-if=
"isScenario"
>
<el-form-item
:label=
"$t('api_test.scenario.name')"
prop=
"name"
>
<el-input
v-model=
"scenario.name"
></el-input>
</el-form-item>
<el-form-item
:label=
"$t('api_test.scenario.base_url')"
prop=
"url"
>
<el-input
:placeholder=
"$t('api_test.scenario.base_url_describe')"
v-model=
"scenario.url"
></el-input>
</el-form-item>
<el-tabs
v-model=
"activeName"
>
<el-tab-pane
:label=
"$t('api_test.scenario.variables')"
name=
"variables"
>
<ms-api-key-value
:items=
"scenario.variables"
/>
</el-tab-pane>
<el-tab-pane
:label=
"$t('api_test.scenario.headers')"
name=
"headers"
>
<ms-api-key-value
:items=
"scenario.headers"
/>
</el-tab-pane>
</el-tabs>
</el-form>
</
template
>
<
script
>
import
MsApiKeyValue
from
"
./ApiKeyValue
"
;
export
default
{
name
:
"
MsApiScenarioForm
"
,
components
:
{
MsApiKeyValue
},
props
:
{
scenario
:
Object
},
data
()
{
return
{
activeName
:
"
variables
"
,
rules
:
{}
}
},
computed
:
{
isScenario
()
{
return
this
.
scenario
.
type
===
"
Scenario
"
;
}
},
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/api/test/components/ApiTestRuntimeConfig.vue
已删除
100644 → 0
浏览文件 @
b7ae9c5e
<
template
>
<div>
<el-row>
<el-col
:span=
"5"
:offset=
"6"
>
<span>
浏览器
</span>
</el-col>
</el-row>
<el-row
>
<el-col
:span=
"20"
:offset=
"2"
>
<el-radio-group
v-model=
"browser.value"
class=
"browser-radio"
>
<el-radio
v-for=
"item in browser.options"
:key=
"item.label"
:label=
"item.label"
>
<img
:src=
"item.url"
/>
</el-radio>
</el-radio-group>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"5"
:offset=
"6"
>
<span>
资源池
</span>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"18"
:offset=
"1"
>
<el-select
v-model=
"resourcePool.value"
filterable
placeholder=
"请选择"
>
<el-option
v-for=
"item in resourcePool.options"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
>
</el-option>
</el-select>
</el-col>
</el-row>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
ApiTestRuntimeConfig
"
,
data
()
{
return
{
resourcePool
:
{
options
:
[
{
value
:
'
选项1
'
,
label
:
'
资源池1
'
},
{
value
:
'
选项2
'
,
label
:
'
资源池3
'
},
{
value
:
'
选项3
'
,
label
:
'
资源池3
'
}],
value
:
''
},
browser
:
{
options
:
[{
url
:
require
(
'
@/assets/browser/firefox.svg
'
),
label
:
'
firefox
'
,
},
{
url
:
require
(
'
@/assets/browser/chrome.svg
'
),
label
:
'
chrome
'
,
},
{
url
:
require
(
'
@/assets/browser/ie.svg
'
),
label
:
'
ie
'
,
},
{
url
:
require
(
'
@/assets/browser/opera.svg
'
),
label
:
'
opera
'
,
}
],
value
:
'
firefox
'
}
}
},
methods
:
{
validConfig
()
{
if
(
this
.
resourcePool
.
value
==
''
)
{
this
.
$message
.
error
(
this
.
$t
(
'
api_test.select_resource_pool
'
));
return
false
;
}
return
true
;
},
configurations
()
{
return
{
resourcePool
:
this
.
resourcePool
,
browser
:
this
.
browser
}
},
cancelAllEdit
()
{
this
.
browser
.
value
=
'
firefox
'
;
this
.
resourcePool
.
value
=
''
;
}
}
}
</
script
>
<
style
scoped
>
.el-row
{
margin-top
:
30px
;
margin-bottom
:
30px
;
}
span
{
font-size
:
20px
;
font-weight
:
bold
;
color
:
dimgray
;
}
</
style
>
frontend/src/business/components/api/test/components/ApiTestSceneConfig.vue
已删除
100644 → 0
浏览文件 @
b7ae9c5e
<
template
>
<div
v-loading=
"result.loading"
>
<el-upload
accept=
".jmx"
drag
action=
""
:limit=
"1"
:show-file-list=
"false"
:before-upload=
"beforeUpload"
:http-request=
"handleUpload"
:on-exceed=
"handleExceed"
:file-list=
"fileList"
>
<i
class=
"el-icon-upload"
/>
<div
class=
"el-upload__text"
v-html=
"$t('load_test.upload_tips')"
></div>
<template
v-slot:tip
>
<div
class=
"el-upload__tip"
>
{{
$t
(
'
load_test.upload_type
'
)
}}
</div>
</
template
>
</el-upload>
<el-table
class=
"basic-config"
:data=
"tableData"
>
<el-table-column
prop=
"name"
:label=
"$t('load_test.file_name')"
>
</el-table-column>
<el-table-column
prop=
"size"
:label=
"$t('load_test.file_size')"
>
</el-table-column>
<el-table-column
prop=
"type"
:label=
"$t('load_test.file_type')"
>
</el-table-column>
<el-table-column
:label=
"$t('load_test.last_modify_time')"
>
<
template
v-slot:default=
"scope"
>
<i
class=
"el-icon-time"
/>
<span
class=
"last-modified"
>
{{
scope
.
row
.
lastModified
|
timestampFormatDate
}}
</span>
</
template
>
</el-table-column>
<el-table-column
prop=
"status"
:label=
"$t('load_test.file_status')"
>
</el-table-column>
<el-table-column
:label=
"$t('commons.operating')"
>
<
template
v-slot:default=
"scope"
>
<el-button
@
click=
"handleDownload(scope.row)"
:disabled=
"!scope.row.id"
type=
"primary"
icon=
"el-icon-download"
size=
"mini"
circle
/>
<el-button
@
click=
"handleDelete(scope.row, scope.$index)"
type=
"danger"
icon=
"el-icon-delete"
size=
"mini"
circle
/>
</
template
>
</el-table-column>
</el-table>
</div>
</template>
<
script
>
import
{
Message
}
from
"
element-ui
"
;
export
default
{
name
:
"
ApiTestSceneConfig
"
,
props
:
[
"
testPlan
"
],
data
()
{
return
{
result
:
{},
getFileMetadataPath
:
"
/api/file/metadata
"
,
jmxDownloadPath
:
'
/api/file/download
'
,
jmxDeletePath
:
'
/api/file/delete
'
,
fileList
:
[],
tableData
:
[],
};
},
created
()
{
if
(
this
.
testPlan
.
id
)
{
this
.
getFileMetadata
(
this
.
testPlan
)
}
},
watch
:
{
testPlan
()
{
if
(
this
.
testPlan
.
id
)
{
this
.
getFileMetadata
(
this
.
testPlan
)
}
}
},
methods
:
{
getFileMetadata
(
testPlan
)
{
this
.
fileList
=
[];
// 一个测试只有一个文件
this
.
tableData
=
[];
// 一个测试只有一个文件
this
.
result
=
this
.
$get
(
this
.
getFileMetadataPath
+
"
/
"
+
testPlan
.
id
,
response
=>
{
let
file
=
response
.
data
;
if
(
!
file
)
{
Message
.
error
({
message
:
this
.
$t
(
'
load_test.related_file_not_found
'
),
showClose
:
true
});
return
;
}
this
.
testPlan
.
file
=
file
;
this
.
fileList
.
push
({
id
:
file
.
id
,
name
:
file
.
name
});
this
.
tableData
.
push
({
id
:
file
.
id
,
name
:
file
.
name
,
size
:
file
.
size
+
'
Byte
'
,
/// todo: 按照大小显示Byte、KB、MB等
type
:
'
JMX
'
,
lastModified
:
file
.
updateTime
,
status
:
'
todo
'
,
});
})
},
beforeUpload
(
file
)
{
if
(
!
this
.
fileValidator
(
file
))
{
/// todo: 显示错误信息
return
false
;
}
this
.
tableData
.
push
({
name
:
file
.
name
,
size
:
file
.
size
+
'
Byte
'
,
/// todo: 按照大小显示Byte、KB、MB等
type
:
'
JMX
'
,
lastModified
:
file
.
lastModified
,
status
:
'
todo
'
,
});
return
true
;
},
handleUpload
(
uploadResources
)
{
this
.
testPlan
.
file
=
uploadResources
.
file
;
},
handleDownload
(
file
)
{
let
data
=
{
name
:
file
.
name
,
id
:
file
.
id
,
};
let
config
=
{
url
:
this
.
jmxDownloadPath
,
method
:
'
post
'
,
data
:
data
,
responseType
:
'
blob
'
};
this
.
result
=
this
.
$request
(
config
).
then
(
response
=>
{
const
content
=
response
.
data
;
const
blob
=
new
Blob
([
content
]);
if
(
"
download
"
in
document
.
createElement
(
"
a
"
))
{
// 非IE下载
// chrome/firefox
let
aTag
=
document
.
createElement
(
'
a
'
);
aTag
.
download
=
file
.
name
;
aTag
.
href
=
URL
.
createObjectURL
(
blob
);
aTag
.
click
();
URL
.
revokeObjectURL
(
aTag
.
href
)
}
else
{
// IE10+下载
navigator
.
msSaveBlob
(
blob
,
this
.
filename
)
}
}).
catch
(
e
=>
{
Message
.
error
({
message
:
e
.
message
,
showClose
:
true
});
});
},
handleDelete
(
file
,
index
)
{
this
.
$alert
(
this
.
$t
(
'
commons.delete_file_confirm
'
)
+
file
.
name
+
"
?
"
,
''
,
{
confirmButtonText
:
this
.
$t
(
'
commons.confirm
'
),
callback
:
(
action
)
=>
{
if
(
action
===
'
confirm
'
)
{
this
.
_handleDelete
(
file
,
index
);
}
}
});
},
_handleDelete
(
file
,
index
)
{
this
.
fileList
.
splice
(
index
,
1
);
this
.
tableData
.
splice
(
index
,
1
);
this
.
testPlan
.
file
=
null
;
},
handleExceed
()
{
this
.
$message
.
error
(
this
.
$t
(
'
load_test.delete_file
'
));
},
fileValidator
(
file
)
{
/// todo: 是否需要对文件内容和大小做限制
return
file
.
size
>
0
;
},
},
}
</
script
>
<
style
scoped
>
.basic-config
{
width
:
100%
}
.last-modified
{
margin-left
:
5px
;
}
</
style
>
frontend/src/business/components/common/router/router.js
浏览文件 @
e9e27f42
...
...
@@ -18,7 +18,7 @@ import PerformanceTestReport from "../../performance/report/PerformanceTestRepor
import
ApiTestReport
from
"
../../api/report/ApiTestReport
"
;
import
ApiTest
from
"
../../api/ApiTest
"
;
import
PerformanceTest
from
"
../../performance/PerformanceTest
"
;
import
EditApiTest
from
"
../../api/test/EditApiTest
"
;
import
ApiScenarioConfig
from
"
../../api/test/ApiScenarioConfig
"
;
import
PerformanceTestHome
from
"
../../performance/home/PerformanceTestHome
"
;
import
ApiTestList
from
"
../../api/test/ApiTestList
"
;
import
ApiTestHome
from
"
../../api/home/ApiTestHome
"
;
...
...
@@ -96,13 +96,13 @@ const router = new VueRouter({
},
{
path
:
'
test/create
'
,
name
:
"
create
Fuc
Test
"
,
component
:
EditApiTest
,
name
:
"
create
API
Test
"
,
component
:
ApiScenarioConfig
,
},
{
path
:
"
test/edit/:testId
"
,
name
:
"
edit
Fuc
Test
"
,
component
:
EditApiTest
,
name
:
"
edit
API
Test
"
,
component
:
ApiScenarioConfig
,
props
:
{
content
:
(
route
)
=>
{
return
{
...
...
frontend/src/business/components/performance/report/components/TestOverview.vue
浏览文件 @
e9e27f42
...
...
@@ -96,6 +96,10 @@
})
this
.
$get
(
"
/performance/report/content/load_chart/
"
+
this
.
id
,
res
=>
{
let
data
=
res
.
data
;
let
userList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
users
"
).
map
(
m
=>
m
.
yAxis
);
let
hitsList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
hits
"
).
map
(
m
=>
m
.
yAxis
);
let
userMax
=
this
.
_getChartMax
(
userList
);
let
hitsMax
=
this
.
_getChartMax
(
hitsList
);
let
loadOption
=
{
title
:
{
text
:
'
Load
'
,
...
...
@@ -105,30 +109,57 @@
color
:
'
#65A2FF
'
},
},
tooltip
:
{
show
:
true
,
trigger
:
'
axis
'
},
legend
:
{},
xAxis
:
{},
yAxis
:
[{
name
:
'
User
'
,
type
:
'
value
'
,
min
:
0
,
max
:
userMax
,
splitNumber
:
5
,
// interval: 10
/ 5
interval
:
userMax
/
5
},
{
name
:
'
Hits/s
'
,
type
:
'
value
'
,
splitNumber
:
5
,
min
:
0
,
// max: 5
,
// interval: 5
/ 5
max
:
hitsMax
,
interval
:
hitsMax
/
5
}
],
series
:
[]
};
let
setting
=
{
series
:
[
{
name
:
'
users
'
,
color
:
'
#0CA74A
'
,
},
{
name
:
'
hits
'
,
yAxisIndex
:
'
1
'
,
color
:
'
#65A2FF
'
,
},
{
name
:
'
errors
'
,
yAxisIndex
:
'
1
'
,
color
:
'
#E6113C
'
,
}
]
}
this
.
loadOption
=
this
.
generateOption
(
loadOption
,
data
);
this
.
loadOption
=
this
.
generateOption
(
loadOption
,
data
,
setting
);
})
this
.
$get
(
"
/performance/report/content/res_chart/
"
+
this
.
id
,
res
=>
{
let
data
=
res
.
data
;
let
userList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
users
"
).
map
(
m
=>
m
.
yAxis
);
let
responseTimeList
=
data
.
filter
(
m
=>
m
.
groupName
===
"
responseTime
"
).
map
(
m
=>
m
.
yAxis
);
let
userMax
=
this
.
_getChartMax
(
userList
);
let
resMax
=
this
.
_getChartMax
(
responseTimeList
);
let
resOption
=
{
title
:
{
text
:
'
Response Time
'
,
...
...
@@ -138,28 +169,55 @@
color
:
'
#99743C
'
},
},
tooltip
:
{
show
:
true
,
trigger
:
'
axis
'
},
legend
:
{},
xAxis
:
{},
yAxis
:
[{
name
:
'
User
'
,
type
:
'
value
'
,
splitNumber
:
5
,
min
:
0
min
:
0
,
max
:
userMax
,
interval
:
userMax
/
5
},
{
name
:
'
Response Time
'
,
type
:
'
value
'
,
splitNumber
:
5
,
min
:
0
min
:
0
,
max
:
resMax
,
interval
:
resMax
/
5
}
],
series
:
[]
}
this
.
resOption
=
this
.
generateOption
(
resOption
,
data
);
let
setting
=
{
series
:
[
{
name
:
'
users
'
,
color
:
'
#0CA74A
'
,
},
{
name
:
"
responseTime
"
,
yAxisIndex
:
'
1
'
,
color
:
'
#99743C
'
,
}
]
}
this
.
resOption
=
this
.
generateOption
(
resOption
,
data
,
setting
);
})
},
generateOption
(
option
,
data
)
{
generateOption
(
option
,
data
,
setting
)
{
let
chartData
=
data
;
let
seriesArray
=
[];
for
(
let
set
in
setting
)
{
if
(
set
===
"
series
"
)
{
seriesArray
=
setting
[
set
];
continue
;
}
this
.
$set
(
option
,
set
,
setting
[
set
]);
}
let
legend
=
[],
series
=
{},
xAxis
=
[],
seriesData
=
[];
chartData
.
forEach
(
item
=>
{
if
(
!
xAxis
.
includes
(
item
.
xAxis
))
{
...
...
@@ -183,11 +241,24 @@
type
:
'
line
'
,
data
:
data
};
let
seriesArrayNames
=
seriesArray
.
map
(
m
=>
m
.
name
);
if
(
seriesArrayNames
.
includes
(
name
))
{
for
(
let
j
=
0
;
j
<
seriesArray
.
length
;
j
++
)
{
let
seriesObj
=
seriesArray
[
j
];
if
(
seriesObj
[
'
name
'
]
===
name
)
{
Object
.
assign
(
items
,
seriesObj
);
}
}
}
seriesData
.
push
(
items
);
}
this
.
$set
(
option
,
"
series
"
,
seriesData
);
return
option
;
},
_getChartMax
(
arr
)
{
const
max
=
Math
.
max
(...
arr
);
return
Math
.
ceil
(
max
/
4.5
)
*
5
;
}
},
watch
:
{
status
()
{
...
...
frontend/src/business/components/track/case/TestCase.vue
浏览文件 @
e9e27f42
...
...
@@ -26,6 +26,7 @@
:current-project=
"currentProject"
@
openTestCaseEditDialog=
"openTestCaseEditDialog"
@
testCaseEdit=
"openTestCaseEditDialog"
@
refresh=
"refresh"
ref=
"testCaseList"
>
</test-case-list>
</el-main>
...
...
frontend/src/business/components/track/case/components/TestCaseExport.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div>
<el-tooltip
class=
"item"
effect=
"dark"
content=
"导出用例"
placement=
"right"
>
<el-button
type=
"info"
icon=
"el-icon-download"
size=
"mini"
circle
></el-button>
</el-tooltip>
</div>
</
template
>
<
script
>
export
default
{
name
:
"
TestCaseImport
"
}
</
script
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/case/components/TestCaseImport.vue
0 → 100644
浏览文件 @
e9e27f42
<
template
>
<div>
<el-tooltip
class=
"item"
effect=
"dark"
content=
"导入用例"
placement=
"right"
>
<el-button
type=
"info"
icon=
"el-icon-upload2"
size=
"mini"
circle
@
click=
"dialogVisible = true"
></el-button>
</el-tooltip>
<el-dialog
width=
"30%"
title=
"导入测试用例"
:visible.sync=
"dialogVisible"
@
close=
"init"
>
<el-row>
<el-link
type=
"primary"
class=
"download-template"
href=
"/test/case/export/template"
>
下载模版
</el-link>
</el-row>
<el-row>
<el-upload
class=
"upload-demo"
:action=
"'/test/case/import/' + projectId"
:on-preview=
"handlePreview"
multiple
:limit=
"1"
:on-exceed=
"handleExceed"
:beforeUpload=
"UploadValidate"
:on-success=
"handleSuccess"
:on-error=
"handleError"
:file-list=
"fileList"
>
<template
v-slot:trigger
>
<el-button
size=
"mini"
type=
"success"
plain
>
点击上传
</el-button>
</
template
>
<
template
v-slot:tip
>
<div
class=
"el-upload__tip"
>
只能上传xls/xlsx文件,且不超过20M
</div>
</
template
>
</el-upload>
</el-row>
<el-row>
<ul>
<li
v-for=
"errFile in errList"
:key=
"errFile.rowNum"
>
{{errFile.errMsg}}
</li>
</ul>
</el-row>
</el-dialog>
</div>
</template>
<
script
>
import
ElUploadList
from
"
element-ui/packages/upload/src/upload-list
"
;
export
default
{
name
:
"
TestCaseImport
"
,
components
:
{
ElUploadList
},
data
()
{
return
{
dialogVisible
:
false
,
fileList
:
[],
errList
:
[]
}
},
props
:
{
projectId
:
{
type
:
String
}
},
methods
:
{
handlePreview
(
file
)
{
console
.
log
(
"
init
"
);
this
.
init
();
},
handleExceed
(
files
,
fileList
)
{
this
.
$message
.
warning
(
`当前限制选择 1 个文件,本次选择了
${
files
.
length
}
个文件`
);
},
UploadValidate
(
file
)
{
var
suffix
=
file
.
name
.
substring
(
file
.
name
.
lastIndexOf
(
'
.
'
)
+
1
);
if
(
suffix
!=
'
xls
'
&&
suffix
!=
'
xlsx
'
)
{
this
.
$message
({
message
:
'
上传文件只能是 xls、xlsx格式!
'
,
type
:
'
warning
'
});
return
false
;
}
if
(
file
.
size
/
1024
/
1024
>
20
)
{
this
.
$message
({
message
:
'
上传文件大小不能超过 20MB!
'
,
type
:
'
warning
'
});
return
false
;
}
return
true
;
},
handleSuccess
(
response
)
{
let
res
=
response
.
data
;
if
(
res
.
success
)
{
this
.
$message
.
success
(
"
导入成功!
"
);
this
.
dialogVisible
=
false
;
this
.
$emit
(
"
refresh
"
);
}
else
{
this
.
errList
=
res
.
errList
;
}
this
.
fileList
=
[];
},
handleError
(
err
,
file
,
fileList
)
{
this
.
$message
.
error
(
err
.
message
);
},
init
()
{
this
.
fileList
=
[];
this
.
errList
=
[];
}
}
}
</
script
>
<
style
>
.el-dialog__body
{
padding-top
:
10px
;
}
.download-template
{
padding-top
:
0px
;
padding-bottom
:
10px
;
}
</
style
>
<
style
scoped
>
</
style
>
frontend/src/business/components/track/case/components/TestCaseList.vue
浏览文件 @
e9e27f42
...
...
@@ -4,12 +4,21 @@
<el-card
v-loading=
"result.loading"
>
<template
v-slot:header
>
<div>
<el-row
type=
"flex"
justify=
"s
pace-between
"
align=
"middle"
>
<el-row
type=
"flex"
justify=
"s
tart
"
align=
"middle"
>
<el-col
:span=
"5"
>
<span
class=
"title"
>
{{
$t
(
'
test_track.test_case
'
)
}}
</span>
<ms-create-box
:tips=
"$t('test_track.create')"
:exec=
"testCaseCreate"
/>
</el-col>
<el-col
:span=
"1"
:offset=
"12"
>
<test-case-import
:projectId=
"currentProject == null? null : currentProject.id"
@
refresh=
"refresh"
/>
</el-col>
<el-col
:span=
"1"
>
<test-case-export/>
</el-col>
<el-col
:span=
"5"
>
<span
class=
"search"
>
<el-input
type=
"text"
size=
"small"
:placeholder=
"$t('load_test.search_by_name')"
...
...
@@ -105,10 +114,12 @@
<
script
>
import
MsCreateBox
from
'
../../../settings/CreateBox
'
;
import
TestCaseImport
from
'
../components/TestCaseImport
'
;
import
TestCaseExport
from
'
../components/TestCaseExport
'
;
export
default
{
name
:
"
TestCaseList
"
,
components
:
{
MsCreateBox
},
components
:
{
MsCreateBox
,
TestCaseImport
,
TestCaseExport
},
data
()
{
return
{
result
:
{},
...
...
@@ -195,6 +206,9 @@
type
:
'
success
'
});
});
},
refresh
()
{
this
.
$emit
(
'
refresh
'
);
}
}
}
...
...
frontend/src/i18n/zh-CN.js
浏览文件 @
e9e27f42
...
...
@@ -168,7 +168,27 @@ export default {
'
resource_pool_is_null
'
:
'
资源池为空
'
,
},
api_test
:
{
'
select_resource_pool
'
:
'
请选择资源池
'
scenario
:
{
name
:
"
场景名称
"
,
base_url
:
"
基础URL
"
,
base_url_describe
:
"
基础URL作为所有请求的URL前缀
"
,
variables
:
"
变量
"
,
headers
:
"
请求头
"
},
request
:
{
name
:
"
请求名称
"
,
method
:
"
请求方法
"
,
url
:
"
请求URL
"
,
url_describe
:
"
例如: https://fit2cloud.com
"
,
parameters
:
"
请求参数
"
,
parameters_desc
:
"
参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2
"
,
headers
:
"
请求头
"
,
body
:
"
请求内容
"
,
body_kv
:
"
键值对
"
,
body_text
:
"
文本
"
,
assertions
:
"
断言
"
,
extract
:
"
提取
"
}
},
test_track
:
{
'
test_track
'
:
'
测试跟踪
'
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录