WEB自动化-04-Cypress 测试用例编写和组织

Posted 测试界的飘柔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WEB自动化-04-Cypress 测试用例编写和组织相关的知识,希望对你有一定的参考价值。

4 测试用例编写和组织

4.1 用例结构

Cypress是建立在Mocha和Chai之上,因此同时支持Chai的BDD和TDD两种风格。如果你熟悉javascript风格的代码,那么在Cypress中写测试用例是很容易上手的。

Mocha是一款适用于Node.js和浏览器的测试框架,可使用异步测试变得简单灵活。

Cypress的测试风格继承于Mocha,提供了describe()、context()、it()、specify()四个关键字,对于一条可执行的测试而言,必须包含以下两个组成部分:

describe()和context()等效,均表示一个测试套件或测试集
it()和specify()等效,均表示一个测试用例

示例如下所示:

describe('我是一个测试集', () => 
    it('测试用例-1', () => 
        expect(1+2).to.eq(3)
    );
    it('测试用例-2', () => 
        expect(3-2).to.eq(1)
    );
    it('测试用例-3', () => 
        expect(3*2).to.eq(5)
    );
);

最终的运行结果如下所示:

4.2 Hook

Hook中文常翻译为钩子函数,Cypress也提供了Hook(从Mocha继承而来)。这些Hook函数能够在运行每个测试用例或测试集之前,做一些准备操作,也可以每个测试用例或测试集运行完成之后执行一些操作。示例如下所示:

/// <reference types="cypress" />

before(()=>
    // 全局Hook
    // 所有用例运行行前执行,但仅执行一次
    cy.log("我是全局before Hook,所有测试用例运行前执行我,但仅执行一次")
);

beforeEach(()=>
    // 全局Hook
    // 每个测试用例运行前执行
    cy.log("我是全局beforeEach Hook,每个测试用例运行前执行我")
);

afterEach(()=>
    // 全局Hook
    // 每个测试用例运行完成后执行
    cy.log("我是全局afterEach Hook,每个测试用例运行完成后执行我")
);

after(()=>
    // 全局Hook
    // 所有测试用例运行完成后执行,但仅执行一次
    cy.log("我是全局after Hook,所有测试用例运行完成后执行,但仅执行一次")
);

describe('Test Hooks in TestSuite', () => 
    before(()=>
        // 当前测试集Hook
        // 当前测试集中,所有测试用例运行前执行,但仅执行一次
        cy.log("我是当前测试集before Hook,所有测试用例运行前执行我,但仅执行一次")
    );
    
    beforeEach(()=>
        // 当前测试集Hook
        // 当前测试集中,每个测试用例运行前执行
        cy.log("我是当前测试集beforeEach Hook,每个测试用例运行前执行我")
    );
    
    afterEach(()=>
        // 当前测试集Hook
        // 当前测试集中,每个测试用例运行完成后执行
        cy.log("我是当前测试集afterEach Hook,每个测试用例运行完成后执行我")
    );
    
    after(()=>
        // 当前测试集Hook
        // 当前测试集中,所有测试用例运行完成后执行,但仅执行一次
        cy.log("我是当前测试集after Hook,所有测试用例运行完成后执行,但仅执行一次")
    );
    
    it('Test Hook in case-1', () => 
        cy.log("我是测试用例-1");
        cy.visit("https://www.baidu.com/",timeout:10000);
    );
    it('Test Hook in case-2', () => 
        cy.log("我是测试用例-2");
        cy.visit("https://www.baidu.com/",timeout:10000);
    );
);

最终的运行结果如下所示:

从以上示例代码可以总结出来运行顺序如下所示:

  • 仅在测试开始时运行before且仅运行一次
  • 任何一个用例运行前都要运行beforeEach
  • 运行测试用例
  • 任何一个用例结束时都要运行afterEach
  • 仅在测试结束时运行after且仅运行一次

4.2.1 before()/after()

before()是所有测试用例的统一前置动作,而before()在一个describe()内只会执行一次,其执行顺序在所有测试用例it()之前。after()是所有测试用例的统一后置动作,而after()在一个describe()内只会执行一次,其执行顺序在所有测试用例it()之后。示例代码如下所示:

/// <reference types="cypress" />

describe('', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );
    
    it('测试用例-1', () => 
       cy.visit(baseUrl,timeout:10000);
       cy.get(".header input").should("have.class","new-todo");
    );

    it('测试用例-2', () => 
       cy.visit(baseUrl,timeout:10000);
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );
    
    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

最终的运行结果如下所示:

4.2.2 beforeEach()/afterEach()

