@cucumber/playwright

中文文档
3.1.0发布于 2021/07/07MITTypeScriptCommonJS零依赖近 30 天下载 4132

用 Cucumber.js 写更好的情景

此软件包已被标记为弃用

Use @cucumber/screenplay instead

中文使用文档
文档目录15 节
  1. 大会
  2. 安装
  3. 使用量
  4. 演员
  5. 与系统的互动
  6. 使用不同的互动执行
  7. 步骤之间共享数据
  8. 从行为者处接触世界
  9. 同步行为和最终的一致性
  10. 高级配置
  11. 使用承诺
  12. 使用明确的演员查询
  13. 覆盖演员 Paremeter 类型选项
  14. 设计建议
  15. 贷项

@cucumber/ playwright (中文(简体) )

CI

Cucumber Playwright 是一个小型的图书馆, 用于 黄瓜.js 能够更好 验收测试(Gherkin 情景):

  • QQ 运行的全装验收测试 毫秒
  • * 鼓励比较容易单独测试的松绑系统组件
  • QQ 以多种方式集合系统组件, 这样您就可以优化 速度 或者说 测试覆盖率
  • 可读的情景描述 什麽 代替 怎么样
  • • 可维护的自动化代码

见 贷方 章节,介绍启发此库的先前工作的细节.

当您使用 Cucumber Playwright 时,您的步骤定义通常是单行道:

When('{actor} logs in successfully', async function (actor: Actor) {
  await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))
})

您可以提供几种执行方式: logIn - 一个与用户界面交互,但 (单位:千美元) 一个 与 API 层交互 内侧 通过直接函数调用或HTTP请求的用户界面.

这迫使您在“ 填充字段” 和“ 点击按钮” 等情景中避免使用UI 语言, 因为它不能使 觉得应该这样 logIn 不使用UI的执行。 同样,它迫使你避免使用HTTP 语言如"执行HTTP POST /login",因为这样做在其中没有意义 logIn 执行 使用UI.

这些限制鼓励您写作 可读 描述 用户可以做什么 狂风比 您的系统是如何执行的.你的场景变成活的文件 所有人都能理解 团队.

大会

有了Cucumber Playwright,你就可以演化出一个接受测试套件,用多种配置来运行,或者 集会编辑 组装图 下级 说明如何:

  • #FFB000 测试组件
  • #DC267F 基础设施构成部分
  • #648FFF 生产部件
DOM - HTTP - 域名DOM 域HTTP 域域名
DOM-HTTP-DomainDOM-DomainHTTP-DomainDomain

安装

首先,将库添加到您的项目中:

npm install @cucumber/playwright --save-dev

使用量

这个向导会带你们走过去 @cucumber/playwright 。 步骤。 详情请参见: 转到 features 目录(也是本库的验收测试).

演员

核心概念 @cucumber/playwright 这是 Actor。一个演员对象代表用户与 系统.

为了从您步骤定义中访问演员对象, 您首先需要定义 {actor} 参数类型。 。 。 .

创建名为的文件 features/support/World.ts (如果你还没有获得)并添加以下代码:

import { defineParameterType, setWorldConstructor } from '@cucumber/cucumber'
import { ActorWorld, ActorParameterType } from '@cucumber/playwright'

// Define an {actor} parameter type that creates Actor objects
defineParameterType(ActorParameterType)

// Define your own World class that extends from ActorWorld
export default class World extends ActorWorld {
}
setWorldConstructor(World)

您的步法定义将会通过 Actor 对象用于 {actor} 参数,例如:

When Martha logs in
When('{actor} logs in', async function (actor: Actor) {
  // The logIn() function is an Interaction
  await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))
})

继续阅读以学习如何定义 互动。 。 。 .

与系统的互动

现在你的步法定义可以通过 Actor 对象,我们需要定义 Interactions,演员可以使用 改为 交互 与系统.

交互是返回另一个函数的函数 Actor 参数.

添加以下内容到 features/support/interactions/logIn.ts数字 :

type LogIn = (email: string, password: string) => Interaction<Promise<string>>

export const logIn: LogIn = (email, password) => {
  return async (actor: Actor) => {
    // Just a dummy implementation for now - we'll come back and flesh this out later
    return '42'
  }
}

在步骤定义中,我们现在可以导入此交互:

import { logIn } from '../support/interactions/logIn'

When('{actor} logs in', async function (actor: Actor) {
  const userId = await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))
})

互动和问题

何况 Actor#attemptsTo 还有一个 Actor#ask 方法。 它的签名完全一样 行为作为 Actor#attemptsTo。它经常使您的代码更容易读取 Actor#attemptsTo 在你身边 When 步骤定义 修改 系统状态,以及 Actor#ask 内 Then 步骤定义 那个 查询 系统状态.

