142.通过har文件生成用例
大家好~我是
米洛
!
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程
,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记
,获取最新文章教程!
回顾
温故而知新,特别是很久不来跟系列的读者,每次看看回顾都挺不错的,还记得上一节讲了啥。
上一节我们编写了导入har的接口,这一节我们就实现前端部分去配合它。
编写前端部分
还记得之前我们编写的用例录制页面吗?我决定把它的table部分抽离出来,因为这块是可以复用的。
并且我给自己定了一个小目标,后续的组件都基本上用tsx开发,虽然很艰难,但是有压力就有动力,何况tsx完美兼容jsx。
- 提取table部分,新建src/components/TestCase/recorder/RequestInfoList.tsx
import React from "react";
import type {ColumnsType} from "antd/lib/table/Table";
import {Modal, Table, Tag, Tooltip} from "antd";
import SyntaxHighlighter from "react-syntax-highlighter";
import {vs2015} from "react-syntax-highlighter/dist/cjs/styles/hljs";
import {TableRowSelection} from "antd/lib/table/interface";
import NoRecord from "@/components/NotFound/NoRecord";
interface RequestProps {
index: number;
url: string;
request_method: string;
status_code: number | string;
response_headers: string;
request_headers: string;
body: string;
}
interface RequestInfoProps {
dataSource: Array<RequestProps>;
rowKey?: string;
rowSelection: TableRowSelection<any>;
loading?: boolean;
emptyText?: string | '暂无数据';
}
interface TagProps {
color: string;
fontColor: string;
}
const tagColor = (method: string): TagProps => {
switch (method.toUpperCase()) {
case "GET":
return {color: 'rgb(235, 249, 244)', fontColor: 'rgb(47, 177, 130)'}
case "POST":
return {color: 'rgb(242, 244, 248)', fontColor: 'rgb(5, 112, 175)'}
case "PUT":
return {color: 'rgb(255, 247, 230)', fontColor: 'rgb(255, 174, 0)'}
case "DELETE":
return {color: 'rgb(253, 244, 246)', fontColor: 'rgb(222, 72, 108)'}
default:
return {color: 'rgb(243, 251, 254)', fontColor: 'rgb(166, 187, 210)'}
}
}
const MethodTag = ({color, text, fontColor}) => {
return <Tag style={{color: fontColor, borderRadius: 12, padding: '0 12px'}} color={color}>{text}</Tag>
}
const Detail = ({name, record}) => {
return <a onClick={() => {
Modal.info({
title: name,
width: 700,
bodyStyle: {padding: -12},
content: <SyntaxHighlighter language="json" style={vs2015}>{record[name]}</SyntaxHighlighter>
})
}}>详细</a>
}
const RequestInfoList: React.FC<RequestInfoProps> = ({dataSource, loading, ...restProps}) => {
const columns: ColumnsType<RequestProps> = [
{
title: '编号',
key: 'index',
render: (text, record, index) => `${index + 1}`
},
{
title: '请求地址',
key: 'url',
dataIndex: 'url',
width: '20%',
render: url => <Tooltip title={url}><a href={url}>{url.slice(0, 48)}</a> </Tooltip>
},
{
title: '请求方式',
key: 'request_method',
dataIndex: 'request_method',
render: md => <MethodTag fontColor={tagColor(md).fontColor} color={tagColor(md).color} text={md}/>
},
{
title: '请求headers',
key: 'request_headers',
dataIndex: 'request_headers',
render: (request_headers, record): React.ReactNode => {
return <Detail name="request_headers" record={record}/>
}
},
{
title: '请求参数',
key: 'body',
dataIndex: 'body',
render: (body, record) => {
if (!body) {
return '-'
}
return <Detail name="body" record={record}/>
}
},
{
title: '返回headers',
key: 'response_headers',
dataIndex: 'response_headers',
render: (response_headers, record) => {
if (!response_headers) {
return '-'
}
return <Detail name="response_headers" record={record}/>
}
},
{
title: 'response',
key: 'response_content',
dataIndex: 'response_content',
render: (response_content, record) => {
if (!response_content) {
return '-'
}
return <Detail name="response_content" record={record}/>
}
},
]
return (
<Table columns={columns} pagination={false} dataSource={dataSource}
rowSelection={restProps.rowSelection} rowKey={record => record[restProps.rowKey]}
loading={loading} locale={{emptyText: <NoRecord desc={restProps.emptyText} height={150}/>}}/>
)
}
export default RequestInfoList;
其实具体细节也很jsx差不多,由于ts我还不是很熟,我暂时把它当做PropType在用(以前早期的时候我们都会定义PropType保证组件的入参)。
这里我定义了RequestInfoProps参数,作为RequestInfoList组件的入参,有一部分可以省略,比如emptytext,也可以给默认值
。
可以看到,tsx除了一些变量类型的声明,interface的定义,<泛型>的使用,其他和jsx也没有太大区别(安慰你们,也安慰我自己,可以尝试下嘛)
对于我来说,尽量不用any就行了,免得写成了any script.
抽出组件以后呢,我们就需要传入dataSource了,因为这块内容是会由他的父组件
决定。
接着我们编写父组件部分,基本上根据用户点击har导入
和录制数据
按钮,就可以决定dataSource了,如果dataSource为空,还得给个友好提示,让用户去录制页面
录制去。
我们的这个表单是放到Drawer的,所以我们需要编写一个Drawer的tsx,并把开启/关闭抽屉
的权利赋给TestCaseDirectory.jsx组件。
由于前端代码很占篇幅,所以我们抽一部分出来讲解:
<Drawer title="生成用例" onClose={() => setVisible()} visible={visible} width={960} extra={
<Button onClick={onGenerateCase} type="primary"><FireOutlined/> 生成用例</Button>
}>
<Form form={form} {...CONFIG.SUB_LAYOUT}>
<Row gutter={8}>
<Col span={12}>
<Form.Item label="用例目录" name="directory_id" rules={[{required: true, message: '请选择用例目录'}]}>
<TreeSelect placeholder="请选择用例目录" treeLine treeData={directory}/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="用例名称" name="name" rules={[{required: true, message: '请输入用例名称'}]}>
<Input placeholder="请输入用例名称"/>
</Form.Item>
</Col>
</Row>
</Form>
{
record.length === 0 ?
<Empty image={NoRecord} imageStyle={{height: 220}} description="当前没有任何请求数据,你可以选择【录制】后的数据,也可以导入har文件提取接口👏">
<Space>
<Button onClick={onLoadRecords}><CameraOutlined/> 录制请求</Button>
<Upload showUploadList={false} customRequest={onUpload} fileList={[]}>
<Button type="primary">
<ImportOutlined/>
导入Har
</Button>
</Upload>
</Space>
</Empty> :
<RequestInfoList dataSource={record} rowSelection={rowSelection} rowKey="index"
loading={loading.effects['recorder/generateCase']}/>
}
</Drawer>
这个类似html的页面,是我们的视图层。我们的布局是这样, 对着代码看界面:
上面2个字段属于表单也就是Form,下面的字段属于RequestInfo组件或者Empty(随着是否有数据切换),接着就是右上角的生成按钮。
后续对着编写事件即可,其实前端也不算复杂
,简单的还是很好写的,就是进阶感觉很难。
最后来看看效果
别忘记生成后关闭对话框,并且重新获取目录下的case哦~
这样,一份粗糙的用例生成
就完事了,其实还有用例录制
页面没有完成,那个就先搁着吧,需要研究一下mitmproxy中的一个参数,暂且卖个关子。