You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Asciidoctor(AsciiDoc)集成至Cypress并结合二者编写测试

可行性结论

完全可以实现,无需依赖第三方封装的专用集成插件。核心实现逻辑是通过Asciidoctor官方的JS解析库,读取结构化编写的AsciiDoc测试用例文件,将文档内的测试套件、单用例信息、前置条件、操作步骤、断言规则映射为Cypress原生的describe、it块及对应API调用,最终达成「一份AsciiDoc文件同时作为可阅读的测试文档、可直接执行的自动化测试脚本」的效果,非技术角色也能直接参与用例编写维护。

具体实现步骤
  • 安装基础依赖
    进入已初始化的Cypress项目根目录,执行以下命令安装解析所需依赖:

    npm install @asciidoctor/core --save-dev
    
  • 约定AsciiDoc测试用例写作规范
    先统一用例的标记规则,避免解析逻辑混乱,推荐的最小可用格式示例如下:

    = 用户登录模块测试套件
    :suite-tag: smoke
    
    == 正确账号密码登录成功
    :case-priority: P0
    :precondition: 访问页面路径 /login
    
    . 在用户名输入框输入内容 `test_user_01`
    . 在密码输入框输入内容 `Test@123456`
    . 点击id为`login-submit`的登录按钮
    . 断言:页面路径跳转至 `/dashboard`
    . 断言:页面可见文本 `欢迎回来,test_user_01`
    
    == 错误密码登录提示失败
    :case-priority: P1
    :precondition: 访问页面路径 /login
    
    . 在用户名输入框输入内容 `test_user_01`
    . 在密码输入框输入内容 `wrong_pass`
    . 点击id为`login-submit`的登录按钮
    . 断言:页面class为`error-tip`的元素可见文本 `账号或密码错误,请重试`
    

    规则说明:一级标题对应Cypress测试套件名,文档属性(:key: value格式)存储测试标签、优先级、前置条件等元信息,二级标题对应单个测试用例名,有序列表对应逐行执行的测试步骤,带断言:前缀的步骤映射为Cypress断言逻辑,反引号包裹的内容为步骤对应的操作参数。

  • 编写AsciiDoc用例解析器
    在Cypress的支持文件目录下新建asciidoc-loader.js,实现AsciiDoc AST到Cypress调用的映射逻辑,核心代码如下:

    const asciidoctor = require('@asciidoctor/core')()
    const path = require('path')
    const fs = require('fs')
    
    function loadAdocCases(caseDir = path.join(process.cwd(), 'cypress/e2e/adoc-cases')) {
      // 读取目录下所有.adoc后缀的用例文件
      const caseFiles = fs.readdirSync(caseDir).filter(file => file.endsWith('.adoc'))
      
      caseFiles.forEach(file => {
        const fileContent = fs.readFileSync(path.join(caseDir, file), 'utf-8')
        const doc = asciidoctor.load(fileContent)
        const suiteName = doc.getTitle()
    
        // 生成Cypress测试套件
        describe(suiteName, () => {
          // 遍历每个二级标题对应的单测试用例
          doc.getSections().forEach(section => {
            const caseName = section.getTitle()
            const precondition = section.getAttribute('precondition')
            // 提取有序列表内的所有步骤
            const stepList = section.getBlocks()
              .find(block => block.getContext() === 'olist')
              ?.getItems()
              ?.map(item => item.getText()) || []
    
            // 生成单个Cypress测试用例
            it(caseName, () => {
              // 执行前置操作
              if (precondition?.startsWith('访问页面路径')) {
                const targetUrl = precondition.match(/`(.*?)`/)[1]
                cy.visit(targetUrl)
              }
    
              // 逐行解析执行步骤
              stepList.forEach(step => {
                // 输入框操作映射
                if (step.startsWith('在用户名输入框输入内容')) {
                  const content = step.match(/`(.*?)`/)[1]
                  cy.get('[data-testid="username-input"]').type(content)
                }
                if (step.startsWith('在密码输入框输入内容')) {
                  const content = step.match(/`(.*?)`/)[1]
                  cy.get('[data-testid="password-input"]').type(content)
                }
                // 点击操作映射
                if (step.startsWith('点击id为')) {
                  const eleId = step.match(/`(.*?)`/)[1]
                  cy.get(`#${eleId}`).click()
                }
                // 路径断言映射
                if (step.startsWith('断言:页面路径跳转至')) {
                  const targetPath = step.match(/`(.*?)`/)[1]
                  cy.location('pathname').should('eq', targetPath)
                }
                // 文本可见断言映射
                if (step.startsWith('断言:页面可见文本')) {
                  const text = step.match(/`(.*?)`/)[1]
                  cy.contains(text).should('be.visible')
                }
                // 元素文本断言映射
                if (step.startsWith('断言:页面class为')) {
                  const [, className, expectedText] = step.match(/class为`(.*?)`的元素可见文本 `(.*?)`/)
                  cy.get(`.${className}`).should('contain', expectedText)
                }
              })
            })
          })
        })
      })
    }
    
    module.exports = { loadAdocCases }
    
  • 配置Cypress加载执行AsciiDoc用例
    在Cypress的e2e测试目录下新建入口执行文件run-adoc-cases.cy.js,内容仅需引入解析器并触发加载即可:

    import { loadAdocCases } from '../support/asciidoc-loader'
    // 解析所有AsciiDoc用例并动态生成可执行测试
    loadAdocCases()
    

    后续将写好的.adoc格式用例放到cypress/e2e/adoc-cases目录下,正常启动Cypress运行测试即可,执行效果和原生JS/TS编写的用例完全一致,支持截图、录屏、测试报告等所有Cypress原生能力。

扩展优化提示
  • 初期不需要覆盖所有操作类型,先把团队高频使用的操作、断言规则做进映射逻辑即可,后续按需扩展,比引入重量的BDD框架灵活度更高、维护成本更低。
  • 可以给AsciiDoc用例增加自定义属性支持测试筛选、接口Mock、测试数据注入等能力,比如加:mock: getUserInfo 200的属性,解析时自动触发cy.intercept逻辑。
  • 调试解析逻辑时,可以先打印Asciidoctor解析出的AST结构,确认节点类型、属性、文本内容读取正确后再写映射规则,排查问题效率更高。

内容的提问来源于stack exchange,提问作者DMate

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:15:45