功能简介

前置操作和后置操作都是 API 请求在发送和响应过程中执行的脚本,主要用于在发起 API 请求前和获得响应后完成验证或执行某些操作,目的是为了提高 API 调试和测试的效率,并确保接口的正确性。


前置操作

前置操作是在 API 请求之前执行的脚本代码,可以用于做以下事情:

  1. 设置 API header(请求头)

    它们可用于设置请求的请求头、请求正文、验证请求参数和配置身份验证等。

  2. 设置 API 参数

    前置操作能够访问环境变量、全局变量和请求变量中的数据。前置操作也可以帮助请求者了解请求参数以及如何处理它们。

  3. 添加身份验证

    例如基本身份验证或 OAuth。在发送 API 请求之前,前置操作可以被用来获取访问令牌或者其他权限,确保 API 请求发送的是有效的和合法的请求。


变量替换

“变量替换”功能通常用于在发送 API 请求前,作用是把接口请求参数里的所有的已引用变量(包括动态值)替换成真实的请求内容。通常用于处理接口签名等转换场景。此时主要涉及以下两种场景:

  1. 通过脚本 set 变量

    该场景下的操作需要放在“变量替换”之前运行,否则通过该脚本 set 的变量就不会对当前接口的请求参数生效。

  2. 接口签名脚本

    该场景下的脚本需放在“变量替换”之后,这样脚本才能获取到该接口实际请求的参数值,否则获取到的参数值是变量替换之前的模板数据。

如需了解更多,请参考《接口签名如何处理?》


后置操作

后置操作能够利用获取来自接口响应的数据,例如状态代码、header、body 等信息,进行二次处理:

  1. 验证 API 响应的状态码和响应时间是否符合预期。
  2. 验证 API 响应的内容,如 JSON 或 XML 数据。
  3. 从 API 响应中提取数据,并将其用于后续请求。
  4. 自动提取响应中需要的数据。

层级关系

前后置操作都可以在接口目录中设置父级操作。父级操作可以被继承到该目录下的所有接口中,适用于需要在多个接口中执行相同的前置操作的场景,例如鉴权,变量替换等。 接口本身可以灵活调整各操作的运行顺序。

在这里插入图片描述

子级接口可以选择是否采用父级操作。

在这里插入图片描述

当子级接口下又存在多个接口时,若选择关闭引用,次级接口将默认关闭该自定义脚本。

在这里插入图片描述

在这里插入图片描述


提取变量

在一些具有明显上下游关系的接口中,有时需要将 A 接口的返回数据作为 B 接口的请求参数

比如在创建宠物信息场景下,需要将 A 接口返回的 pet_id 中的数据作为后续接口的请求参数,然后在 B 接口中的「后置操作」中添加「提取变量」功能,基于 A 接口返回的结果自动提取数据并设置为变量(临时变量/环境变量/全局变量),方便其它接口运行的时候直接使用。


指定变量类型

打开 Apifox 中的某条接口,在**“后置操作”页中添加“提取变量”,变量类型选择为“环境变量”**。

在这里插入图片描述


接口间相互传递数据

例如当前 B 接口的请求参数依赖于 A 接口返回的数据,现希望 B 接口在发送请求的同时能自动获取 A 接口返回的数据并作为请求参数。实现思路如下:

  1. A 接口在后置操作中添加提取变量功能,将返回的数据自动提取至变量中。
  2. B 接口对应的参数值直接引用已提取的变量。

A 接口

打开 A 接口用例的”后置操作“页,在后置操作中添加提取变量功能。将接口返回 Response JSON 数据里的 token 值提取到名为 petId 的变量中。

在这里插入图片描述


B 接口

在 B 接口中的请求参数中直接填写 {{petId}},即可在请求中引用上一步骤中所创建的数值。

在这里插入图片描述


若不确定是否正确引用了 A 接口所生成的数据,可以在 B 接口的前置操作中添加以下自定义脚本:

var petId = pm.environment.get("petId");

console.log(petId)

这样就可以在控制台中查看已引用的变量值。

在这里插入图片描述


JSON Path 介绍

JSONPath 之于 JSON,就如 XPath 之于 XML。JSONPath 可以方便对 JSON 数据结构进行内容提取。

如果对 JSON Path 不熟悉,推荐使用这个免费的 AI 工具来生成:https://app.anakin.ai/apps/21854


概览

  1. 根对象使用 $ 来表示,而无需区分是对象还是数组

  2. 表达式可以使用.,也可以使用[]。如:

    $.store.book[0].title$['store']['book'][0]['title']

  3. 表达式(<expr>)可用作显式名称或索引的替代,如:

    $.store.book[(@.length-1)].title :表示获取最后一个 book 的 title

  4. 使用符号@表示当前对象。过滤器表达式通过语法支持,?(<boolean expr>) 如:

    $.store.book[?(@.price < 10)].title :表示获取价格小于 10 的所有 book 的 title


语法

要点:

  • $ 表示文档的根元素
  • @ 表示文档的当前元素
  • .node_name['node_name'] 匹配下级节点
  • [index] 检索数组中的元素
  • [start:end:step] 支持数组切片语法
  • * 作为通配符,匹配所有成员
  • .. 子递归通配符,匹配成员的所有子元素
  • (<expr>) 使用表达式
  • ?(<boolean expr>)进行数据筛选

JSONPath 语法和 XPath 对比:

XPathJsonPath说明
/$文档根元素
.@当前元素
/.[]匹配下级元素
..N/A匹配上级元素,JsonPath 不支持此操作符
//..递归匹配所有子元素
**通配符,匹配下级元素
@N/A匹配属性,JsonPath 不支持此操作符
[][]下标运算符,根据索引获取元素,XPath 索引从 1 开始,JsonPath 索引从 0 开始
``[,]
N/A[start:end:step]数据切片操作,XPath 不支持
[]?()过滤表达式
N/A()脚本表达式,使用底层脚本引擎,XPath 不支持
()N/A分组,JsonPath 不支持