例如:

export type InboxMessages = () => Interaction<readonly string[]>

export const inboxMessages: InboxMessages = (userId) => {
  return (actor: Actor) => {
    return ['hello', 'world'] 
  }
}

在步骤定义中:

import { inboxMessages } from '../support/interactions/inboxMessages'

Then('{actor} should have received the following messages:', function (actor: Actor, expectedMessages: DataTable) {
  const receivedMessages = actor.ask(inboxMessages())
  assert.deepStrictEqual(receivedMessages, expectedMessages.rows.map(row => row[0]))
})

使用不同的互动执行

往往可以采取多种方式实施同样的互动。 这允许你 通过快速反馈逐步建立新的功能.

例如,您可能正在研究允许用户登录的新要求。 你可以开始 在您执行 HTTP 层或周围的 UI 之前, 仅构建服务器侧域逻辑 快速反馈随着你的进展.

稍后,你可以再次运行相同的情景, 但这次交换您的交互 执行 使 HTTP 请求或与 DOM 交互 - 不更改任何代码.

如果你看着 包含在此重播中的高喊示例你会看到,我们组织 我们在两个目录中的相互作用:

features
├── hear_shout.feature
└── support
    └── interactions
        ├── dom
        │   ├── inboxMessages.ts
        │   ├── moveTo.ts
        │   └── shout.ts
        └── session
            ├── inboxMessages.ts
            ├── moveTo.ts
            └── shout.ts

为了在运行时决定使用何种交互执行,您可以使用 交互加载器 提供 内 @cucumber/playwright数字 :

import { setWorldConstructor } from '@cucumber/cucumber'
import { ActorWorld, defineActorParameterType, Interaction } from '@cucumber/playwright'
import { InboxMessages, Shout, StartSession } from './interactions/types'

export default class World extends ActorWorld {
  public startSession: StartSession
  public shout: Shout
  public inboxMessages: InboxMessages
}
setWorldConstructor(World)

Before(async function (this: World) {
  // Loads this.startSession, this.shout and this.inboxMessages
  await this.loadInteractions()
})

这个 await this.interaction(...) 调用将从基于 数值 interactions 世界参数, (中文(简体) ), 定义 ./cucumber.js 配置文件 .

如果你使用这种技术,你还需要调整你的步骤定义,以参考来自 世界 (单位:千美元)this:

When('{actor} shouts {string}', async function (this: World, actor: Actor, message: string) {
  await actor.attemptsTo(this.shout(message))
})

步骤之间共享数据

你的演员们有权力 remember 财务报告和审定财务报表 recall 数据在步间。 例如:

When('{actor} logs in', async function (actor: Actor<World>) {
  const userId = await actor.attemptsTo(logIn(`${actor.name}@test.com`, 'valid-password'))
  actor.remember('userId', userId)
})

Then('{actor} should be logged in', function (actor: Actor) {
  assert.ok(actor.recall('userId'))
})

您也可以将函数传递给 remember 以纪念一个值 :

function getSomething(actor: Actor) {
  actor.recall('something', () => ({ foo: 'bar' }))
}

// The same object is returned every time
assert.strictEqual(getSomething(actor), getSomething(actor))

说明: 记录的数据范围为: Actor,所以你不能访问一个演员从另一个演员中记住的数据 一个 你可以让多个演员用同一个密钥存储不同的数据. 每个 Actor 在结尾处丢弃 每一个情景,所以你将无法 recall 任何来自之前的情景.

从行为者处接触世界

如果您的交互需要访问数据 world他们可以通过 Actor#world 属性。 狦琌 为此,您还应声明互动执行中角色的通用类型:

export const moveTo: MoveTo = (coordinate) => {
  // We're declaring the World type of the actor so that we can access its members
  return async (actor: Actor<World>) => {
    actor.world.shouty.moveTo(actor.name, coordinate)
  }
}

同步行为和最终的一致性

在分布式系统中,行动结果在整个系统中传播可能需要一段时间.

例如,在聊天应用程序中,当一个用户发送消息时,可能要花上几毫秒的时间才能到达 其他用户收到消息,因为它通过网络行走,即使它都在你的机器上.

在这样的情况下,你可以使用 eventually 函数以定期检查特定条件:

Then('{actor} hears {actor}’s message', async function (this: World, listener: Actor<World>, shouter: Actor) {
  const shouterLastMessage = shouter.recall('lastMessage')

  await eventually(() => {
    const listenerMessages = listener.ask(this.inboxMessages())
    assert.deepStrictEqual(listenerMessages, [shouterLastMessage])
  })
})