beforeEach()是每个测试用例执行前的前置操作,即每个用例执行前都会执行一次。一个describe()有几个用例it()就会执行几次。afterEach()是每个测试用例执行后的后置操作,即每个用例执行完成后都会执行一次。一个describe()有几个用例it()就会执行几次。示例代码如下所示:

/// <reference types="cypress" />

describe('', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );

    beforeEach(()=>
      cy.log("每个用例执行前,均会执行一次beforeEach");
      cy.visit(baseUrl,timeout:10000);
    );

    it('测试用例-1', () => 
       cy.get(".header input").should("have.class","new-todo");
    );

    it('测试用例-2', () => 
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );

    afterEach(()=>
      cy.log("每个用例执行完成后,均会执行afterEach");
    );

    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

最终的运行结果如下所示:

4.3 包含/排队用例

通过学习Hook,我们可以把测试的前置和后置条件进行剥离,更好的编写和组织测试用例。在实际项目中,我们也需要仅运行指定的测试用例或跳过某些用例等。Cypress也提供相应的功能,一起来看看吧。

4.3.1 包含测试集/测试用例

在Cypress提供该功能的是.only()。当使用.only()指定某个测试集/测试用例后,仅当指定了的测试集/测试用例才会运行,其他未指定的测试集/测试用例则不会运行。示例如下所示:

1.仅运行指定测试集

/// <reference types="cypress" />

describe.only('包含only', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );
    
    beforeEach(()=>
      cy.log("每个用例执行前,均会执行一次beforeEach");
      cy.visit(baseUrl,timeout:20000);
    );

    it('测试用例-1', () =>   
       cy.get(".header input").should("have.class","new-todo");
    );

    it('测试用例-2', () => 
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );
    
    afterEach(()=>
      cy.log("每个用例执行完成后,均会执行afterEach");
    );
   
    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

describe('不包含only', () => 
  let tempUrl="https://www.baidu.com";
  beforeEach(()=>
     cy.visit(tempUrl);
  );
  
  it('不包含only测试用例-1', () => 
      cy.get("#kw").type("SurpassEnter");
      // cy.get("#kw").type("Surpass");
      // cy.get("#su").click();
  );
  
  afterEach(()=>
    cy.log("每个用例执行完成后,均会执行afterEach");
  );
);

最终的运行结果如下所示:

2.仅运行指定测试用例

describe.only('包含only', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );
    
    beforeEach(()=>
      cy.log("每个用例执行前,均会执行一次beforeEach");
      cy.visit(baseUrl,timeout:20000);
    );

    it('测试用例-1', () =>   
       cy.get(".header input").should("have.class","new-todo");
    );

    it.only('测试用例-2', () => 
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );
    
    afterEach(()=>
      cy.log("每个用例执行完成后,均会执行afterEach");
    );
   
    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

最终的运行结果如下所示:

4.3.2 排除测试集/测试用例

在Cypress提供该功能的是.skip()。当使用.skip()指定某个测试集/测试用例后,则不会运行该测试集/测试用例,其他未指定的测试集/测试用例则会运行。示例如下所示:

1.排除指定测试集

/// <reference types="cypress" />

describe('包含only', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );
    
    beforeEach(()=>
      cy.log("每个用例执行前,均会执行一次beforeEach");
      cy.visit(baseUrl,timeout:20000);
    );

    it('测试用例-1', () =>   
       cy.get(".header input").should("have.class","new-todo");
    );

    it.only('测试用例-2', () => 
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );
    
    afterEach(()=>
      cy.log("每个用例执行完成后,均会执行afterEach");
    );
   
    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

describe.skip('不包含only', () => 
  let tempUrl="https://www.baidu.com";
  beforeEach(()=>
     cy.visit(tempUrl);
  );
  
  it('不包含only测试用例-1', () => 
      cy.get("#kw").type("SurpassEnter");
      // cy.get("#kw").type("Surpass");
      // cy.get("#su").click();
  );
  
  afterEach(()=>
    cy.log("每个用例执行完成后,均会执行afterEach");
  );
);

最终的运行结果如下所示:

2.排除指定测试用例

describe('顶层测试集', () => 
    let baseUrl="https://example.cypress.io/todo";
    before(()=>
      cy.log("所有用例运行前执行before,仅执行一次");
    );
    
    beforeEach(()=>
      cy.log("每个用例执行前,均会执行一次beforeEach");
      cy.visit(baseUrl,timeout:20000);
    );

    it('测试用例-1', () =>   
       cy.get(".header input").should("have.class","new-todo");
    );

    it.skip('测试用例-2', () => 
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );
    
    describe.skip('嵌套测试集', () => 
      it('嵌套测试集用例-1', () => 
          cy.log("测试嵌套用例集");
      );
    );

    afterEach(()=>
      cy.log("每个用例执行完成后,均会执行afterEach");
    );
   
    after(()=>
        cy.log("所有用例运行完成后执行after,仅执行一次");
    );
);