注意:

  • JsonPath 的索引从0开始计数
  • JsonPath 中字符串使用单引号表示,例如:$.store.book[?(@.category=='reference')]中的'reference'

JsonPath 示例

下面是相应的 JsonPath 的示例:

{
    "store": {
        "book": [{
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            }, {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99
            }, {
                "category": "fiction",
                "author": "Herman Melville",
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            }, {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    }
}

对这个文档进行解析:

XPathJsonPathResult
/store/book/author$.store.book[*].author所有 book 的 author 节点
//author$..author所有 author 节点
/store/*$.store.*store 下的所有节点,book 数组和 bicycle 节点
/store//price$.store..pricestore 下的所有 price 节点
//book[3]$..book[2]匹配第 3 个 book 节点
//book[last()]$..book[(@.length-1)],或 $..book[-1:]匹配倒数第 1 个 book 节点
//book[position()<3]$..book[0,1],或 $..book[:2]匹配前两个 book 节点
//book[isbn]$..book[?(@.isbn)]过滤含 isbn 字段的节点
//book[price<10]$..book[?(@.price<10)]过滤price<10的节点
//*$..*递归匹配所有子节点

使用脚本

脚本介绍

Apifox 包含一个基于 Javascript 的脚本引擎,通过脚本(JavaScript 代码片段)可实现在接口请求或集合测试时添加动态行为。脚本示例详见

脚本功能:

  • 使用后置脚本功能测试(断言)请求返回结果的正确性。

  • 使用前置脚本动态修改接口请求参数,如增加接口签名参数等。

  • 使用脚本操作变量并在接口请求之间传递数据。

  • 支持全局设置(在根目录里设置)前置操作、后置操作,设置后项目里的所有接口运行时都会生效。

  • 支持分组里设置前置操作、后置操作,设置后分组里的所有接口运行时都会生效。

  • 接口请求的执行流程:

    [全局前置脚本] -> [分组前置脚本] -> [接口前置脚本] -> [发送接口请求] -> [返回接口结果] -> [全局后置脚本] -> [分组后置脚本] -> [接口后置脚本]。

  • 脚本调试:调试脚本可以在 前置脚本后置脚本 里编写,使用console.log('hello')方式将调试信息写入控制台,打开 控制台 即可查看。

  • 脚本可以直接 调用其他语言编写的程序,支持java(.jar)pythonphpjsBeanShellgoshellrubyLua 等语言编写的外部程序。

注:Apifox 完美兼容 Postman 脚本语法,可以将 Postman 脚本可以无缝迁移至 Apifox。


前置脚本

  • 前置脚本是在请求发送前执行的代码片段。例如要在请求头中包含时间戳或在 URL 参数中发送随机的字母、数字、字符串等数据。
  • 前置脚本使用 JavaScript 编写,语法与后置脚本完全相同,但不存在 pm.response 对象。
  • 可以在接口的 “前置操作” tab 页中添加前置脚本,发送接口请求前将自动运行前置脚本。

示例-设置环境变量

  1. 若在请求参数中包含当前时间戳,那么可以将函数返回值设置为环境变量。

    在这里插入图片描述

  2. 将参数 timestamp 的值设置为 {{timestamp}} 。当请求发送时,前置脚本将被执行,环境变量 timestamp 的值会被设置为当前时间戳,同时 {{timestamp}}也会被替换为当前时间戳

    在这里插入图片描述


示例-获取 token 并添加到请求头

若在请求头需要携带 token,那么可以将获取 token 接口返回的 token 值直接添加到请求头

// token环境变量值为null,调用token获取接口并将值存入token环境变量
var requestUrl = 'https://test.com/api/usertoken?appKey=aaa&appSecret=bbb'
pm.sendRequest(requestUrl, function (err, response) {
    if (err) {
        console.error(err);
        return;
    }

    var respData = JSON.parse(response.text());
    var token = respData.data;
    console.log(token)
    // 将token放到环境变量,请求头token键值引用环境变量 
    //pm.environment.set('X-GV-3RD-USER-TOKEN', token);
    // 直接把token键值加入到请求头中
    pm.request.headers.add({
        key: "X-GV-3RD-USER-TOKEN",
        value: token
    });
});

更多示例


后置脚本

  • 发送接口请求后执行的代码片段也称为后置脚本。主要用来断言请求返回的结果是否正确、将请求返回的结果数据写入环境变量等场景。
  • 可以在接口的 “后置操作” tab 页中添加后置脚本,接口返回响应后将自动运行后置脚本。

示例-断言请求响应是否正确

// pm.response.to.have 示例
pm.test('返回结果状态码为 200', function() {
  pm.response.to.have.status(200);
});

// pm.expect() 示例
pm.test('当前为正式环境', function() {
  pm.expect(pm.environment.get('env')).to.equal('production');
});

// response assertions 示例
pm.test('返回结果没有错误', function() {
  pm.response.to.not.be.error;
  pm.response.to.have.jsonBody('');
  pm.response.to.not.have.jsonBody('error');
});

// pm.response.to.be* 示例
pm.test('返回结果没有错', function() {
  // assert that the status code is 200
  pm.response.to.be.ok; // info, success, redirection, clientError,  serverError, are other variants
  // assert that the response has a valid JSON body
  pm.response.to.be.withBody;
  pm.response.to.be.json; // this assertion also checks if a body  exists, so the above check is not needed
});

示例-将接口响应数据写入至环境变量

// 获取 JSON 格式的请求返回数据
var jsonData = pm.response.json();

// 将 jsonData.token 的值写入环境变量
pm.environment.set('token', jsonData.token);

更多示例


公共脚本

  • 公共脚本主要用途是实现脚本复用,避免在多个地方重复编写相同功能的脚本。
  • 可以将频繁被引用的脚本或通用的类与方法编写至公共脚本中,然后在接口中直接引用。
  • 管理公共脚本:在 “项目设置” - “公共脚本” 菜单中进行管理
  • 引用公共脚本:点击接口中的“前置操作”或“后置操作”页,在此处引用公共脚本。发起接口请求时将优先运行公共脚本;公共脚本的运行顺序与添加顺序保持一致。
  • 调用公共脚本
    • 脚本之间是可以实现相互调用,以下是具体的使用场景:
      • 普通脚本使用纯函数通过 return 返回的方式调用公共脚本,不建议使用 pm.sendRequestpm.environments.set 方法。
      • 公共脚本之间支持相互调用。
    • 为了避免脚本之间的变量冲突,所有脚本执行的时候都是在各自的作用域(通过闭包包裹)下运行。
    • 若使用 var、let、const、function 声明的变量或者方法,那么归属于局部变量或局部方法,无法被其他脚本调用的。如果想要使得变量或方法被其他脚本调用,需要将脚本改成全局变量全局方法
    • 注意:
      • 请确保不同脚本之间全局变量或者全局方法命名间没有冲突。
      • 调用脚本需要注意脚本执行顺序,只有后置的脚本可以调用先执行的脚本。

变量示例

  • 示例 A

    // 声明局部变量,无法被其他脚本调用
    var my_var = "hello"

    将代码修改为以下格式:

    // 声明全局变量,可以被其他脚本调用
    my_var = "hello";
    
  • 示例 B

    // 声明局部方法,无法被其他脚本调用
    function my_fun(name) {
      console.log("hello" + name);
    }
    

    将代码修改为以下格式:

    // 声明全局方法,可以被其他脚本调用
    my_fun = function (name) {
      console.log("hello" + name);
    };
    

pm 对象 API(全局方法)

pm

  • pm:Object

    pm 对象包含了接口(或测试集)运行的相关信息,并且可以通过它访问需要发送的请求信息和发送后返回的结果信息。另外还可以通过它 getset 环境变量和全局变量。

  • pm.info:Object

    pm.info 对象包含了接口(或测试集)运行的相关信息。

    • pm.info.eventName:String

      当前执行是什么类型的脚本:前置脚本(prerequest),或后置脚本(test)。

    • pm.info.iteration:Number

      当前执行第几轮循环(iteration),仅集合测试有效。

    • pm.info.iterationCount:Number

      本次执行需要循环的总轮数,仅集合测试有效。

    • pm.info.requestName:String

      当前正在运行的接口用例名称

    • pm.info.requestId:String

      当前正在运行的接口用例名称的唯一 ID


pm.sendRequest

pm.sendRequest:Function 用途为在脚本内异步发送 HTTP/HTTPS 请求。

  • 该方法接受一个 collection SDK 兼容的 request 参数和一个 callback 函数参数。

    callback 有 2 个参数,第一个是 error ,第二个是 collection SDK 兼容的 response。

    更多信息请查阅 Collection SDK 文档

  • 在前置脚本和后置脚本都可以使用。

示例:

// GET 请求示例
pm.sendRequest("https://postman-echo.com/get", function(err, res) {
  if (err) {
    console.log(err);
  } else {
    pm.environment.set("variable_key", "new_value");
  }
});

// 完整的 request 参数示例
const echoPostRequest = {
  url: "https://postman-echo.com/post",
  method: "POST",
  header: {
    headername1: "value1",
    headername2: "value2",
  },
  // body 为 x-www-form-urlencoded 格式
  body: {
    mode: "urlencoded", // 此处为 urlencoded
    // 此处为 urlencoded
    urlencoded: [
      { key: "account", value: "apifox" },
      { key: "password", value: "123456" },
    ],
  },
  /*
  // body 为 form-data 格式
  body: {
    mode: 'formdata', // 此处为 formdata
    // 此处为 formdata
    formdata: [
      { key: 'account', value: 'apifox' },
      { key: 'password', value: '123456' }
    ]
  }

  // body 为 json 格式
  header: {
    "Content-Type": "application/json", // 注意:header 需要加上 Content-Type
  },
  body: {
    mode: 'raw',// 此处为 raw
    raw: JSON.stringify({ account: 'apifox', password:'123456' }), // 序列化后的 json 字符串
  }

  // body 为 raw 或 json 格式
  body: {
    mode: 'raw',
    raw: '此处为 body 内容',
  }
  */
};
pm.sendRequest(echoPostRequest, function(err, res) {
  console.log(err ? err : res.json());
});

// 对返回结果进行断言
pm.sendRequest("https://postman-echo.com/get", function(err, res) {
  if (err) {
    console.log(err);
  }
  pm.test("response should be okay to process", function() {
    pm.expect(err).to.equal(null);
    pm.expect(res).to.have.property("code", 200);
    pm.expect(res).to.have.property("status", "OK");
  });
});

参考:


pm.variables

临时变量

  • pm.variables.has(variableName:String):function → Boolean: 检查是否存在某个临时变量。
  • pm.variables.get(variableName:String):function → *: get 单个临时变量。
  • pm.variables.set(variableName:String, variableValue:String):function → void: set 单个临时变量。
  • pm.variables.replaceIn(variableName:String):function: 以真实的值替换字符串里的包含的动态变量,如{{variable_name}}
  • pm.variables.toObject():function → Object: 以对象形式获取所有临时变量。

不同类型的变量,有不同的优先级,不同类型变量的优先级顺序为: 临时变量 > 环境变量 > 全局变量

Variable SDK 参考


pm.iterationData

测试数据变量

因为测试数据是单独管理的,暂不支持在脚本中直接设置测试数据变量,但是可以在脚本中访问测试数据变量,如下:

  • pm.iterationData.has(variableName:String):function → Boolean: 检查是否存在某个测试数据变量。
  • pm.iterationData.get(variableName:String):function → *: get 单个测试数据变量。
  • pm.iterationData.replaceIn(variableName:String):function: 以真实的值替换字符串里的包含的动态变量,如{{variable_name}}
  • pm.iterationData.toObject():function → Object: 以对象形式获取所有测试数据变量。

pm.environment

环境变量

  • pm.environment.name:String: 环境名。
  • pm.environment.has(variableName:String):function → Boolean:检查是否存在某个环境变量。
  • pm.environment.get(variableName:String):function → *:get 单个环境变量。
  • pm.environment.set(variableName:String, variableValue:String):function:set 单个环境变量。
  • pm.environment.replaceIn(variableName:String):function:以真实的值替换字符串里的包含的动态变量,如{{variable_name}}
  • pm.environment.toObject():function → Object:以对象形式获取当前环境的所有变量。
  • pm.environment.unset(variableName:String):function: unset 单个环境变量。
  • pm.environment.clear():function:清空当前环境的所有变量。

注意:以上所有操作都是读写的本地值,而不会读写远程值


pm.globals

全局变量

  • pm.globals.has(variableName:String):function → Boolean:检查是否存在某个全局变量。

  • pm.globals.get(variableName:String):function → *:get 单个全局变量。

  • pm.globals.set(variableName:String, variableValue:String):function:set 单个全局变量。

  • pm.globals.replaceIn(variableName:String):function:以真实的值替换字符串里的包含的动态变量,如{{variable_name}}

    如前置脚本,获取请求参数的值如果包含变量,则需要使用 pm.globals.replaceIn 才能将变量替换会真正的值。

  • pm.globals.toObject():function → Object:以对象形式获取所有全局变量。

  • pm.globals.unset(variableName:String):function: unset 单个全局变量。

  • pm.globals.clear():function:清空当前环境的全局变量。

注意:以上所有操作都是读写的本地值,而不会读写远程值


pm.request

pm.requestRequest SDK 参考

request 是接口请求对象。在前置脚本中表示将要发送的请求,在后置脚本中表示已经发送了的请求

request 包含了以下结构:

  • pm.request.url:Url: 当前请求的 URL。
  • pm.request.getBaseUrl():获取当前运行环境选择的的 前置 URL,在 2.1.39 版本之后支持。
  • pm.request.headers:HeaderList:当前请求的 headers 列表。
  • pm.request.method:String 当前请求的方法,如GETPOST等。
  • pm.request.body:RequestBody: 当前请求的 body 体。
  • pm.request.headers.add({ key: headerName:String, value: headerValue:String}):function: 给当前请求添加一个 key 为headerName的 header。
  • pm.request.headers.remove(headerName:String):function: 删除当前请求里 key 为headerName的 header
  • pm.request.headers.get(headerName:String):function: 查询当前请求里的 headerName
  • pm.request.headers.upsert({ key: headerName:String, value: headerValue:String}):function: upsert key 为headerName的 header(如不存在则新增,如已存在则修改)。
  • pm.request.auth: 当前请求的身份验证信息

使用示例:

在后置操作中填入自定义脚本,参考下图示例,选择所需要的提取对象,编写对应的函数。

例如提取请求中的 headers 中的 Accept 值并打印到控制台。

在这里插入图片描述


pm.response

以下部分 API 仅在后置脚本中可用

pm.response: Response SDK 参考

在后置脚本中 pm.response 接口请求完成后返回的 response 信息。

response 包含了以下结构:

  • pm.response.code:Number

  • pm.response.status:String

  • pm.response.headers:HeaderList

  • pm.response.responseTime:Number

  • pm.response.responseSize:Number

  • pm.response.text():Function → String

  • pm.response.json():Function → Object

  • pm.response.setBody('')

  • pm.response.headers.get:

    在后置脚本中使用 pm.response.headers.get 命令可以提取返回响应中的 headers 中的值。例如想要在控制台中显示 Header 中的 Date 值,那么可以在后置操作中填写如下自定义脚本:

    var test = pm.response.headers.get("Date")
    console.log(test)
    

    在这里插入图片描述

    若希望将其作为变量供其它接口调用,详细说明请参考《最佳实践:接口之间如何传递数据?》


pm.cookies

pm.cookies: CookieList SDK 参考

cookies 为当前请求对应域名下的 cookie 列表。

  • pm.cookies.has(cookieName:String):Function → Boolean

    检查是否存在名为cookieName的 cookie 值

  • pm.cookies.get(cookieName:String):Function → String

    get 名为cookieName的 cookie 值

  • pm.cookies.toObject:Function → Object

    以对象形式获取当前域名下所有 cookie

  • pm.cookies.jar().clear(pm.request.getBaseUrl())

    清空全局 cookies

注意:pm.cookies 为接口请求后返回的 cookie,而不是接口请求发出去的 cookie。


pm.test

  • pm.test(testName:String, specFunction:Function):Function

    该方法用来断言某个结果是否符合预期。

    • 以下示例为检查返回的 respone 是否正确:

      pm.test("response should be okay to process", function() {
        pm.response.to.not.be.error;
        pm.response.to.have.jsonBody("");
        pm.response.to.not.have.jsonBody("error");
      });
      
    • 通过 callback 的可选参数 done ,还可用来测试异步方法:

      pm.test("async test", function(done) {
        setTimeout(() => {
          pm.expect(pm.response.code).to.equal(200);
          done();
        }, 1500);
      });
      
  • pm.test.index():Function → Number

    从特定位置获取测试总数


pm.expect

pm.expect(assertion:*):Function → Assertion

pm.expect 是一个普通的断言方法,查看详细的说明:ChaiJS expect BDD library

该方法用来断言 responsevariables里的数据非常有用。

更多关于 pm.expect断言的是示例,查看:Assertion library examples


Response 对象可用的断言 API 列表

  • pm.response.to.have.status(code:Number)
  • pm.response.to.have.status(reason:String)
  • pm.response.to.have.header(key:String)
  • pm.response.to.have.header(key:String, optionalValue:String)
  • pm.response.to.have.body()
  • pm.response.to.have.body(optionalValue:String)
  • pm.response.to.have.body(optionalValue:RegExp)
  • pm.response.to.have.jsonBody()
  • pm.response.to.have.jsonBody(optionalExpectEqual:Object)
  • pm.response.to.have.jsonBody(optionalExpectPath:String)
  • pm.response.to.have.jsonBody(optionalExpectPath:String, optionalValue:*)
  • pm.response.to.have.jsonSchema(schema:Object)
  • pm.response.to.have.jsonSchema(schema:Object, ajvOptions:Object)

pm.response.to.be

pm.response.to.be 是用来快速断言的一系列内置规则。

  • pm.response.to.be.info

    检查状态码是否为1XX

  • pm.response.to.be.success

    检查状态码是否为2XX

  • pm.response.to.be.redirection

    检查状态码是否为3XX

  • pm.response.to.be.clientError

    检查状态码是否为4XX

  • pm.response.to.be.serverError

    检查状态码是否为5XX

  • pm.response.to.be.error

    检查状态码是否为4XX5XX

  • pm.response.to.be.ok

    检查状态码是否为200

  • pm.response.to.be.accepted

    检查状态码是否为202

  • pm.response.to.be.badRequest

    检查状态码是否为400

  • pm.response.to.be.unauthorized

    检查状态码是否为401

  • pm.response.to.be.forbidden

    检查状态码是否为403

  • pm.response.to.be.notFound

    检查状态码是否为404

  • pm.response.to.be.rateLimited

    检查状态码是否为429


使用 JS 类库

内置类库列表

  • Encode、Decode 库

    • crypto-js

      (v3.1.9-1):编码 / 解码库,常用的编码解码方式基本都有,如 Base64、MD5、SHA、HMAC、AES 等等。

      • 注意:只能 require 整个模块,不能单独 require 类库里的某个子模块。
    • atob(v2.1.2):Base64 解码

    • btoa(v1.2.1):Base64 编码

    • tv4(v1.3.0):JSONSchema 校验库

    • xml2js(v0.4.19):XML 转 JSON

  • Encode、Decode 库

    • jsrsasign(10.3.0):RSA 加密 / 解密 (Apifox 版本号 >= 1.4.5 才支持,老版本不支持)
  • 断言

    • chai (v4.2.0):BDD / TDD 断言库
  • 实用工具

    • postman-collection( v3.4.0):Postman Collection 库
    • cheerio(v0.22.0):jQuery 的一个子集
    • lodash (v4.17.11):JS 实用工具库
    • moment(v2.22.2):日期处理库 (不含 locales)
    • uuid :生成 UUID
    • csv-parse/lib/sync( v1.2.4):CSV 格式数据处理
    • iconv-lite:用于字符编码之间的转换,支持数十种字符编码格式的转换。
    • mockjs:生成随机数据,拦截 Ajax 请求。
  • JSONSchema 校验库

    • ajv(v6.6.2):JSONSchema 校验库
  • 内置 NodeJS 模块

通过 require 可以加载并使用 Apifox 内置的 JS 类库(也可以直接使用,但这样编写脚本时可能缺乏一些灵活性,具体选择取决于业务需求)。

// 引入 CryptoJS 库(可省略)
const CryptoJS = require('crypto-js');
console.log(CryptoJS.SHA256("Message"));

内置类库使用示例

使用内置类库加密、解密、编码、解码数据的常见示例参考。

SHA256 加密
// SHA256 加密,输出 Base64
// 定义要加密的消息
const message = "Hello, World!";

// 使用 SHA256 算法进行加密
const hash = CryptoJS.SHA256(message);

// 将加密结果输出为 Base64 编码
const base64Encoded = CryptoJS.enc.Base64.stringify(hash);

// 输出结果
console.log("SHA256: " + base64Encoded);

HMAC-SHA256 加密
// HMAC-SHA256 加密,输出 Base64
// 定义要加密的消息和密钥
const message = "Hello, World!";
const secretKey = "MySecretKey";

// 使用 HMAC-SHA256 算法进行加密
const hash = CryptoJS.HmacSHA256(message, secretKey);

// 将加密结果输出为 Base64 编码
const base64Encoded = CryptoJS.enc.Base64.stringify(hash);

// 输出结果
console.log("HMAC-SHA256: " + base64Encoded);

Base64 编码/解码

Base64 编码

// 要编码的消息
const message = "你好,Apifox!";

// 使用 CryptoJS 进行 Base64 编码
const wordArray = CryptoJS.enc.Utf8.parse(message);
const base64Encoded = CryptoJS.enc.Base64.stringify(wordArray);

// 输出编码结果
console.log("Base64: " + base64Encoded);

Base64 解码

  • 字符串解码:

    // Base64 编码的字符串(一般从响应数据中提取)
    let encodedData = {
        "data": "5L2g5aW977yMQXBpZm94IQ=="
    }
    
    // 解码 Base64 编码的数据
    let decodedData = CryptoJS.enc.Base64.parse(encodedData.data).toString(CryptoJS.enc.Utf8);
    
    // 输出解码后的结果
    console.log(decodedData); // "你好,Apifox!"
    
  • JSON 解码:

    可通过 pm.response.setBody() 方法将解码后的 JSON 数据设置为响应 Body。

    // 引入 CryptoJS 库
    const CryptoJS = require("crypto-js");
    
    // 从响应中获取 Base64 编码的字符串
    let encodedData = pm.response.text();
    
    // 解码 Base64 编码的数据
    let decodedData = CryptoJS.enc.Base64.parse(encodedData).toString(CryptoJS.enc.Utf8);
    
    // 解析解码后的 JSON 字符串
    let jsonData = JSON.parse(decodedData);
    
    // 将解析后的 JSON 数据设置为响应体
    pm.response.setBody(jsonData);
    
    // 输出结果
    console.log(jsonData);
    

AES 加密/解密

AES 加密

// 引入 CryptoJS 库
const CryptoJS = require("crypto-js");

// 假设这是要加密的`password`字段的值,从环境变量中获取
const password = pm.environment.get("password"); 

// 使用安全的密钥和 IV(这里为了示例简化说明,实际应用中需要保护好这些敏感信息)
const key = CryptoJS.enc.Utf8.parse('mySecretKey12345'); // 确保是 16/24/32 字节
const iv = CryptoJS.enc.Utf8.parse('myIVmyIVmyIVmyIV'); // 确保是 16 字节

// AES加密
const encrypted = CryptoJS.AES.encrypt(password, key, {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
}).toString();

// 把加密后的密码设置为一个新的变量,可以在请求体内使用
pm.environment.set("encryptedPassword", encrypted);

AES 解密

假设有一个经过 AES 加密的密文,其加密模式为 ECB,填充模式为 Pkcs7。其 AES 解密脚本示例如下:

// 引入 CryptoJS 库
const CryptoJS = require('crypto-js');

// 经过 Base64 编码的 AES 加密后的密文(一般从响应数据中提取)
const ciphertext = "Gig+YJFu4fLrrexzam/vblRV3hoT25hPZn0HoNoosHQ=";

// 解密所需密钥,确保是 16/24/32 字节(一般从环境变量中读取)
const key = CryptoJS.enc.Utf8.parse('1234567891234567');

// AES 解密
const decryptedBytes = CryptoJS.AES.decrypt(ciphertext, key, {
    mode: CryptoJS.mode.ECB, // 解密模式
    padding: CryptoJS.pad.Pkcs7 // 填充方式
});

// 将解密后的字节数组转换为 UTF-8 字符串
const originalText = decryptedBytes.toString(CryptoJS.enc.Utf8);

// 输出解密后的文本
console.log(originalText); // "你好,Apifox!"

RSA 加密/解密

RSA 加密

// 引入 jsrsasign 库
const jsrsasign = require('jsrsasign');

// 定义公钥(一般从环境变量中读取)
const publicKey = `
-----BEGIN PUBLIC KEY-----
公钥……
-----END PUBLIC KEY-----
`;

// 用公钥加密
const plaintext = "你好,Apifox!";
const pubKeyObj = jsrsasign.KEYUTIL.getKey(publicKey);

const encryptedHex = jsrsasign.KJUR.crypto.Cipher.encrypt(plaintext, pubKeyObj);
console.log("加密密文:", encryptedHex);

RSA 解密

// 引入 jsrsasign 库
const jsrsasign = require('jsrsasign');

// 定义私钥(一般从环境变量中读取)
const privateKeyPEM = `
-----BEGIN PRIVATE KEY-----
私钥……
-----END PRIVATE KEY-----
`;

// 定义密文(一般从响应数据中提取)
const ciphertext = '';

// 解密
const prvKeyObj = jsrsasign.KEYUTIL.getKey(privateKeyPEM);
const decrypted = jsrsasign.KJUR.crypto.Cipher.decrypt(ciphertext, prvKeyObj);
console.log(decrypted);

一个简单的 RSA 加密解密的完整示例参考 (注意 jsrsasign 版本为 10.3.0,其它版本语法可能会不兼容),可以将其在 Node.js 环境下运行,并根据需要在 Apifox 中执行加密或解密的操作:

const rsa = require('jsrsasign');

// 生成 RSA 密钥对
const keypair = rsa.KEYUTIL.generateKeypair("RSA", 2048);
const publicKey = rsa.KEYUTIL.getPEM(keypair.pubKeyObj);
const privateKey = rsa.KEYUTIL.getPEM(keypair.prvKeyObj, "PKCS8PRV");

console.log("公钥:", publicKey);
console.log("私钥:", privateKey);

// 用公钥加密
const plaintext = "你好,Apifox!";
const pubKeyObj = rsa.KEYUTIL.getKey(publicKey);

const encryptedHex = rsa.KJUR.crypto.Cipher.encrypt(plaintext, pubKeyObj);
console.log("加密密钥:", encryptedHex);

// 用私钥解密
const prvKeyObj = rsa.KEYUTIL.getKey(privateKey);

const decrypted = rsa.KJUR.crypto.Cipher.decrypt(encryptedHex, prvKeyObj);
console.log("解密明文:", decrypted);

在这里插入图片描述


非内置的 JS 类库

使用 fox.liveRequire 方法可以动态地引入从 npm 上发布的其他纯 JavaScript 库,以扩展 Apifox 的功能。请注意,仅支持在浏览器端运行的库,并且不支持带有 C/C++ 等语言扩展的库。如果尝试加载此类库,可能会导致运行超时或产生异常。为了最好的运行效果,请选择明确支持浏览器端运行的库,并仅引入纯 JavaScript 库。

注意:

  • 仅 Apifox 版本号 >= 1.4.5 才支持,老版本不支持,请升级到最新版。
  • 非内置库需要动态从网络下载 JS 类库,所以必须要联网,且性能有有所损耗,建议优先使用内置的 JS 库。
  • WEB 版不支持该方式,请使用 Apifox 桌面客户端。

示例代码如下:

// 使用非内置的 JS 类库示例

// 引入单个 npm 库:md5
fox.liveRequire("md5", (md5) => {
  try {
    console.log(md5("message")); // => '04a410d39d39f9831217edd702d7fde0'
  } catch (error) {
    console.error("An error occurred during liveRequire callback", error);
    throw error;
  }
});

// 引入多个 npm 库:camelize,md5
fox.liveRequire(["camelize", "md5"], ([camelize, md5]) => {
  try {
    console.log("loaded module  is ", camelize, md5);

    console.log('camelize("foo-bar") is ', camelize("foo-bar")); // => 'fooBar'
    console.log('md5("message") is ', md5("message")); // => '04a410d39d39f9831217edd702d7fde0'
  } catch (error) {
    console.error("An error occurred during liveRequire callback", error);
    throw error;
  }
});

// 引入多个 npm 库(带版本):camelcase,md5
fox.liveRequire(
  [
    {
      name: "camelcase",
      version: "6.2.1",
    },
    "md5",
  ],
  ([camelCase, md5]) => {
    try {
      console.log("loaded module  is ", camelCase, md5);

      console.log('camelCase("foo-bar") is ', camelCase("foo-bar")); // => 'fooBar'
      console.log('md5("message") is ', md5("message")); // => '04a410d39d39f9831217edd702d7fde0'
    } catch (error) {
      console.error("An error occurred during liveRequire callback", error);
      throw error;
    }
  }
);

脚本示例

脚本使用环境/全局/临时变量

  • 环境变量

    // 设置环境变量
    pm.environment.set('variable_key', 'variable_value');
    
    // 获取环境变量
    var variable_key = pm.environment.get('variable_key');
    
    // unset 环境变量
    pm.environment.unset('variable_key');
    

    环境变量写入

    环境变量支持数组、对象、字符串等形式存储

    var array = [1, 2, 3, 4];
    pm.environment.set('array', JSON.stringify(array));
    
    var obj = { a: [1, 2, 3, 4], b: { c: 'val' } };
    pm.environment.set('obj', JSON.stringify(obj));
    

    读取的时候,需要使用JSON.parse 转换回来

    try {
      var array = JSON.parse(pm.environment.get('array'));
      var obj = JSON.parse(pm.environment.get('obj'));
    } catch (e) {
      // 处理异常
    }
    
  • 全局变量

    // 设置全局变量
    pm.globals.set('variable_key', 'variable_value');
    
    // 获取全局变量
    var variable_key = pm.globals.get('variable_key');
    
    // unset 全局变量
    pm.globals.unset('variable_key');
    
  • 临时变量

    // 设置临时变量
    pm.variables.set('variable_key', 'variable_value');
    
    // 获取临时变量
    var variable_key = pm.variables.get('variable_key');
    
    // unset 临时变量
    pm.variables.unset('variable_key');
    

脚本读取/修改接口请求信息

脚本如何读取/修改接口请求信息主要使用 pm.request

注意

  • 只有在前置脚本里修改请求信息才是有效的,在后置脚本里修改无效。

  • 通过脚本取出来的接口参数,如果参数包含变量,变量是不会替换成对应的值。如想要获取替换后的值,可使用

    pm.variables.replaceIn
    

    方法处理:

    // pm.variables.replaceIn 处理参数里的变量
    var body = pm.variables.replaceIn(pm.request.body.raw);
    var jsonData = JSON.parse(body);
    

URL 相关信息

// 获取 url 对象
var urlObj = pm.request.url;

// 获取完整接口请求 URL,包含 query 参数
var url = urlObj.toString();

// 获取协议(http 或 https)
var protocol = urlObj.protocol;

// 获取 端口
var port = urlObj.port;

Header 参数

获取 header 参数

// 获取 Header 参数对象
var headers = pm.request.headers;

// 获取 key 为 field1 的 header 参数的值
var field1 = headers.get("field1");

// 已键值对象方式获取所有 header 参数
var headersObject = headers.toObject();

// 遍历整个 header
headers.each((item) => {
  console.log(item.key); // 输出参数名
  console.log(item.value); // 输出参数值
});

修改 header 参数

// 获取 Header 参数对象
var headers = pm.request.headers;

// 增加 header 参数
headers.add({
  key: "field1",
  value: "value1",
});

// 修改 header 参数(如不存在则新增)
headers.upsert({
  key: "field2",
  value: "value2",
});

Query 参数

获取 query 参数

// 获取 Query 参数对象
var queryParams = pm.request.url.query;

// 获取 key 为 field1 的 query 参数的值
var field1 = queryParams.get("field1");

// 已键值对象方式获取所有 query 参数
var quertParamsObject = queryParams.toObject();

// 遍历整个 query
queryParams.each((item) => {
  console.log(item.key); // 输出参数名
  console.log(item.value); // 输出参数值
});

修改 query 参数

// 获取 Query 参数对象
var queryParams = pm.request.url.query;

// 增加 query 参数
queryParams.add({
  key: "field1",
  value: "value1",
});

// 修改 query 参数(如不存在则新增)
queryParams.upsert({
  key: "field2",
  value: "value2",
});

Body 参数

Body 参数来自 pm.request.body,pm.request.body 是一个RequestBody 实例。

注意

  • 如需修改 Body 里的数据,推荐在 Body 里引用变量,然后在前置脚本里设置对应变量的值,即可达到修改的目的。

  • Body 参数也支持直接修改(版本 >= 1.4.16+),使用方式如下:

    var body = pm.request.body.toJSON();
    console.log("body 对象", body);
    
    var bodyStr = body.raw;
    console.log("body 字符串", bodyStr);
    
    var bodyJSON = JSON.parse(bodyStr);
    bodyJSON.id = 100;
    pm.request.body.update(JSON.stringify(bodyJSON, null, 2));
    console.log("修改后 body", pm.request.body.toJSON());
    

body 类型为 form-data

获取 form-data 信息

// 当 body 类型为 form-data 时,从 pm.request.body.formdata 获取请求参数
var formData = pm.request.body.formdata;

// 获取 key 为 field1 的 form-data 参数的值
var field1 = formData.get("field1");
console.log(field1); // 控制台打印 field1

// 已键值对象方式获取所有 formdata 参数
var formdataObject = formData.toObject();
console.log(formdataObject); // 控制台打印 formdataObject

// 遍历整个 form-data 数据
formData.each((item) => {
  console.log(item.key); // 控制台打印参数名
  console.log(item.value); // 控制台打印参数值
});

设置 form-data 信息

注:可以使用该方法实现文件路径参数化

pm.request.body.update({
    mode: 'formdata',
    formdata: [{
        key: 'foo',
        value: 'bar'
    }]
});

body 类型为 x-www-form-urlencode

获取 x-www-form-urlencode 信息

// 当 body 类型为 x-www-form-urlencode** 时,从 pm.request.body.urlencoded 获取请求参数
var formData = pm.request.body.urlencoded;

// 获取 key 为 field1 的 form-data 参数的值
var field1 = formData.get("field1");

// 已键值对象方式获取所有 formdata 参数
var formdataObject = formData.toObject();

// 遍历整个 form 数据
formData.each((item) => {
  console.log(item.key); // 控制台打印参数名
  console.log(item.value); // 控制台打印参数值
});

设置 x-www-form-urlencode 信息

pm.request.body.update({
    mode: 'urlencoded',
    urlencoded: [{
        key: 'foo',
        value: 'bar'
    }]
});

body 类型为 json

获取 json 信息

// 当 body 类型为 json 时,从 pm.request.body.raw 获取请求参数

try {
  var jsonData = JSON.parse(pm.request.body.raw);
  console.log(jsonData); // 控制台打印参整个 json 数据
} catch (e) {
  console.log(e);
}

body 类型为 raw

获取 raw 信息

// 当 body 类型为 raw 时,从 pm.request.body.raw 获取请求参数

var raw = pm.request.body.raw;
console.log(raw); // 控制台打印参整个 raw 数据

设置 raw 信息

pm.request.body.update('Hello World!');

body 类型为 GraphQL

设置 GraphQL 信息

pm.request.body.update({
    mode: 'graphql',
    graphql: {
        query: `
            query Square($ten: Int!) {
                square(n: $ten)
            }
        `,
        variables: { ten: 10 }
    }
});

断言(测试脚本)

后置脚本是在请求发送完成后执行的代码片段。主要用来断言请求返回的结果是否正确、将请求返回的结果数据写入环境变量等。

Apifox 内置了ChaiJS 作为断言库,以下是常用的断言测试脚本示例,但并非全部示例,更多用法请参考文档

  • 断言请求返回的结果是否正确

    // pm.response.to.have 示例
    pm.test('返回结果状态码为 200', function() {
      pm.response.to.have.status(200);
    });
    
    // pm.expect() 示例
    pm.test('当前为正式环境', function() {
      pm.expect(pm.environment.get('env')).to.equal('production');
    });
    
    // response assertions 示例
    pm.test('返回结果没有错误', function() {
      pm.response.to.not.be.error;
      pm.response.to.have.jsonBody('');
      pm.response.to.not.have.jsonBody('error');
    });
    
    // pm.response.to.be* 示例
    pm.test('返回结果没有错', function() {
      // assert that the status code is 200
      pm.response.to.be.ok; // info, success, redirection, clientError,  serverError, are other variants
      // assert that the response has a valid JSON body
      pm.response.to.be.withBody;
      pm.response.to.be.json; // this assertion also checks if a body  exists, so the above check is not needed
    });
    
  • 将请求返回的结果数据写入环境变量

    // 获取 JSON 格式的请求返回数据
    var jsonData = pm.response.json();
    
    // 将 jsonData.token 的值写入环境变量
    pm.environment.set('token', jsonData.token);
    
  • 检查 response body 是否包含某个字符串

    pm.test('Body matches string', function() {
      pm.expect(pm.response.text()).to.include('string_you_want_to_search');
    });
    
  • 检查 response body 是否包含等于字符串

    pm.test('Body is correct', function() {
      pm.response.to.have.body('response_body_string');
    });
    
  • 检查 json 值

    pm.test('Your test name', function() {
      var jsonData = pm.response.json();
      pm.expect(jsonData.value).to.eql(100);
    });
    
  • 检查 header 是否有设置 Content-Type

    pm.test('Content-Type header is present', function() {
      pm.response.to.have.header('Content-Type');
    });
    
  • 检查请求响应耗时是否低于 200 毫秒

    pm.test('Response time is less than 200ms', function() {
      pm.expect(pm.response.responseTime).to.be.below(200);
    });
    
  • 检查 HTTP 状态码是否为 200

    pm.test('Status code is 200', function() {
      pm.response.to.have.status(200);
    });
    
  • 检查 HTTP 状态码名称是否包含某个字符串

    pm.test('Status code name has string', function() {
      pm.response.to.have.status('Created');
    });
    
  • 是否正确的 POST 请求状态码

    pm.test('Successful POST request', function() {
      pm.expect(pm.response.code).to.be.oneOf([201, 202]);
    });
    

断言(后置操作)

“后置操作”支持添加断言,你可以对接口返回的数据或响应时间设置断言,以判断当前接口返回是否符合预期。

设置断言

打开 Apifox 中的某条接口,在“后置操作”页中设置断言。例如输入 $.data.status

在这里插入图片描述

提示

根对象使用 $ 符号进行表示,而无需区分是对象还是数组。


查看结果

运行后即可查看断言结果:

在这里插入图片描述


主要参考

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