这个 eventually 函数接受单个参数 - 零参数 condition 函数。 如果 condition 函数 丢出一个错误, 它会在常规中再次被调用 interval 直到它没有例外. 如果它不通过或完成在一个 timeout 时,会显示超时出错.

默认 interval 实值 50ms 默认 timeout 实值 1000ms。这可以用一秒钟取代 { interval: number, timeout: number } 参数 condition。 。 。 .

高级配置

以下是一些更高级配置的准则.

使用承诺

默认类型 Interaction 实值 void。如果您的系统是同步的 (即用途) async 函数返回 a Promise),可以使用 PromiseInteraction 类型代替 Interaction。 。 。 .

使用明确的演员查询

如果无法延长 ActorWorld,您可以添加一个 ActorLookup 字段到您这样的现有世界类:

import { ActorLookup } from '@cucumber/playwright'

class World {
  private readonly actorLookUp = new ActorLookup()

  public findOrCreateActor(actorName: string): Actor<World> {
    return this.actorLookUp.findOrCreateActor(this, actorName)
  }
}

覆盖演员 Paremeter 类型选项

这个 defineParameterType(ActorParameterType) 函数调用定义命名的参数类型 {actor} 默认, 它使用 RegExp /[A-Z][a-z]+/ (一作入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入.

如果您想要使用不同的名称或正则表达式, 您可以覆盖这些默认 :

defineParameterType({ ...ActorParameterType, name: 'acteur' })
defineParameterType({ ...ActorParameterType, regexp: /Marcel|Bernadette|Hubert/ })
defineParameterType({ ...ActorParameterType, name: 'acteur', regexp: /Marcel|Bernadette|Hubert/ })

设计建议

当你合作的时候 @cucumber/playwright 我们建议你们只用两层 互动执行:

  • dom 用于使用 DOM 的相互作用
  • session 用于使用 Session

A 类 Session 代表一个与您的系统有互动会话的用户(参与者). A 类 Session 将通常使用 在代码的两个位置:

  • 从你的 session 互动
  • 从您的 UI 代码( React/Vue 组件等)

Session 是一个针对您执行的界面,您应当执行您自己。 您的用户界面代码 将使用它与服务器交互。 这种对关切的区分使网络实施细节无法用于 血液进入UI代码.

你通常有两次执行 Session 接口 - HttpSession 财务报告和审定财务报表 ShoutySession。 。 。 .

这个 HttpSession 也就是你将所有 fetch, (中文(简体) ), WebSocket 财务报告和审定财务报表 EventSource 逻辑 这是班级 你的UI会用于生产。 你也会在测试中使用它.

这个 ShoutySession 是直接与服务器侧域层交谈并直接调用函数的操作 (不设任何网络). 这一实施将仅用于测试.

通过这种方式组织你的代码,你有四种方式 你可以运行你的"木瓜假想".

  • session 使用 ShoutySession (快速测试,域层覆盖)
  • session 使用逗号 HttpSession (较慢的测试,http+域层覆盖)
  • dom 使用 ShoutySession (较慢的测试,UI+域层覆盖)
  • dom 使用 HttpSession (最慢测试,UI+http+域层覆盖)

我们用的例子 世界参数 控制如何与系统互动以及系统如何组装.

贷项

这个图书馆的灵感来自 剧作家 模式.

这个库旨在提供一个比原Playwright模式更温和的学习曲线,因此有些概念如: 能力 财务报告和审定财务报表 任务 被排除在外.

版本与发布记录

@cucumber/playwright 共发布 2 个版本,首次发布于 2021/07/07;过去 12 个月发布了 0 个版本。

版本发布日期距今
3.1.0latest2021/07/075 年前
3.0.02021/07/075 年前

关于 @cucumber/playwright 的常见问题

@cucumber/playwright 是什么?

用 Cucumber.js 写更好的情景注意:该包已被作者标记为弃用——Use @cucumber/screenplay instead

如何安装 @cucumber/playwright?

在项目目录运行 npm install @cucumber/playwright,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@cucumber/cucumber(^7.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cucumber/playwright 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cucumber/playwright 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@cucumber/playwright 可以商用吗?

@cucumber/playwright 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cucumber/playwright 的下载量有多少?

根据 npm 官方下载统计,@cucumber/playwright 在 2026/10/11 – 2026/11/09 这 30 天内被下载 4,132 次,日均约 138 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cucumber/playwright 还在维护吗?

最新版本 3.1.0 发布于 2021/07/07(5 年前);至今共发布 2 个版本,其中过去 12 个月发布了 0 个;首次发布于 2021/07/07。作者已将其标记为弃用,新项目建议寻找替代方案。