最终的运行结果如下所示:

从上面示例中可以看出,标记为被排除的测试集后,则该测试集所有测试用例均不被执行

在实际项目中,only()和skip()通常是结合着使用,这时我们要先看标记describe的是skip()还是only(),其规则如下所示:

  • 如果先标记describe的是skip(),则整个测试用例集都将被排除,不执行
  • 如果先标记describe的是only(),若该用例集下,没有任何标记skip的用例,则所有用例都将运行
  • 如果先标记describe的是only(),若该用例集下,如果有标记only的用例,则将仅运行标记了only的用例
  • 如果describe和it均没有skip/only,则默认运行所有测试集或用例

4.4 动态执行测试用例

前面学习如何排除和执行指定测试集/测试用例,但在一些实际项目中,在执行用例的时候,会根据某一个条件来动态执行测试用例。为了解决这个问题,Cyrpess也提供相应的功能,通过更改相应的测试配置来支持。其语法格式如下所示:

describe(name, config, fn)
context(name, config, fn)
it(name, config, fn)
specify(name, config, fn)

部分配置参数为只读,不支持修改,可参考官网:https://docs.cypress.io/guides/references/configuration##Test-Configuration

1.测试用例集配置

示例代码如下所示:

/// <reference types="cypress" />

describe('When not in Chrome',browser:"!chrome", () => 
    let baseUrl="https://example.cypress.io/todo";

    it.only('Show warnings', () => 
       cy.visit(baseUrl,timeout:20000);
       cy.get(".header input").should("have.class","new-todo");
    );

    it.only('测试用例-2', () => 
    cy.visit(baseUrl,timeout:20000);
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );

);

在Chrome中运行时其结果如下所示:

下面的示例,将仅运行在指定的浏览器中,且指定的分辨率和环境变量将覆盖默认的值,如下所示:

/// <reference types="cypress" />

describe('When in Chrome',
   
       browser:"chrome",
       viewportWidth: 800,
       viewportHeight: 600,
       env: 
         flag: true,
         url: "https://www.surpassme.com",
       ,
   
   , 
   () => 
    let baseUrl="https://example.cypress.io/todo";

    it.only('Show warnings', () =>   
       cy.visit(baseUrl,timeout:20000);
       cy.get(".header input").should("have.class","new-todo");
    );

    it.only('测试用例-2', () => 
    cy.visit(baseUrl,timeout:20000);
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );

);

运行时其结果如下所示:

2.单个测试用例配置

示例代码如下所示:

/// <reference types="cypress" />

describe('When in Chrome', () => 
    let baseUrl="https://example.cypress.io/todo";

    it.only('Show warnings', 
        
            retries: 
                runMode: 3,
                openMode: 2
            
        ,
        () =>   
        cy.visit(baseUrl,timeout:20000);
        cy.get(".header input").should("have.class","new-todo");
        );

    it.only('测试用例-2', () => 
        cy.visit(baseUrl,timeout:20000);
       cy.get('.new-todo').type('todo Aenter').type('todo Benter');
       cy.get('.todo-list li').should('have.length', 4);
    );

);

4.5 动态生成测试用例

在实际项目中,会遇到一种情况,就是多个用例的操作步骤、断言均一样,仅是输入和输出不一样。如果我还是一条数据写一个用例,则效率会非常低。此时,我们可以利用Cypress动态生成测试用例来高效完成。我们以登录为例,如下所示:

1.创建一个postdata.json格式的数据文件

[
    
        "title": "测试数据-1",
        "customerName": "Surpass",
        "telephone": "18812345678",
        "email":"ceo@surpass.com"
    ,
    
        "title": "测试数据-2",
        "customerName": "kevin",
        "telephone": "17808231169",
        "email":"hr@surpass.com"
    
]

2.创建测试用例

/// <reference types="cypress" />

import data from "./postdata.json"

describe("测试发送数据", () => 
    let baseUrl="http://httpbin.org/forms/post";
    beforeEach(()=>
        cy.visit(baseUrl)
    );
    data.forEach( item => 
        it(item.title, () => 
            cy.log(item.title,item.customerName,item.telephone)
            cy.get(":nth-child(1) > label > input").type(item.customerName).should("contain.value",item.customerName);
            cy.get("form > :nth-child(2) > label > input").type(item.telephone).should("contain.value",item.telephone);
            cy.get("form > :nth-child(3) > label > input").type(item.email).should("contain.value",item.email);
        );
    )
)

