Test platform series (142) generates use cases through har files

Milo2022-06-23 20:19:24

Keep creating , Accelerate growth ! This is my participation 「 Nuggets day new plan · 6 Yuegengwen challenge 」 Of the 26 God , Click to see the event details

Hello everyone ~ I am a Milo
I'm from 0 To 1 Build an open source interface testing platform , Also writing a set of corresponding course , I hope you can support me more .
Welcome to my official account. Milo's test diary , Get the latest articles and tutorials !


Consider the past you shall know the future , Especially the readers who haven't come to the series for a long time , Every time I look back, it's very good , I still remember what I said in the last section .

In the previous section, we wrote the import har The interface of , In this section, we will implement the front-end part to cooperate with it .

Writing the front-end part

Remember the use case recording page we wrote before ? I decided to make it table Part of it comes out , Because this piece can be reused .

And I set a small goal for myself , Subsequent components are basically tsx Development , It's hard though , But there is pressure, there is motivation , Moreover, tsx Perfect compatibility jsx.

  • extract table part , newly build 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 | ' Temporarily no data ';
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)'}
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> }) }}> detailed </a>
const RequestInfoList: React.FC<RequestInfoProps> = ({dataSource, loading, ...restProps}) => {
const columns: ColumnsType<RequestProps> = [
title: ' Number ',
key: 'index',
render: (text, record, index) => `${index + 1}`
title: ' Request address ',
key: 'url',
dataIndex: 'url',
width: '20%',
render: url => <Tooltip title={url}><a href={url}>{url.slice(0, 48)}</a> </Tooltip>
title: ' Request mode ',
key: 'request_method',
dataIndex: 'request_method',
render: md => <MethodTag fontColor={tagColor(md).fontColor} color={tagColor(md).color} text={md}/>
title: ' request headers',
key: 'request_headers',
dataIndex: 'request_headers',
render: (request_headers, record): React.ReactNode => {
return <Detail name="request_headers" record={record}/>
title: ' Request parameters ',
key: 'body',
dataIndex: 'body',
render: (body, record) => {
if (!body) {
return '-'
return <Detail name="body" record={record}/>
title: ' return 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;

In fact, the details are very jsx almost , because ts I'm not very familiar with , I regard it as PropType In use ( We used to define in the early days PropType Ensure the input of components ).

Here I define RequestInfoProps Parameters , As RequestInfoList Input parameters of components , Some parts can be omitted , such as emptytext, You can also give The default value is .

You can see ,tsx Except for some variable type declarations ,interface The definition of ,< Generic > Use , Others and jsx It doesn't make much difference ( Comfort you , And comfort myself , You can try it )

For me , As far as possible need not any That's it , So as not to write any script.

After pulling out the components , We need to pass in dataSource 了 , Because this content will be written by him Parent component decision .

Then we write the parent component part , Basically, according to the user's click har Import and Recording data Button , You can decide dataSource 了 , If dataSource It's empty , And a friendly reminder , Let users go to Record page Record to .

Our form is put in Drawer Of , So we need to write one Drawer Of tsx, And turn it on / close Drawer To confer on TestCaseDirectory.jsx Components .

Because the front-end code takes up a lot of space , So we took a part to explain :

 <Drawer title=" Generating use cases " onClose={() => setVisible()} visible={visible} width={960} extra={
<Button onClick={onGenerateCase} type="primary"><FireOutlined/> Generating use cases </Button>
<Form form={form} {...CONFIG.SUB_LAYOUT}> <Row gutter={8}> <Col span={12}> <Form.Item label=" Use case catalog " name="directory_id" rules={[{required: true, message: ' Please select the use case directory '}]}> <TreeSelect placeholder=" Please select the use case directory " treeLine treeData={directory}/> </Form.Item> </Col> <Col span={12}> <Form.Item label=" Use case name " name="name" rules={[{required: true, message: ' Please enter the use case name '}]}> <Input placeholder=" Please enter the use case name "/> </Form.Item> </Col> </Row> </Form>
record.length === 0 ?
<Empty image={NoRecord} imageStyle={{height: 220}} description=" There is currently no requested data , You can choose 【 Recording 】 Later data , You can also import har File extraction interface "> <Space> <Button onClick={onLoadRecords}><CameraOutlined/> Record request </Button> <Upload showUploadList={false} customRequest={onUpload} fileList={[]}> <Button type="primary"> <ImportOutlined/> Import Har </Button> </Upload> </Space> </Empty> :
<RequestInfoList dataSource={record} rowSelection={rowSelection} rowKey="index" loading={loading.effects['recorder/generateCase']}/>

This is similar to html The page of , Is our view layer . Our layout is like this , Look at the code interface :

above 2 Fields belong to the form, that is Form, The following fields belong to RequestInfo Component or Empty( With or without data switching ), Then there is the generate button in the upper right corner .

The subsequent events can be written , In fact, the front end is not complex , Simple or easy to write , It's hard to advance .

Finally, let's see the effect

Don't forget to close the dialog after generation , And retrieve the case Oh ~

such , A rough Use case generation Just a matter of , Actually there are Use case recording Page not completed , Let's put it aside , It needs to be studied mitmproxy One of the parameters in , Let's sell it for a while .