@cucumber/ playwright (中文(简体) )
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,你就可以演化出一个接受测试套件,用多种配置来运行,或者 集会编辑 组装图 下级 说明如何:
测试组件
基础设施构成部分
生产部件
| DOM - HTTP - 域名 | DOM 域 | HTTP 域 | 域名 |
|---|---|---|---|
安装
首先,将库添加到您的项目中:
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+域层覆盖)
我们用的例子 世界参数 控制如何与系统互动以及系统如何组装.
贷项
这个图书馆的灵感来自 剧作家 模式.
- Beyond Page Objects: 下一代测试 带有宁静和 Playwright 模式的自动化 由安迪·帕尔默,安东尼·马尔卡诺,约翰·弗格森·斯马特和扬·莫拉克出演
- 静态/静态 - JavaScript/TypeScript首次由Jan Molak执行 Playwright 模式
- 剧作家模式 如扬·莫拉克所描述的
- 理解剧作家 - Matt Wynne的博客系列
这个库旨在提供一个比原Playwright模式更温和的学习曲线,因此有些概念如: 能力 财务报告和审定财务报表 任务 被排除在外.