运行时其结果如下所示:

如果大家对数据驱动比较了解的话,从上面的示例可以看到,是不是非常像?其最大价值为测试数据更改时,不影响测试代码。

4.6 断言风格

Cypress支持两种风格的断言BDD (expect/should) 和TDD (assert),以下列举常见的断言:

1、针对长度(Length)的断言

cy.get('.todo-list li').should('have.length', 4);

2、针对类(Class)的断言

cy.get("form").find("input").should("not.have.class","surpass");

3、针对值(Value)的断言

 cy.get("form > :nth-child(3) > label > input").should("contain.value","Surpass");

4、针对文本内容(Text)的断言

cy.get("button").should("contain.text","Submit order");
// 或
cy.get("button").should("not.contain","Surpass");

5、针对元素是否可见(Visible)的断言

cy.get("button").should("be.visible");

6、判断元素是否存在的断言

cy.get("button").should("not.exist");

7、判断元素状态(State)的断言

cy.get(":nth-child(4) > :nth-child(2) > label > input").click().should("be.checked");

8、针对CSS的断言

cy.get(".surpass").should("have.css","line-surpass");

4.7 运行测试用例

4.7.1 运行单个用例

运行单个测试用例,可以获得到更好的性能。在Test Runner界面,点击单个JS文件即可,其操作方法如下所示:

4.7.2 运行所有用例

运行所有测试用例,可以在Test Runner中单击运行Run x integration specs

4.7.3 运行部分用例

可以通过筛选从而仅运行符合筛选条件的用例。

筛选条件中忽略大小写

4.8 测试运行状态

在Cypress中,测试用例的运行状态可以分为4种状态:通过(Passed)、失败(Failed)、等待(Pending)、跳过(Skipped)。

4.8.1 Passed

Passed表示测试用例的运行状态为通过,没有出现断言失败的情况。如下所示:

4.8.2 Failed

Failed表示测试用例的运行状态为失败,有出现断言失败的情况。如下所示:

4.8.3 Pending

当一个测试用例没有想好怎么写时,我们可以写一些占位性质的用例,虽然Cypress不会运行,但却会让后续的用例状态处于Pending状态,如下所示:

/// <reference types="cypress" />

describe('测试Pending场景', () => 
    it('未完成的测试用例')

    it.skip('已经完成的用例', () => 
        let url="http://httpbin.org/forms/post";
        let customerName="Surpass",telephone="18812345678",email="ceo@surpass.com",textMsg="I Love Surpass",buttonText="Submit order";
        cy.visit(url,timeout:10000);
        cy.get(":nth-child(1) > label > input").type(customerName).should("contain.value",customerName);
        cy.get("form > :nth-child(2) > label > input").type(telephone).should("contain.value",telephone);
        cy.get("form > :nth-child(3) > label > input").type(email).should("contain.value",email);
        cy.get("textarea").type(textMsg).should("contain.value",textMsg);
        cy.get("button").should("contain.text",buttonText)
    );

    xit("另一种测试用例",()=>
        expect(false).to.true;
    );
);

运行时其结果如下所示:

以上三种情况的用例均会标识为Pending状态

如果用例编写人员故意使用以上三种方式中的其中一种方式来跳过执行用例,则Cypress将视为Pending Test

4.8.4 Skipped

当一个测试用例本来要执行,但却因为某些原因而未能运行,则Cypress会将该用例的运行状态视为Skipped。我们在一个测试集里面添加beforeEach(),示例代码如下所示:

/// <reference types="cypress" />

describe('测试用例状态为Skipped场景', () => 
    let baseUrl="https://example.cypress.io/todo";
    beforeEach(()=>
        cy.visit(baseUrl);
    );

    it('hides footer initially', () => 
        cy.get('.filters').should('be.exist');
    );

    it('adds 2 todos', () => 
        cy.get('.new-todo').type('learn testingenter').type('be coolenter');
        cy.get('.todo-list li').should('have.length', 4);
      )
);

以上这种情况,所有测试用例能顺利执行并通过,如下所示:

我们修改以上代码,让beforeEach()访问的地址不存在,再来看看,如下所示:

/// <reference types="cypress" />

describe('测试用例状态为Skipped场景', () => 
    let baseUrl="https://example.cypress.io/todo";
    beforeEach(()=>
        cy.visit(baseUrl+"/does-not-exist");
    );
    it('hides footer initially', () => 
        cy.get('.filters').should('be.exist');
    );

    it('adds 2 todos', () => 
        cy.get('.new-todo').type('learn testingenter').type('be coolenter');
        cy.get('.todo-list li').should('have.length', 4);
      )
);

运行结果如下所示:

在上述示例代码中,用例adds 2 todos本来想运行,但却因为beforeEach出现问题,而未能运行,最终被标识为Skipped。

4.9 Test Runner简介

我们先来看看Test Runner的截图如下所示:

Cypress自带的Test Runner功能非常强大,允许在测试运行期间查看测试命令的执行情况,并监控在命令执行时,被测试程序所处的状态。其组成部分如下所示:

1.测试状态目录

在上图标识为1的区域,用于展示测试用例运行成功、失败的数量、运行时间、禁用自动滚动和重新运行按钮等

2.运行命令日志

在上图标识为2的区域,用于记录每个执行的命令。鼠标单击,可以在应用程序预览(标识为5的区域)中查看其对应的元素及其执行详细信息。

3.元素定位辅助器

在上图标识为3的区域,通过该功能可以快速定位元素。如下所示:

4.URL预览栏

在上图标识为4的区域,通过URL预览栏,可以查看被测试程序运行进所对应的URL地址。

5.应用程序预览

在上图标识为5的区域,通过应用程序预览窗口,可以展示应用程序在测试运行中的实时状态

6.视窗大小窗口

在上图标识为6的区域,通过视窗大小可以了解当前应用程序运行时的视窗大小。如下所示:

现在我邀请你进入我们的软件测试学习交流群:746506216】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。

喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!

Cypress系列- Cypress 编写和组织测试用例篇 之 .skip() 和 .only() 的详细使用

如果想从头学起Cypress,可以看下面的系列文章哦

https://www.cnblogs.com/poloyy/category/1768839.html

 

前言

  • 在做自动化测试中,跳过执行某些测试用例,或只运行某些指定的测试用例,这种情况是很常见的
  • Cypress 中也提供了这种功能

 

跳过执行测试套件或测试用例

通过 .skip() 可以完成,简洁明了

 

跳过执行测试套件的栗子

知识点

通过 describe.skip() 或者 context.skip() 来跳过不需要执行的测试套件

 

测试代码

技术图片

 

测试结果

技术图片

可以看到,只有第二个测试套件里面的第一个 it() 执行了,其他两个 it() 均标记为未执行技术图片

 

跳过执行测试用例的栗子

知识点

通过 it.skip()  来跳过不需要执行的测试用例

 

测试代码

这里我们结合 beforeEach() ,看看对跳过执行的用例会不会也生效

技术图片

 

测试结果

技术图片

 可以看到,跳过执行的测试用例是不会执行前置操作或后置操作的

 

指定执行测试套件或测试用例

通过 .ony()  可以完成,简洁明了

重点:当存在 .only() 指定某个测试套件或测试用例时,只有这个测试套件或测试用例会被执行,其他未加 .only() 的测试套件或测试用例都不会执行

 

指定执行测试套件的栗子

知识点

通过 describe.only() 或者 context.only() 来指定需要执行的测试套件

 

测试代码

  • 结合 it.skip() 测试一下效果
  • 然后在第二个 describe (没有添加 .only() )里面的 context 测试套件添加 .only() 

技术图片

 

测试结果

这里 Cypress 的 Test  Runner显示的有点问题,我们来看 headless 模式下的运行情况

技术图片

  • 添加了 .only() 的子套件,即使父套件没有添加,它也会执行
  • 添加了 .only() 的套件,该套件下所有测试用例默认都会执行(即使不加 .only() ),除非加了 .skip() ,不过会处于 pending (待定)状态【我也不知道为啥...】

 

指定执行测试用例的栗子

知识点

通过 it.only()  来指定需要执行的测试用例

 

测试代码

技术图片

 

 

测试结果

技术图片

 

技术图片

  • 如果当前测试套件下有 it.only() ,那么即使存在测试套件添加了 .only() ,该测试套件也不会执行(如上面的:该套件不会运行)
  • 同个测试套件系啊有多个 it.only() 时,都会执行(有些地方可能会说只执行最后一个)

 

以上是关于WEB自动化-04-Cypress 测试用例编写和组织的主要内容,如果未能解决你的问题,请参考以下文章

基于selenium的web自动化测试

如果有一个项目我们怎么进行前期准备工作及测试用例的选取,在编写自动化测试用例过程中应该遵守以下几点原则?--web用例的稳定性和效率如何提高:

python自动化web自动化:6.测试框架实战二之页面page类用例编写失败截图及测试报告

如何在Linux 下执行自动化测试用例

web自动化流程总结

《随便测测》WEB接口测试平台